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
195 	public void testListAllBranchesShouldNotDie() throws Exception {
196 		setUpRepoWithRemote().branchList().setListMode(ListMode.ALL).call();
197 	}
198 
199 	@Test
200 	public void testListBranchesWithContains() throws Exception {
201 		git.branchCreate().setName("foo").setStartPoint(secondCommit).call();
202 
203 		List<Ref> refs = git.branchList().call();
204 		assertEquals(2, refs.size());
205 
206 		List<Ref> refsContainingSecond = git.branchList()
207 				.setContains(secondCommit.name()).call();
208 		assertEquals(1, refsContainingSecond.size());
209 		// master is on initial commit, so it should not be returned
210 		assertEquals("refs/heads/foo", refsContainingSecond.get(0).getName());
211 	}
212 
213 	@Test
214 	public void testCreateFromCommit() throws Exception {
215 		Ref branch = git.branchCreate().setName("FromInitial").setStartPoint(
216 				initialCommit).call();
217 		assertEquals(initialCommit.getId(), branch.getObjectId());
218 		branch = git.branchCreate().setName("FromInitial2").setStartPoint(
219 				initialCommit.getId().name()).call();
220 		assertEquals(initialCommit.getId(), branch.getObjectId());
221 		try {
222 			git.branchCreate().setName("FromInitial").setStartPoint(
223 					secondCommit).call();
224 		} catch (RefAlreadyExistsException e) {
225 			// expected
226 		}
227 		branch = git.branchCreate().setName("FromInitial").setStartPoint(
228 				secondCommit).setForce(true).call();
229 		assertEquals(secondCommit.getId(), branch.getObjectId());
230 	}
231 
232 	@Test
233 	public void testCreateForce() throws Exception {
234 		// using commits
235 		Ref newBranch = createBranch(git, "NewForce", false, secondCommit
236 				.getId().name(), null);
237 		assertEquals(newBranch.getTarget().getObjectId(), secondCommit.getId());
238 		try {
239 			newBranch = createBranch(git, "NewForce", false, initialCommit
240 					.getId().name(), null);
241 			fail("Should have failed");
242 		} catch (RefAlreadyExistsException e) {
243 			// expected
244 		}
245 		newBranch = createBranch(git, "NewForce", true, initialCommit.getId()
246 				.name(), null);
247 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
248 		git.branchDelete().setBranchNames("NewForce").call();
249 		// using names
250 
251 		git.branchCreate().setName("NewForce").setStartPoint("master").call();
252 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
253 		try {
254 			git.branchCreate().setName("NewForce").setStartPoint("master")
255 					.call();
256 			fail("Should have failed");
257 		} catch (RefAlreadyExistsException e) {
258 			// expected
259 		}
260 		git.branchCreate().setName("NewForce").setStartPoint("master")
261 				.setForce(true).call();
262 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
263 	}
264 
265 	@Test
266 	public void testCreateFromLightweightTag() throws Exception {
267 		RefUpdate rup = db.updateRef("refs/tags/V10");
268 		rup.setNewObjectId(initialCommit);
269 		rup.setExpectedOldObjectId(ObjectId.zeroId());
270 		rup.update();
271 
272 		Ref branch = git.branchCreate().setName("FromLightweightTag")
273 				.setStartPoint("refs/tags/V10").call();
274 		assertEquals(initialCommit.getId(), branch.getObjectId());
275 
276 	}
277 
278 	@Test
279 	public void testCreateFromAnnotatetdTag() throws Exception {
280 		Ref tagRef = git.tag().setName("V10").setObjectId(secondCommit).call();
281 		Ref branch = git.branchCreate().setName("FromAnnotatedTag")
282 				.setStartPoint("refs/tags/V10").call();
283 		assertFalse(tagRef.getObjectId().equals(branch.getObjectId()));
284 		assertEquals(secondCommit.getId(), branch.getObjectId());
285 	}
286 
287 	@Test
288 	public void testDelete() throws Exception {
289 		createBranch(git, "ForDelete", false, "master", null);
290 		git.branchDelete().setBranchNames("ForDelete").call();
291 		// now point the branch to a non-merged commit
292 		createBranch(git, "ForDelete", false, secondCommit.getId().name(), null);
293 		try {
294 			git.branchDelete().setBranchNames("ForDelete").call();
295 			fail("Deletion of a non-merged branch without force should have failed");
296 		} catch (NotMergedException e) {
297 			// expected
298 		}
299 		List<String> deleted = git.branchDelete().setBranchNames("ForDelete")
300 				.setForce(true).call();
301 		assertEquals(1, deleted.size());
302 		assertEquals(Constants.R_HEADS + "ForDelete", deleted.get(0));
303 		createBranch(git, "ForDelete", false, "master", null);
304 		try {
305 			createBranch(git, "ForDelete", false, "master", null);
306 			fail("Repeated creation of same branch without force should fail");
307 		} catch (RefAlreadyExistsException e) {
308 			// expected
309 		}
310 		// change starting point
311 		Ref newBranch = createBranch(git, "ForDelete", true, initialCommit
312 				.name(), null);
313 		assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId());
314 		newBranch = createBranch(git, "ForDelete", true, secondCommit.name(),
315 				null);
316 		assertEquals(newBranch.getTarget().getObjectId(), secondCommit.getId());
317 		git.branchDelete().setBranchNames("ForDelete").setForce(true);
318 		try {
319 			git.branchDelete().setBranchNames("master").call();
320 			fail("Deletion of checked out branch without force should have failed");
321 		} catch (CannotDeleteCurrentBranchException e) {
322 			// expected
323 		}
324 		try {
325 			git.branchDelete().setBranchNames("master").setForce(true).call();
326 			fail("Deletion of checked out branch with force should have failed");
327 		} catch (CannotDeleteCurrentBranchException e) {
328 			// expected
329 		}
330 	}
331 
332 	@Test
333 	public void testPullConfigRemoteBranch() throws Exception {
334 		Git localGit = setUpRepoWithRemote();
335 		Ref remote = localGit.branchList().setListMode(ListMode.REMOTE).call()
336 				.get(0);
337 		assertEquals("refs/remotes/origin/master", remote.getName());
338 		// by default, we should create pull configuration
339 		createBranch(localGit, "newFromRemote", false, remote.getName(), null);
340 		assertEquals("origin", localGit.getRepository().getConfig().getString(
341 				"branch", "newFromRemote", "remote"));
342 		localGit.branchDelete().setBranchNames("newFromRemote").call();
343 		// the pull configuration should be gone after deletion
344 		assertNull(localGit.getRepository().getConfig().getString("branch",
345 				"newFromRemote", "remote"));
346 
347 		createBranch(localGit, "newFromRemote", false, remote.getName(), null);
348 		assertEquals("origin", localGit.getRepository().getConfig().getString(
349 				"branch", "newFromRemote", "remote"));
350 		localGit.branchDelete().setBranchNames("refs/heads/newFromRemote")
351 				.call();
352 		// the pull configuration should be gone after deletion
353 		assertNull(localGit.getRepository().getConfig().getString("branch",
354 				"newFromRemote", "remote"));
355 
356 		// use --no-track
357 		createBranch(localGit, "newFromRemote", false, remote.getName(),
358 				SetupUpstreamMode.NOTRACK);
359 		assertNull(localGit.getRepository().getConfig().getString("branch",
360 				"newFromRemote", "remote"));
361 		localGit.branchDelete().setBranchNames("newFromRemote").call();
362 	}
363 
364 	@Test
365 	public void testPullConfigLocalBranch() throws Exception {
366 		Git localGit = setUpRepoWithRemote();
367 		// by default, we should not create pull configuration
368 		createBranch(localGit, "newFromMaster", false, "master", null);
369 		assertNull(localGit.getRepository().getConfig().getString("branch",
370 				"newFromMaster", "remote"));
371 		localGit.branchDelete().setBranchNames("newFromMaster").call();
372 		// use --track
373 		createBranch(localGit, "newFromMaster", false, "master",
374 				SetupUpstreamMode.TRACK);
375 		assertEquals(".", localGit.getRepository().getConfig().getString(
376 				"branch", "newFromMaster", "remote"));
377 		localGit.branchDelete().setBranchNames("refs/heads/newFromMaster")
378 				.call();
379 		// the pull configuration should be gone after deletion
380 		assertNull(localGit.getRepository().getConfig().getString("branch",
381 				"newFromRemote", "remote"));
382 	}
383 
384 	@Test
385 	public void testPullConfigRenameLocalBranch() throws Exception {
386 		Git localGit = setUpRepoWithRemote();
387 		// by default, we should not create pull configuration
388 		createBranch(localGit, "newFromMaster", false, "master", null);
389 		assertNull(localGit.getRepository().getConfig().getString("branch",
390 				"newFromMaster", "remote"));
391 		localGit.branchDelete().setBranchNames("newFromMaster").call();
392 		// use --track
393 		createBranch(localGit, "newFromMaster", false, "master",
394 				SetupUpstreamMode.TRACK);
395 		assertEquals(".", localGit.getRepository().getConfig().getString(
396 				"branch", "newFromMaster", "remote"));
397 		localGit.branchRename().setOldName("newFromMaster").setNewName(
398 				"renamed").call();
399 		assertNull(".", localGit.getRepository().getConfig().getString(
400 				"branch", "newFromMaster", "remote"));
401 		assertEquals(".", localGit.getRepository().getConfig().getString(
402 				"branch", "renamed", "remote"));
403 		localGit.branchDelete().setBranchNames("renamed").call();
404 		// the pull configuration should be gone after deletion
405 		assertNull(localGit.getRepository().getConfig().getString("branch",
406 				"newFromRemote", "remote"));
407 	}
408 
409 	@Test
410 	public void testRenameLocalBranch() throws Exception {
411 		// null newName not allowed
412 		try {
413 			git.branchRename().call();
414 		} catch (InvalidRefNameException e) {
415 			// expected
416 		}
417 		// invalid newName not allowed
418 		try {
419 			git.branchRename().setNewName("In va lid").call();
420 		} catch (InvalidRefNameException e) {
421 			// expected
422 		}
423 		// not existing name not allowed
424 		try {
425 			git.branchRename().setOldName("notexistingbranch").setNewName(
426 					"newname").call();
427 		} catch (RefNotFoundException e) {
428 			// expected
429 		}
430 		// create some branch
431 		createBranch(git, "existing", false, "master", null);
432 		// a local branch
433 		Ref branch = createBranch(git, "fromMasterForRename", false, "master",
434 				null);
435 		assertEquals(Constants.R_HEADS + "fromMasterForRename", branch
436 				.getName());
437 		Ref renamed = git.branchRename().setOldName("fromMasterForRename")
438 				.setNewName("newName").call();
439 		assertEquals(Constants.R_HEADS + "newName", renamed.getName());
440 		try {
441 			git.branchRename().setOldName(renamed.getName()).setNewName(
442 					"existing").call();
443 			fail("Should have failed");
444 		} catch (RefAlreadyExistsException e) {
445 			// expected
446 		}
447 		try {
448 			git.branchRename().setNewName("In va lid").call();
449 			fail("Rename with invalid ref name should fail");
450 		} catch (InvalidRefNameException e) {
451 			// expected
452 		}
453 		// rename without old name and detached head not allowed
454 		RefUpdate rup = git.getRepository().updateRef(Constants.HEAD, true);
455 		rup.setNewObjectId(initialCommit);
456 		rup.forceUpdate();
457 		try {
458 			git.branchRename().setNewName("detached").call();
459 		} catch (DetachedHeadException e) {
460 			// expected
461 		}
462 	}
463 
464 	@Test
465 	public void testRenameRemoteTrackingBranch() throws Exception {
466 		Git localGit = setUpRepoWithRemote();
467 		Ref remoteBranch = localGit.branchList().setListMode(ListMode.REMOTE)
468 				.call().get(0);
469 		Ref renamed = localGit.branchRename()
470 				.setOldName(remoteBranch.getName()).setNewName("newRemote")
471 				.call();
472 		assertEquals(Constants.R_REMOTES + "newRemote", renamed.getName());
473 	}
474 
475 	@Test
476 	public void testCreationImplicitStart() throws Exception {
477 		git.branchCreate().setName("topic").call();
478 		assertEquals(db.resolve("HEAD"), db.resolve("topic"));
479 	}
480 
481 	@Test
482 	public void testCreationNullStartPoint() throws Exception {
483 		String startPoint = null;
484 		git.branchCreate().setName("topic").setStartPoint(startPoint).call();
485 		assertEquals(db.resolve("HEAD"), db.resolve("topic"));
486 	}
487 
488 	public Ref createBranch(Git actGit, String name, boolean force,
489 			String startPoint, SetupUpstreamMode mode)
490 			throws JGitInternalException, GitAPIException {
491 		CreateBranchCommand cmd = actGit.branchCreate();
492 		cmd.setName(name);
493 		cmd.setForce(force);
494 		cmd.setStartPoint(startPoint);
495 		cmd.setUpstreamMode(mode);
496 		return cmd.call();
497 	}
498 }