View Javadoc
1   /*
2    * Copyright (C) 2010, 2014 Chris Aniszczyk <caniszczyk@gmail.com>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.api;
44  
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertNotNull;
47  import static org.junit.Assert.assertTrue;
48  import static org.junit.Assert.fail;
49  
50  import java.io.File;
51  import java.io.IOException;
52  import java.net.URISyntaxException;
53  import java.util.Properties;
54  
55  import org.eclipse.jgit.api.errors.GitAPIException;
56  import org.eclipse.jgit.api.errors.JGitInternalException;
57  import org.eclipse.jgit.api.errors.TransportException;
58  import org.eclipse.jgit.errors.MissingObjectException;
59  import org.eclipse.jgit.hooks.PrePushHook;
60  import org.eclipse.jgit.junit.JGitTestUtil;
61  import org.eclipse.jgit.junit.RepositoryTestCase;
62  import org.eclipse.jgit.lib.ObjectId;
63  import org.eclipse.jgit.lib.Ref;
64  import org.eclipse.jgit.lib.RefUpdate;
65  import org.eclipse.jgit.lib.Repository;
66  import org.eclipse.jgit.lib.StoredConfig;
67  import org.eclipse.jgit.revwalk.RevCommit;
68  import org.eclipse.jgit.transport.PushResult;
69  import org.eclipse.jgit.transport.RefLeaseSpec;
70  import org.eclipse.jgit.transport.RefSpec;
71  import org.eclipse.jgit.transport.RemoteConfig;
72  import org.eclipse.jgit.transport.RemoteRefUpdate;
73  import org.eclipse.jgit.transport.TrackingRefUpdate;
74  import org.eclipse.jgit.transport.URIish;
75  import org.eclipse.jgit.util.FS;
76  import org.junit.Test;
77  
78  public class PushCommandTest extends RepositoryTestCase {
79  
80  	@Test
81  	public void testPush() throws JGitInternalException, IOException,
82  			GitAPIException, URISyntaxException {
83  
84  		// create other repository
85  		Repository db2 = createWorkRepository();
86  		final StoredConfig config2 = db2.getConfig();
87  
88  		// this tests that this config can be parsed properly
89  		config2.setString("fsck", "", "missingEmail", "ignore");
90  		config2.save();
91  
92  		// setup the first repository
93  		final StoredConfig config = db.getConfig();
94  		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
95  		URIish uri = new URIish(db2.getDirectory().toURI().toURL());
96  		remoteConfig.addURI(uri);
97  		remoteConfig.update(config);
98  		config.save();
99  
100 		try (Git git1 = new Git(db)) {
101 			// create some refs via commits and tag
102 			RevCommit commit = git1.commit().setMessage("initial commit").call();
103 			Ref tagRef = git1.tag().setName("tag").call();
104 
105 			try {
106 				db2.resolve(commit.getId().getName() + "^{commit}");
107 				fail("id shouldn't exist yet");
108 			} catch (MissingObjectException e) {
109 				// we should get here
110 			}
111 
112 			RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
113 			git1.push().setRemote("test").setRefSpecs(spec)
114 					.call();
115 
116 			assertEquals(commit.getId(),
117 					db2.resolve(commit.getId().getName() + "^{commit}"));
118 			assertEquals(tagRef.getObjectId(),
119 					db2.resolve(tagRef.getObjectId().getName()));
120 		}
121 	}
122 
123 	@Test
124 	public void testPrePushHook() throws JGitInternalException, IOException,
125 			GitAPIException, URISyntaxException {
126 
127 		// create other repository
128 		Repository db2 = createWorkRepository();
129 
130 		// setup the first repository
131 		final StoredConfig config = db.getConfig();
132 		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
133 		URIish uri = new URIish(db2.getDirectory().toURI().toURL());
134 		remoteConfig.addURI(uri);
135 		remoteConfig.update(config);
136 		config.save();
137 
138 		File hookOutput = new File(getTemporaryDirectory(), "hookOutput");
139 		writeHookFile(PrePushHook.NAME, "#!/bin/sh\necho 1:$1, 2:$2, 3:$3 >\""
140 				+ hookOutput.toPath() + "\"\ncat - >>\"" + hookOutput.toPath()
141 				+ "\"\nexit 0");
142 
143 		try (Git git1 = new Git(db)) {
144 			// create some refs via commits and tag
145 			RevCommit commit = git1.commit().setMessage("initial commit").call();
146 
147 			RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
148 			git1.push().setRemote("test").setRefSpecs(spec).call();
149 			assertEquals("1:test, 2:" + uri + ", 3:\n" + "refs/heads/master "
150 					+ commit.getName() + " refs/heads/x "
151 					+ ObjectId.zeroId().name(), read(hookOutput));
152 		}
153 	}
154 
155 	private File writeHookFile(String name, String data)
156 			throws IOException {
157 		File path = new File(db.getWorkTree() + "/.git/hooks/", name);
158 		JGitTestUtil.write(path, data);
159 		FS.DETECTED.setExecute(path, true);
160 		return path;
161 	}
162 
163 
164 	@Test
165 	public void testTrackingUpdate() throws Exception {
166 		Repository db2 = createBareRepository();
167 
168 		String remote = "origin";
169 		String branch = "refs/heads/master";
170 		String trackingBranch = "refs/remotes/" + remote + "/master";
171 
172 		try (Git git = new Git(db)) {
173 			RevCommit commit1 = git.commit().setMessage("Initial commit")
174 					.call();
175 
176 			RefUpdate branchRefUpdate = db.updateRef(branch);
177 			branchRefUpdate.setNewObjectId(commit1.getId());
178 			branchRefUpdate.update();
179 
180 			RefUpdate trackingBranchRefUpdate = db.updateRef(trackingBranch);
181 			trackingBranchRefUpdate.setNewObjectId(commit1.getId());
182 			trackingBranchRefUpdate.update();
183 
184 			final StoredConfig config = db.getConfig();
185 			RemoteConfig remoteConfig = new RemoteConfig(config, remote);
186 			URIish uri = new URIish(db2.getDirectory().toURI().toURL());
187 			remoteConfig.addURI(uri);
188 			remoteConfig.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/"
189 					+ remote + "/*"));
190 			remoteConfig.update(config);
191 			config.save();
192 
193 
194 			RevCommit commit2 = git.commit().setMessage("Commit to push").call();
195 
196 			RefSpec spec = new RefSpec(branch + ":" + branch);
197 			Iterable<PushResult> resultIterable = git.push().setRemote(remote)
198 					.setRefSpecs(spec).call();
199 
200 			PushResult result = resultIterable.iterator().next();
201 			TrackingRefUpdate trackingRefUpdate = result
202 					.getTrackingRefUpdate(trackingBranch);
203 
204 			assertNotNull(trackingRefUpdate);
205 			assertEquals(trackingBranch, trackingRefUpdate.getLocalName());
206 			assertEquals(branch, trackingRefUpdate.getRemoteName());
207 			assertEquals(commit2.getId(), trackingRefUpdate.getNewObjectId());
208 			assertEquals(commit2.getId(), db.resolve(trackingBranch));
209 			assertEquals(commit2.getId(), db2.resolve(branch));
210 		}
211 	}
212 
213 	/**
214 	 * Check that pushes over file protocol lead to appropriate ref-updates.
215 	 *
216 	 * @throws Exception
217 	 */
218 	@Test
219 	public void testPushRefUpdate() throws Exception {
220 		try (Git git = new Git(db);
221 				Git git2 = new Git(createBareRepository())) {
222 			final StoredConfig config = git.getRepository().getConfig();
223 			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
224 			URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
225 					.toURL());
226 			remoteConfig.addURI(uri);
227 			remoteConfig.addPushRefSpec(new RefSpec("+refs/heads/*:refs/heads/*"));
228 			remoteConfig.update(config);
229 			config.save();
230 
231 			writeTrashFile("f", "content of f");
232 			git.add().addFilepattern("f").call();
233 			RevCommit commit = git.commit().setMessage("adding f").call();
234 
235 			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
236 			git.push().setRemote("test").call();
237 			assertEquals(commit.getId(),
238 					git2.getRepository().resolve("refs/heads/master"));
239 
240 			git.branchCreate().setName("refs/heads/test").call();
241 			git.checkout().setName("refs/heads/test").call();
242 
243 			for (int i = 0; i < 6; i++) {
244 				writeTrashFile("f" + i, "content of f" + i);
245 				git.add().addFilepattern("f" + i).call();
246 				commit = git.commit().setMessage("adding f" + i).call();
247 				git.push().setRemote("test").call();
248 				git2.getRepository().getRefDatabase().getRefs();
249 				assertEquals("failed to update on attempt " + i, commit.getId(),
250 						git2.getRepository().resolve("refs/heads/test"));
251 			}
252 		}
253 	}
254 
255 	/**
256 	 * Check that the push refspec is read from config.
257 	 *
258 	 * @throws Exception
259 	 */
260 	@Test
261 	public void testPushWithRefSpecFromConfig() throws Exception {
262 		try (Git git = new Git(db);
263 				Git git2 = new Git(createBareRepository())) {
264 			final StoredConfig config = git.getRepository().getConfig();
265 			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
266 			URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
267 					.toURL());
268 			remoteConfig.addURI(uri);
269 			remoteConfig.addPushRefSpec(new RefSpec("HEAD:refs/heads/newbranch"));
270 			remoteConfig.update(config);
271 			config.save();
272 
273 			writeTrashFile("f", "content of f");
274 			git.add().addFilepattern("f").call();
275 			RevCommit commit = git.commit().setMessage("adding f").call();
276 
277 			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
278 			git.push().setRemote("test").call();
279 			assertEquals(commit.getId(),
280 					git2.getRepository().resolve("refs/heads/newbranch"));
281 		}
282 	}
283 
284 	/**
285 	 * Check that only HEAD is pushed if no refspec is given.
286 	 *
287 	 * @throws Exception
288 	 */
289 	@Test
290 	public void testPushWithoutPushRefSpec() throws Exception {
291 		try (Git git = new Git(db);
292 				Git git2 = new Git(createBareRepository())) {
293 			final StoredConfig config = git.getRepository().getConfig();
294 			RemoteConfig remoteConfig = new RemoteConfig(config, "test");
295 			URIish uri = new URIish(git2.getRepository().getDirectory().toURI()
296 					.toURL());
297 			remoteConfig.addURI(uri);
298 			remoteConfig.addFetchRefSpec(new RefSpec(
299 					"+refs/heads/*:refs/remotes/origin/*"));
300 			remoteConfig.update(config);
301 			config.save();
302 
303 			writeTrashFile("f", "content of f");
304 			git.add().addFilepattern("f").call();
305 			RevCommit commit = git.commit().setMessage("adding f").call();
306 
307 			git.checkout().setName("not-pushed").setCreateBranch(true).call();
308 			git.checkout().setName("branchtopush").setCreateBranch(true).call();
309 
310 			assertEquals(null,
311 					git2.getRepository().resolve("refs/heads/branchtopush"));
312 			assertEquals(null, git2.getRepository()
313 					.resolve("refs/heads/not-pushed"));
314 			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
315 			git.push().setRemote("test").call();
316 			assertEquals(commit.getId(),
317 					git2.getRepository().resolve("refs/heads/branchtopush"));
318 			assertEquals(null, git2.getRepository()
319 					.resolve("refs/heads/not-pushed"));
320 			assertEquals(null, git2.getRepository().resolve("refs/heads/master"));
321 		}
322 	}
323 
324 	/**
325 	 * Check that missing refs don't cause errors during push
326 	 *
327 	 * @throws Exception
328 	 */
329 	@Test
330 	public void testPushAfterGC() throws Exception {
331 		// create other repository
332 		Repository db2 = createWorkRepository();
333 
334 		// setup the first repository
335 		final StoredConfig config = db.getConfig();
336 		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
337 		URIish uri = new URIish(db2.getDirectory().toURI().toURL());
338 		remoteConfig.addURI(uri);
339 		remoteConfig.update(config);
340 		config.save();
341 
342 		try (Git git1 = new Git(db);
343 				Git git2 = new Git(db2)) {
344 			// push master (with a new commit) to the remote
345 			git1.commit().setMessage("initial commit").call();
346 
347 			RefSpec spec = new RefSpec("refs/heads/*:refs/heads/*");
348 			git1.push().setRemote("test").setRefSpecs(spec).call();
349 
350 			// create an unrelated ref and a commit on our remote
351 			git2.branchCreate().setName("refs/heads/other").call();
352 			git2.checkout().setName("refs/heads/other").call();
353 
354 			writeTrashFile("a", "content of a");
355 			git2.add().addFilepattern("a").call();
356 			RevCommit commit2 = git2.commit().setMessage("adding a").call();
357 
358 			// run a gc to ensure we have a bitmap index
359 			Properties res = git1.gc().setExpire(null).call();
360 			assertEquals(7, res.size());
361 
362 			// create another commit so we have something else to push
363 			writeTrashFile("b", "content of b");
364 			git1.add().addFilepattern("b").call();
365 			RevCommit commit3 = git1.commit().setMessage("adding b").call();
366 
367 			try {
368 				// Re-run the push.  Failure may happen here.
369 				git1.push().setRemote("test").setRefSpecs(spec).call();
370 			} catch (TransportException e) {
371 				assertTrue("should be caused by a MissingObjectException", e
372 						.getCause().getCause() instanceof MissingObjectException);
373 				fail("caught MissingObjectException for a change we don't have");
374 			}
375 
376 			// Remote will have both a and b.  Master will have only b
377 			try {
378 				db.resolve(commit2.getId().getName() + "^{commit}");
379 				fail("id shouldn't exist locally");
380 			} catch (MissingObjectException e) {
381 				// we should get here
382 			}
383 			assertEquals(commit2.getId(),
384 					db2.resolve(commit2.getId().getName() + "^{commit}"));
385 			assertEquals(commit3.getId(),
386 					db2.resolve(commit3.getId().getName() + "^{commit}"));
387 		}
388 	}
389 
390 	@Test
391 	public void testPushWithLease() throws JGitInternalException, IOException,
392 			GitAPIException, URISyntaxException {
393 
394 		// create other repository
395 		Repository db2 = createWorkRepository();
396 
397 		// setup the first repository
398 		final StoredConfig config = db.getConfig();
399 		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
400 		URIish uri = new URIish(db2.getDirectory().toURI().toURL());
401 		remoteConfig.addURI(uri);
402 		remoteConfig.update(config);
403 		config.save();
404 
405 		try (Git git1 = new Git(db)) {
406 			// create one commit and push it
407 			RevCommit commit = git1.commit().setMessage("initial commit").call();
408 			git1.branchCreate().setName("initial").call();
409 
410 			RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x");
411 			git1.push().setRemote("test").setRefSpecs(spec)
412 					.call();
413 
414 			assertEquals(commit.getId(),
415 					db2.resolve(commit.getId().getName() + "^{commit}"));
416 			//now try to force-push a new commit, with a good lease
417 
418 			git1.commit().setMessage("second commit").call();
419 			Iterable<PushResult> results =
420 					git1.push().setRemote("test").setRefSpecs(spec)
421 							.setRefLeaseSpecs(new RefLeaseSpec("refs/heads/x", "initial"))
422 							.call();
423 			for (PushResult result : results) {
424 				RemoteRefUpdate update = result.getRemoteUpdate("refs/heads/x");
425 				assertEquals(update.getStatus(), RemoteRefUpdate.Status.OK);
426 			}
427 
428 			git1.commit().setMessage("third commit").call();
429 			//now try to force-push a new commit, with a bad lease
430 
431 			results =
432 					git1.push().setRemote("test").setRefSpecs(spec)
433 							.setRefLeaseSpecs(new RefLeaseSpec("refs/heads/x", "initial"))
434 							.call();
435 			for (PushResult result : results) {
436 				RemoteRefUpdate update = result.getRemoteUpdate("refs/heads/x");
437 				assertEquals(update.getStatus(), RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED);
438 			}
439 		}
440 	}
441 }