View Javadoc
1   /*
2    * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.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.assertFalse;
14  import static org.junit.Assert.assertNull;
15  import static org.junit.Assert.fail;
16  
17  import java.util.List;
18  
19  import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
20  import org.eclipse.jgit.api.ListBranchCommand.ListMode;
21  import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException;
22  import org.eclipse.jgit.api.errors.DetachedHeadException;
23  import org.eclipse.jgit.api.errors.GitAPIException;
24  import org.eclipse.jgit.api.errors.InvalidRefNameException;
25  import org.eclipse.jgit.api.errors.JGitInternalException;
26  import org.eclipse.jgit.api.errors.NotMergedException;
27  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
28  import org.eclipse.jgit.api.errors.RefNotFoundException;
29  import org.eclipse.jgit.junit.RepositoryTestCase;
30  import org.eclipse.jgit.lib.Constants;
31  import org.eclipse.jgit.lib.ObjectId;
32  import org.eclipse.jgit.lib.Ref;
33  import org.eclipse.jgit.lib.RefUpdate;
34  import org.eclipse.jgit.lib.Repository;
35  import org.eclipse.jgit.lib.StoredConfig;
36  import org.eclipse.jgit.revwalk.RevCommit;
37  import org.eclipse.jgit.transport.FetchResult;
38  import org.eclipse.jgit.transport.RefSpec;
39  import org.eclipse.jgit.transport.RemoteConfig;
40  import org.eclipse.jgit.transport.URIish;
41  import org.junit.Before;
42  import org.junit.Test;
43  
44  public class BranchCommandTest extends RepositoryTestCase {
45  	private Git git;
46  
47  	RevCommit initialCommit;
48  
49  	RevCommit secondCommit;
50  
51  	@Override
52  	@Before
53  	public void setUp() throws Exception {
54  		super.setUp();
55  		git = new Git(db);
56  		// checkout master
57  		git.commit().setMessage("initial commit").call();
58  		// commit something
59  		writeTrashFile("Test.txt", "Hello world");
60  		git.add().addFilepattern("Test.txt").call();
61  		initialCommit = git.commit().setMessage("Initial commit").call();
62  		writeTrashFile("Test.txt", "Some change");
63  		git.add().addFilepattern("Test.txt").call();
64  		secondCommit = git.commit().setMessage("Second commit").call();
65  		// create a master branch
66  		RefUpdate rup = db.updateRef("refs/heads/master");
67  		rup.setNewObjectId(initialCommit.getId());
68  		rup.setForceUpdate(true);
69  		rup.update();
70  	}
71  
72  	private Git setUpRepoWithRemote() throws Exception {
73  		Repository remoteRepository = createWorkRepository();
74  		addRepoToClose(remoteRepository);
75  		try (Git remoteGit = new Git(remoteRepository)) {
76  			// commit something
77  			writeTrashFile("Test.txt", "Hello world");
78  			remoteGit.add().addFilepattern("Test.txt").call();
79  			initialCommit = remoteGit.commit().setMessage("Initial commit").call();
80  			writeTrashFile("Test.txt", "Some change");
81  			remoteGit.add().addFilepattern("Test.txt").call();
82  			secondCommit = remoteGit.commit().setMessage("Second commit").call();
83  			// create a master branch
84  			RefUpdate rup = remoteRepository.updateRef("refs/heads/master");
85  			rup.setNewObjectId(initialCommit.getId());
86  			rup.forceUpdate();
87  
88  			Repository localRepository = createWorkRepository();
89  			addRepoToClose(localRepository);
90  			Git localGit = new Git(localRepository);
91  			StoredConfig config = localRepository.getConfig();
92  			RemoteConfig rc = new RemoteConfig(config, "origin");
93  			rc.addURI(new URIish(remoteRepository.getDirectory().getAbsolutePath()));
94  			rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
95  			rc.update(config);
96  			config.save();
97  			FetchResult res = localGit.fetch().setRemote("origin").call();
98  			assertFalse(res.getTrackingRefUpdates().isEmpty());
99  			rup = localRepository.updateRef("refs/heads/master");
100 			rup.setNewObjectId(initialCommit.getId());
101 			rup.forceUpdate();
102 			rup = localRepository.updateRef(Constants.HEAD);
103 			rup.link("refs/heads/master");
104 			rup.setNewObjectId(initialCommit.getId());
105 			rup.update();
106 			return localGit;
107 		}
108 	}
109 
110 	@Test
111 	public void testCreateAndList() throws Exception {
112 		int localBefore;
113 		int remoteBefore;
114 		int allBefore;
115 
116 		// invalid name not allowed
117 		try {
118 			git.branchCreate().setName("In va lid").call();
119 			fail("Create branch with invalid ref name should fail");
120 		} catch (InvalidRefNameException e) {
121 			// expected
122 		}
123 		// existing name not allowed w/o force
124 		try {
125 			git.branchCreate().setName("master").call();
126 			fail("Create branch with existing ref name should fail");
127 		} catch (RefAlreadyExistsException e) {
128 			// expected
129 		}
130 
131 		localBefore = git.branchList().call().size();
132 		remoteBefore = git.branchList().setListMode(ListMode.REMOTE).call()
133 				.size();
134 		allBefore = git.branchList().setListMode(ListMode.ALL).call().size();
135 
136 		assertEquals(localBefore + remoteBefore, allBefore);
137 		Ref newBranch = createBranch(git, "NewForTestList", false, "master",
138 				null);
139 		assertEquals("refs/heads/NewForTestList", newBranch.getName());
140 
141 		assertEquals(1, git.branchList().call().size() - localBefore);
142 		assertEquals(0, git.branchList().setListMode(ListMode.REMOTE).call()
143 				.size()
144 				- remoteBefore);
145 		assertEquals(1, git.branchList().setListMode(ListMode.ALL).call()
146 				.size()
147 				- allBefore);
148 		// we can only create local branches
149 		newBranch = createBranch(git,
150 				"refs/remotes/origin/NewRemoteForTestList", false, "master",
151 				null);
152 		assertEquals("refs/heads/refs/remotes/origin/NewRemoteForTestList",
153 				newBranch.getName());
154 		assertEquals(2, git.branchList().call().size() - localBefore);
155 		assertEquals(0, git.branchList().setListMode(ListMode.REMOTE).call()
156 				.size()
157 				- remoteBefore);
158 		assertEquals(2, git.branchList().setListMode(ListMode.ALL).call()
159 				.size()
160 				- allBefore);
161 	}
162 
163 	@Test(expected = InvalidRefNameException.class)
164 	public void testInvalidBranchHEAD() throws Exception {
165 		git.branchCreate().setName("HEAD").call();
166 		fail("Create branch with invalid ref name should fail");
167 	}
168 
169 	@Test(expected = InvalidRefNameException.class)
170 	public void testInvalidBranchDash() throws Exception {
171 		git.branchCreate().setName("-x").call();
172 		fail("Create branch with invalid ref name should fail");
173 	}
174 
175 	@Test
176 	public void testListAllBranchesShouldNotDie() throws Exception {
177 		setUpRepoWithRemote().branchList().setListMode(ListMode.ALL).call();
178 	}
179 
180 	@Test
181 	public void testListBranchesWithContains() throws Exception {
182 		git.branchCreate().setName("foo").setStartPoint(secondCommit).call();
183 
184 		List<Ref> refs = git.branchList().call();
185 		assertEquals(2, refs.size());
186 
187 		List<Ref> refsContainingSecond = git.branchList()
188 				.setContains(secondCommit.name()).call();
189 		assertEquals(1, refsContainingSecond.size());
190 		// master is on initial commit, so it should not be returned
191 		assertEquals("refs/heads/foo", refsContainingSecond.get(0).getName());
192 	}
193 
194 	@Test
195 	public void testCreateFromCommit() throws Exception {
196 		Ref branch = git.branchCreate().setName("FromInitial").setStartPoint(
197 				initialCommit).call();
198 		assertEquals(initialCommit.getId(), branch.getObjectId());
199 		branch = git.branchCreate().setName("FromInitial2").setStartPoint(
200 				initialCommit.getId().name()).call();
201 		assertEquals(initialCommit.getId(), branch.getObjectId());
202 		try {
203 			git.branchCreate().setName("FromInitial").setStartPoint(
204 					secondCommit).call();
205 		} catch (RefAlreadyExistsException e) {
206 			// expected
207 		}
208 		branch = git.branchCreate().setName("FromInitial").setStartPoint(
209 				secondCommit).setForce(true).call();
210 		assertEquals(secondCommit.getId(), branch.getObjectId());
211 	}
212 
213 	@Test
214 	public void testCreateForce() throws Exception {
215 		// using commits
216 		Ref newBranch = createBranch(git, "NewForce", false, secondCommit
217 				.getId().name(), null);
218 		assertEquals(newBranch.getTarget().getObjectId(), secondCommit.getId());
219 		try {
220 			newBranch = createBranch(git, "NewForce", false, initialCommit
221 					.getId().name(), null);
222 			fail("Should have failed");
223 		} catch (RefAlreadyExistsException e) {
224 			// expected
225 		}
226 		newBranch = createBranch(git, "NewForce", true, initialCommit.getId()
227 				.name(), null);
228 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
229 		git.branchDelete().setBranchNames("NewForce").call();
230 		// using names
231 
232 		git.branchCreate().setName("NewForce").setStartPoint("master").call();
233 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
234 		try {
235 			git.branchCreate().setName("NewForce").setStartPoint("master")
236 					.call();
237 			fail("Should have failed");
238 		} catch (RefAlreadyExistsException e) {
239 			// expected
240 		}
241 		git.branchCreate().setName("NewForce").setStartPoint("master")
242 				.setForce(true).call();
243 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
244 	}
245 
246 	@Test
247 	public void testCreateFromLightweightTag() throws Exception {
248 		RefUpdate rup = db.updateRef("refs/tags/V10");
249 		rup.setNewObjectId(initialCommit);
250 		rup.setExpectedOldObjectId(ObjectId.zeroId());
251 		rup.update();
252 
253 		Ref branch = git.branchCreate().setName("FromLightweightTag")
254 				.setStartPoint("refs/tags/V10").call();
255 		assertEquals(initialCommit.getId(), branch.getObjectId());
256 
257 	}
258 
259 	@Test
260 	public void testCreateFromAnnotatetdTag() throws Exception {
261 		Ref tagRef = git.tag().setName("V10").setObjectId(secondCommit).call();
262 		Ref branch = git.branchCreate().setName("FromAnnotatedTag")
263 				.setStartPoint("refs/tags/V10").call();
264 		assertFalse(tagRef.getObjectId().equals(branch.getObjectId()));
265 		assertEquals(secondCommit.getId(), branch.getObjectId());
266 	}
267 
268 	@Test
269 	public void testDelete() throws Exception {
270 		createBranch(git, "ForDelete", false, "master", null);
271 		git.branchDelete().setBranchNames("ForDelete").call();
272 		// now point the branch to a non-merged commit
273 		createBranch(git, "ForDelete", false, secondCommit.getId().name(), null);
274 		try {
275 			git.branchDelete().setBranchNames("ForDelete").call();
276 			fail("Deletion of a non-merged branch without force should have failed");
277 		} catch (NotMergedException e) {
278 			// expected
279 		}
280 		List<String> deleted = git.branchDelete().setBranchNames("ForDelete")
281 				.setForce(true).call();
282 		assertEquals(1, deleted.size());
283 		assertEquals(Constants.R_HEADS + "ForDelete", deleted.get(0));
284 		createBranch(git, "ForDelete", false, "master", null);
285 		try {
286 			createBranch(git, "ForDelete", false, "master", null);
287 			fail("Repeated creation of same branch without force should fail");
288 		} catch (RefAlreadyExistsException e) {
289 			// expected
290 		}
291 		// change starting point
292 		Ref newBranch = createBranch(git, "ForDelete", true, initialCommit
293 				.name(), null);
294 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
295 		newBranch = createBranch(git, "ForDelete", true, secondCommit.name(),
296 				null);
297 		assertEquals(newBranch.getTarget().getObjectId(), secondCommit.getId());
298 		git.branchDelete().setBranchNames("ForDelete").setForce(true);
299 		try {
300 			git.branchDelete().setBranchNames("master").call();
301 			fail("Deletion of checked out branch without force should have failed");
302 		} catch (CannotDeleteCurrentBranchException e) {
303 			// expected
304 		}
305 		try {
306 			git.branchDelete().setBranchNames("master").setForce(true).call();
307 			fail("Deletion of checked out branch with force should have failed");
308 		} catch (CannotDeleteCurrentBranchException e) {
309 			// expected
310 		}
311 	}
312 
313 	@Test
314 	public void testPullConfigRemoteBranch() throws Exception {
315 		Git localGit = setUpRepoWithRemote();
316 		Ref remote = localGit.branchList().setListMode(ListMode.REMOTE).call()
317 				.get(0);
318 		assertEquals("refs/remotes/origin/master", remote.getName());
319 		// by default, we should create pull configuration
320 		createBranch(localGit, "newFromRemote", false, remote.getName(), null);
321 		assertEquals("origin", localGit.getRepository().getConfig().getString(
322 				"branch", "newFromRemote", "remote"));
323 		localGit.branchDelete().setBranchNames("newFromRemote").call();
324 		// the pull configuration should be gone after deletion
325 		assertNull(localGit.getRepository().getConfig().getString("branch",
326 				"newFromRemote", "remote"));
327 
328 		createBranch(localGit, "newFromRemote", false, remote.getName(), null);
329 		assertEquals("origin", localGit.getRepository().getConfig().getString(
330 				"branch", "newFromRemote", "remote"));
331 		localGit.branchDelete().setBranchNames("refs/heads/newFromRemote")
332 				.call();
333 		// the pull configuration should be gone after deletion
334 		assertNull(localGit.getRepository().getConfig().getString("branch",
335 				"newFromRemote", "remote"));
336 
337 		// use --no-track
338 		createBranch(localGit, "newFromRemote", false, remote.getName(),
339 				SetupUpstreamMode.NOTRACK);
340 		assertNull(localGit.getRepository().getConfig().getString("branch",
341 				"newFromRemote", "remote"));
342 		localGit.branchDelete().setBranchNames("newFromRemote").call();
343 	}
344 
345 	@Test
346 	public void testPullConfigLocalBranch() throws Exception {
347 		Git localGit = setUpRepoWithRemote();
348 		// by default, we should not create pull configuration
349 		createBranch(localGit, "newFromMaster", false, "master", null);
350 		assertNull(localGit.getRepository().getConfig().getString("branch",
351 				"newFromMaster", "remote"));
352 		localGit.branchDelete().setBranchNames("newFromMaster").call();
353 		// use --track
354 		createBranch(localGit, "newFromMaster", false, "master",
355 				SetupUpstreamMode.TRACK);
356 		assertEquals(".", localGit.getRepository().getConfig().getString(
357 				"branch", "newFromMaster", "remote"));
358 		localGit.branchDelete().setBranchNames("refs/heads/newFromMaster")
359 				.call();
360 		// the pull configuration should be gone after deletion
361 		assertNull(localGit.getRepository().getConfig().getString("branch",
362 				"newFromRemote", "remote"));
363 	}
364 
365 	@Test
366 	public void testPullConfigRenameLocalBranch() throws Exception {
367 		Git localGit = setUpRepoWithRemote();
368 		// by default, we should not create pull configuration
369 		createBranch(localGit, "newFromMaster", false, "master", null);
370 		assertNull(localGit.getRepository().getConfig().getString("branch",
371 				"newFromMaster", "remote"));
372 		localGit.branchDelete().setBranchNames("newFromMaster").call();
373 		// use --track
374 		createBranch(localGit, "newFromMaster", false, "master",
375 				SetupUpstreamMode.TRACK);
376 		assertEquals(".", localGit.getRepository().getConfig().getString(
377 				"branch", "newFromMaster", "remote"));
378 		localGit.branchRename().setOldName("newFromMaster").setNewName(
379 				"renamed").call();
380 		assertNull(".", localGit.getRepository().getConfig().getString(
381 				"branch", "newFromMaster", "remote"));
382 		assertEquals(".", localGit.getRepository().getConfig().getString(
383 				"branch", "renamed", "remote"));
384 		localGit.branchDelete().setBranchNames("renamed").call();
385 		// the pull configuration should be gone after deletion
386 		assertNull(localGit.getRepository().getConfig().getString("branch",
387 				"newFromRemote", "remote"));
388 	}
389 
390 	@Test
391 	public void testRenameLocalBranch() throws Exception {
392 		// null newName not allowed
393 		try {
394 			git.branchRename().call();
395 		} catch (InvalidRefNameException e) {
396 			// expected
397 		}
398 		// invalid newName not allowed
399 		try {
400 			git.branchRename().setNewName("In va lid").call();
401 		} catch (InvalidRefNameException e) {
402 			// expected
403 		}
404 		// not existing name not allowed
405 		try {
406 			git.branchRename().setOldName("notexistingbranch").setNewName(
407 					"newname").call();
408 		} catch (RefNotFoundException e) {
409 			// expected
410 		}
411 		// create some branch
412 		createBranch(git, "existing", false, "master", null);
413 		// a local branch
414 		Ref branch = createBranch(git, "fromMasterForRename", false, "master",
415 				null);
416 		assertEquals(Constants.R_HEADS + "fromMasterForRename", branch
417 				.getName());
418 		Ref renamed = git.branchRename().setOldName("fromMasterForRename")
419 				.setNewName("newName").call();
420 		assertEquals(Constants.R_HEADS + "newName", renamed.getName());
421 		try {
422 			git.branchRename().setOldName(renamed.getName()).setNewName(
423 					"existing").call();
424 			fail("Should have failed");
425 		} catch (RefAlreadyExistsException e) {
426 			// expected
427 		}
428 		try {
429 			git.branchRename().setNewName("In va lid").call();
430 			fail("Rename with invalid ref name should fail");
431 		} catch (InvalidRefNameException e) {
432 			// expected
433 		}
434 		// rename without old name and detached head not allowed
435 		RefUpdate rup = git.getRepository().updateRef(Constants.HEAD, true);
436 		rup.setNewObjectId(initialCommit);
437 		rup.forceUpdate();
438 		try {
439 			git.branchRename().setNewName("detached").call();
440 		} catch (DetachedHeadException e) {
441 			// expected
442 		}
443 	}
444 
445 	@Test
446 	public void testRenameRemoteTrackingBranch() throws Exception {
447 		Git localGit = setUpRepoWithRemote();
448 		Ref remoteBranch = localGit.branchList().setListMode(ListMode.REMOTE)
449 				.call().get(0);
450 		Ref renamed = localGit.branchRename()
451 				.setOldName(remoteBranch.getName()).setNewName("newRemote")
452 				.call();
453 		assertEquals(Constants.R_REMOTES + "newRemote", renamed.getName());
454 	}
455 
456 	@Test
457 	public void testCreationImplicitStart() throws Exception {
458 		git.branchCreate().setName("topic").call();
459 		assertEquals(db.resolve("HEAD"), db.resolve("topic"));
460 	}
461 
462 	@Test
463 	public void testCreationNullStartPoint() throws Exception {
464 		String startPoint = null;
465 		git.branchCreate().setName("topic").setStartPoint(startPoint).call();
466 		assertEquals(db.resolve("HEAD"), db.resolve("topic"));
467 	}
468 
469 	public Ref createBranch(Git actGit, String name, boolean force,
470 			String startPoint, SetupUpstreamMode mode)
471 			throws JGitInternalException, GitAPIException {
472 		CreateBranchCommand cmd = actGit.branchCreate();
473 		cmd.setName(name);
474 		cmd.setForce(force);
475 		cmd.setStartPoint(startPoint);
476 		cmd.setUpstreamMode(mode);
477 		return cmd.call();
478 	}
479 }