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