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