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