View Javadoc
1   /*
2    * Copyright (C) 2012, IBM Corporation
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.pgm;
44  
45  import static org.junit.Assert.assertArrayEquals;
46  import static org.junit.Assert.assertNotNull;
47  
48  import java.io.File;
49  import java.util.List;
50  
51  import org.eclipse.jgit.api.Git;
52  import org.eclipse.jgit.api.errors.CheckoutConflictException;
53  import org.eclipse.jgit.diff.DiffEntry;
54  import org.eclipse.jgit.lib.CLIRepositoryTestCase;
55  import org.eclipse.jgit.lib.FileMode;
56  import org.eclipse.jgit.lib.Ref;
57  import org.eclipse.jgit.revwalk.RevCommit;
58  import org.eclipse.jgit.treewalk.FileTreeIterator;
59  import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry;
60  import org.eclipse.jgit.treewalk.TreeWalk;
61  import org.eclipse.jgit.util.FileUtils;
62  import org.junit.Assert;
63  import org.junit.Test;
64  
65  public class CheckoutTest extends CLIRepositoryTestCase {
66  
67  	@Test
68  	public void testCheckoutSelf() throws Exception {
69  		new Git(db).commit().setMessage("initial commit").call();
70  
71  		assertEquals("Already on 'master'", execute("git checkout master"));
72  	}
73  
74  	@Test
75  	public void testCheckoutBranch() throws Exception {
76  		new Git(db).commit().setMessage("initial commit").call();
77  		new Git(db).branchCreate().setName("side").call();
78  
79  		assertEquals("Switched to branch 'side'", execute("git checkout side"));
80  	}
81  
82  	@Test
83  	public void testCheckoutNewBranch() throws Exception {
84  		new Git(db).commit().setMessage("initial commit").call();
85  
86  		assertEquals("Switched to a new branch 'side'",
87  				execute("git checkout -b side"));
88  	}
89  
90  	@Test
91  	public void testCheckoutNonExistingBranch() throws Exception {
92  		assertEquals(
93  				"error: pathspec 'side' did not match any file(s) known to git.",
94  				execute("git checkout side"));
95  	}
96  
97  	@Test
98  	public void testCheckoutNewBranchThatAlreadyExists() throws Exception {
99  		new Git(db).commit().setMessage("initial commit").call();
100 
101 		assertEquals("fatal: A branch named 'master' already exists.",
102 				execute("git checkout -b master"));
103 	}
104 
105 	@Test
106 	public void testCheckoutNewBranchOnBranchToBeBorn() throws Exception {
107 		assertEquals("fatal: You are on a branch yet to be born",
108 				execute("git checkout -b side"));
109 	}
110 
111 	@Test
112 	public void testCheckoutUnresolvedHead() throws Exception {
113 		assertEquals(
114 				"error: pathspec 'HEAD' did not match any file(s) known to git.",
115 				execute("git checkout HEAD"));
116 	}
117 
118 	@Test
119 	public void testCheckoutHead() throws Exception {
120 		new Git(db).commit().setMessage("initial commit").call();
121 
122 		assertEquals("", execute("git checkout HEAD"));
123 	}
124 
125 	@Test
126 	public void testCheckoutExistingBranchWithConflict() throws Exception {
127 		Git git = new Git(db);
128 		writeTrashFile("a", "Hello world a");
129 		git.add().addFilepattern(".").call();
130 		git.commit().setMessage("commit file a").call();
131 		git.branchCreate().setName("branch_1").call();
132 		git.rm().addFilepattern("a").call();
133 		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
134 		writeTrashFile("a/b", "Hello world b");
135 		git.add().addFilepattern("a/b").call();
136 		git.commit().setMessage("commit folder a").call();
137 		git.rm().addFilepattern("a").call();
138 		writeTrashFile("a", "New Hello world a");
139 		git.add().addFilepattern(".").call();
140 
141 		String[] execute = execute("git checkout branch_1");
142 		Assert.assertEquals(
143 				"error: Your local changes to the following files would be overwritten by checkout:",
144 				execute[0]);
145 		Assert.assertEquals("\ta", execute[1]);
146 	}
147 
148 	/**
149 	 * Steps:
150 	 * <ol>
151 	 * <li>Add file 'a' and 'b'
152 	 * <li>Commit
153 	 * <li>Create branch '1'
154 	 * <li>modify file 'a'
155 	 * <li>Commit
156 	 * <li>Delete file 'a' in the working tree
157 	 * <li>Checkout branch '1'
158 	 * </ol>
159 	 * <p>
160 	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
161 	 * checkout.
162 	 *
163 	 * @throws Exception
164 	 */
165 	@Test
166 	public void testCheckoutWithMissingWorkingTreeFile() throws Exception {
167 		Git git = new Git(db);
168 		File fileA = writeTrashFile("a", "Hello world a");
169 		writeTrashFile("b", "Hello world b");
170 		git.add().addFilepattern(".").call();
171 		git.commit().setMessage("add files a & b").call();
172 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
173 		writeTrashFile("a", "b");
174 		git.add().addFilepattern("a").call();
175 		git.commit().setMessage("modify file a").call();
176 
177 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
178 				db.getWorkTree(), "a"), db.getFS());
179 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
180 
181 		FileUtils.delete(fileA);
182 
183 		git.checkout().setName(branch_1.getName()).call();
184 
185 		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
186 				db.getFS());
187 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
188 		assertEquals("Hello world a", read(fileA));
189 	}
190 
191 	@Test
192 	public void testCheckoutOrphan() throws Exception {
193 		Git git = new Git(db);
194 		git.commit().setMessage("initial commit").call();
195 
196 		assertEquals("Switched to a new branch 'new_branch'",
197 				execute("git checkout --orphan new_branch"));
198 		assertEquals("refs/heads/new_branch", db.getRef("HEAD").getTarget().getName());
199 		RevCommit commit = git.commit().setMessage("orphan commit").call();
200 		assertEquals(0, commit.getParentCount());
201 	}
202 
203 	/**
204 	 * Steps:
205 	 * <ol>
206 	 * <li>Add file 'b'
207 	 * <li>Commit
208 	 * <li>Create branch '1'
209 	 * <li>Add folder 'a'
210 	 * <li>Commit
211 	 * <li>Replace folder 'a' by file 'a' in the working tree
212 	 * <li>Checkout branch '1'
213 	 * </ol>
214 	 * <p>
215 	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
216 	 * checkout.
217 	 *
218 	 * @throws Exception
219 	 */
220 	@Test
221 	public void fileModeTestMissingThenFolderWithFileInWorkingTree()
222 			throws Exception {
223 		Git git = new Git(db);
224 		writeTrashFile("b", "Hello world b");
225 		git.add().addFilepattern(".").call();
226 		git.commit().setMessage("add file b").call();
227 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
228 		File folderA = new File(db.getWorkTree(), "a");
229 		FileUtils.mkdirs(folderA);
230 		writeTrashFile("a/c", "Hello world c");
231 		git.add().addFilepattern(".").call();
232 		git.commit().setMessage("add folder a").call();
233 
234 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
235 				db.getWorkTree(), "a"), db.getFS());
236 		assertEquals(FileMode.TREE, entry.getMode());
237 
238 		FileUtils.delete(folderA, FileUtils.RECURSIVE);
239 		writeTrashFile("a", "b");
240 
241 		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
242 				db.getFS());
243 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
244 
245 		git.checkout().setName(branch_1.getName()).call();
246 
247 		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
248 				db.getFS());
249 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
250 	}
251 
252 	/**
253 	 * Steps:
254 	 * <ol>
255 	 * <li>Add file 'a'
256 	 * <li>Commit
257 	 * <li>Create branch '1'
258 	 * <li>Replace file 'a' by folder 'a'
259 	 * <li>Commit
260 	 * <li>Delete folder 'a' in the working tree
261 	 * <li>Checkout branch '1'
262 	 * </ol>
263 	 * <p>
264 	 * The working tree should contain 'a' with FileMode.REGULAR_FILE after the
265 	 * checkout.
266 	 *
267 	 * @throws Exception
268 	 */
269 	@Test
270 	public void fileModeTestFolderWithMissingInWorkingTree() throws Exception {
271 		Git git = new Git(db);
272 		writeTrashFile("b", "Hello world b");
273 		writeTrashFile("a", "b");
274 		git.add().addFilepattern(".").call();
275 		git.commit().setMessage("add file b & file a").call();
276 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
277 		git.rm().addFilepattern("a").call();
278 		File folderA = new File(db.getWorkTree(), "a");
279 		FileUtils.mkdirs(folderA);
280 		writeTrashFile("a/c", "Hello world c");
281 		git.add().addFilepattern(".").call();
282 		git.commit().setMessage("add folder a").call();
283 
284 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
285 				db.getWorkTree(), "a"), db.getFS());
286 		assertEquals(FileMode.TREE, entry.getMode());
287 
288 		FileUtils.delete(folderA, FileUtils.RECURSIVE);
289 
290 		git.checkout().setName(branch_1.getName()).call();
291 
292 		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
293 				db.getFS());
294 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
295 	}
296 
297 	/**
298 	 * Steps:
299 	 * <ol>
300 	 * <li>Add file 'a'
301 	 * <li>Commit
302 	 * <li>Create branch '1'
303 	 * <li>Delete file 'a'
304 	 * <li>Commit
305 	 * <li>Add folder 'a' in the working tree
306 	 * <li>Checkout branch '1'
307 	 * </ol>
308 	 * <p>
309 	 * The checkout command should raise an error. The conflicting paths are 'a'
310 	 * and 'a/c'.
311 	 *
312 	 * @throws Exception
313 	 */
314 	@Test
315 	public void fileModeTestMissingWithFolderInWorkingTree() throws Exception {
316 		Git git = new Git(db);
317 		writeTrashFile("b", "Hello world b");
318 		writeTrashFile("a", "b");
319 		git.add().addFilepattern(".").call();
320 		git.commit().setMessage("add file b & file a").call();
321 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
322 		git.rm().addFilepattern("a").call();
323 		git.commit().setMessage("delete file a").call();
324 
325 		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
326 		writeTrashFile("a/c", "Hello world c");
327 
328 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
329 				db.getWorkTree(), "a"), db.getFS());
330 		assertEquals(FileMode.TREE, entry.getMode());
331 
332 		CheckoutConflictException exception = null;
333 		try {
334 			git.checkout().setName(branch_1.getName()).call();
335 		} catch (CheckoutConflictException e) {
336 			exception = e;
337 		}
338 		assertNotNull(exception);
339 		assertEquals(2, exception.getConflictingPaths().size());
340 		assertEquals("a", exception.getConflictingPaths().get(0));
341 		assertEquals("a/c", exception.getConflictingPaths().get(1));
342 	}
343 
344 	/**
345 	 * Steps:
346 	 * <ol>
347 	 * <li>Add folder 'a'
348 	 * <li>Commit
349 	 * <li>Create branch '1'
350 	 * <li>Delete folder 'a'
351 	 * <li>Commit
352 	 * <li>Add file 'a' in the working tree
353 	 * <li>Checkout branch '1'
354 	 * </ol>
355 	 * <p>
356 	 * The checkout command should raise an error. The conflicting path is 'a'.
357 	 *
358 	 * @throws Exception
359 	 */
360 	@Test
361 	public void fileModeTestFolderThenMissingWithFileInWorkingTree()
362 			throws Exception {
363 		Git git = new Git(db);
364 		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
365 		writeTrashFile("a/c", "Hello world c");
366 		writeTrashFile("b", "Hello world b");
367 		git.add().addFilepattern(".").call();
368 		RevCommit commit1 = git.commit().setMessage("add folder a & file b")
369 				.call();
370 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
371 		git.rm().addFilepattern("a").call();
372 		RevCommit commit2 = git.commit().setMessage("delete folder a").call();
373 
374 		TreeWalk tw = new TreeWalk(db);
375 		tw.addTree(commit1.getTree());
376 		tw.addTree(commit2.getTree());
377 		List<DiffEntry> scan = DiffEntry.scan(tw);
378 		assertEquals(1, scan.size());
379 		assertEquals(FileMode.MISSING, scan.get(0).getNewMode());
380 		assertEquals(FileMode.TREE, scan.get(0).getOldMode());
381 
382 		writeTrashFile("a", "b");
383 
384 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
385 				db.getWorkTree(), "a"), db.getFS());
386 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
387 
388 		CheckoutConflictException exception = null;
389 		try {
390 			git.checkout().setName(branch_1.getName()).call();
391 		} catch (CheckoutConflictException e) {
392 			exception = e;
393 		}
394 		assertNotNull(exception);
395 		assertEquals(1, exception.getConflictingPaths().size());
396 		assertEquals("a", exception.getConflictingPaths().get(0));
397 	}
398 
399 	/**
400 	 * Steps:
401 	 * <ol>
402 	 * <li>Add folder 'a'
403 	 * <li>Commit
404 	 * <li>Create branch '1'
405 	 * <li>Replace folder 'a'by file 'a'
406 	 * <li>Commit
407 	 * <li>Delete file 'a' in the working tree
408 	 * <li>Checkout branch '1'
409 	 * </ol>
410 	 * <p>
411 	 * The working tree should contain 'a' with FileMode.TREE after the
412 	 * checkout.
413 	 *
414 	 * @throws Exception
415 	 */
416 	@Test
417 	public void fileModeTestFolderThenFileWithMissingInWorkingTree()
418 			throws Exception {
419 		Git git = new Git(db);
420 		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
421 		writeTrashFile("a/c", "Hello world c");
422 		writeTrashFile("b", "Hello world b");
423 		git.add().addFilepattern(".").call();
424 		git.commit().setMessage("add folder a & file b").call();
425 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
426 		git.rm().addFilepattern("a").call();
427 		File fileA = new File(db.getWorkTree(), "a");
428 		writeTrashFile("a", "b");
429 		git.add().addFilepattern("a").call();
430 		git.commit().setMessage("add file a").call();
431 
432 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
433 				db.getWorkTree(), "a"), db.getFS());
434 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
435 
436 		FileUtils.delete(fileA);
437 
438 		git.checkout().setName(branch_1.getName()).call();
439 
440 		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
441 				db.getFS());
442 		assertEquals(FileMode.TREE, entry.getMode());
443 	}
444 
445 	/**
446 	 * Steps:
447 	 * <ol>
448 	 * <li>Add file 'a'
449 	 * <li>Commit
450 	 * <li>Create branch '1'
451 	 * <li>Modify file 'a'
452 	 * <li>Commit
453 	 * <li>Delete file 'a' & replace by folder 'a' in the working tree & index
454 	 * <li>Checkout branch '1'
455 	 * </ol>
456 	 * <p>
457 	 * The checkout command should raise an error. The conflicting path is 'a'.
458 	 *
459 	 * @throws Exception
460 	 */
461 	@Test
462 	public void fileModeTestFileThenFileWithFolderInIndex() throws Exception {
463 		Git git = new Git(db);
464 		writeTrashFile("a", "Hello world a");
465 		writeTrashFile("b", "Hello world b");
466 		git.add().addFilepattern(".").call();
467 		git.commit().setMessage("add files a & b").call();
468 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
469 		writeTrashFile("a", "b");
470 		git.add().addFilepattern("a").call();
471 		git.commit().setMessage("add file a").call();
472 
473 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
474 				db.getWorkTree(), "a"), db.getFS());
475 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
476 
477 		git.rm().addFilepattern("a").call();
478 		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
479 		writeTrashFile("a/c", "Hello world c");
480 		git.add().addFilepattern(".").call();
481 
482 		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
483 				db.getFS());
484 		assertEquals(FileMode.TREE, entry.getMode());
485 
486 		CheckoutConflictException exception = null;
487 		try {
488 			git.checkout().setName(branch_1.getName()).call();
489 		} catch (CheckoutConflictException e) {
490 			exception = e;
491 		}
492 		assertNotNull(exception);
493 		assertEquals(1, exception.getConflictingPaths().size());
494 		assertEquals("a", exception.getConflictingPaths().get(0));
495 	}
496 
497 	/**
498 	 * Steps:
499 	 * <ol>
500 	 * <li>Add file 'a'
501 	 * <li>Commit
502 	 * <li>Create branch '1'
503 	 * <li>Modify file 'a'
504 	 * <li>Commit
505 	 * <li>Delete file 'a' & replace by folder 'a' in the working tree & index
506 	 * <li>Checkout branch '1'
507 	 * </ol>
508 	 * <p>
509 	 * The checkout command should raise an error. The conflicting paths are 'a'
510 	 * and 'a/c'.
511 	 *
512 	 * @throws Exception
513 	 */
514 	@Test
515 	public void fileModeTestFileWithFolderInIndex() throws Exception {
516 		Git git = new Git(db);
517 		writeTrashFile("b", "Hello world b");
518 		writeTrashFile("a", "b");
519 		git.add().addFilepattern(".").call();
520 		git.commit().setMessage("add file b & file a").call();
521 		Ref branch_1 = git.branchCreate().setName("branch_1").call();
522 		git.rm().addFilepattern("a").call();
523 		writeTrashFile("a", "Hello world a");
524 		git.add().addFilepattern("a").call();
525 		git.commit().setMessage("add file a").call();
526 
527 		FileEntry entry = new FileTreeIterator.FileEntry(new File(
528 				db.getWorkTree(), "a"), db.getFS());
529 		assertEquals(FileMode.REGULAR_FILE, entry.getMode());
530 
531 		git.rm().addFilepattern("a").call();
532 		FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
533 		writeTrashFile("a/c", "Hello world c");
534 		git.add().addFilepattern(".").call();
535 
536 		entry = new FileTreeIterator.FileEntry(new File(db.getWorkTree(), "a"),
537 				db.getFS());
538 		assertEquals(FileMode.TREE, entry.getMode());
539 
540 		CheckoutConflictException exception = null;
541 		try {
542 			git.checkout().setName(branch_1.getName()).call();
543 		} catch (CheckoutConflictException e) {
544 			exception = e;
545 		}
546 		assertNotNull(exception);
547 		assertEquals(1, exception.getConflictingPaths().size());
548 		assertEquals("a", exception.getConflictingPaths().get(0));
549 
550 		// TODO: ideally we'd like to get two paths from this exception
551 		// assertEquals(2, exception.getConflictingPaths().size());
552 		// assertEquals("a", exception.getConflictingPaths().get(0));
553 		// assertEquals("a/c", exception.getConflictingPaths().get(1));
554 	}
555 
556 	static private void assertEquals(Object expected, Object actual) {
557 		Assert.assertEquals(expected, actual);
558 	}
559 
560 	static private void assertEquals(String expected, String[] actual) {
561 		// if there is more than one line, ignore last one if empty
562 		Assert.assertEquals(
563 				1,
564 				actual.length > 1 && actual[actual.length - 1].equals("") ? actual.length - 1
565 						: actual.length);
566 		Assert.assertEquals(expected, actual[0]);
567 	}
568 
569 	@Test
570 	public void testCheckoutPath() throws Exception {
571 		Git git = new Git(db);
572 		writeTrashFile("a", "Hello world a");
573 		git.add().addFilepattern(".").call();
574 		git.commit().setMessage("commit file a").call();
575 		git.branchCreate().setName("branch_1").call();
576 		git.checkout().setName("branch_1").call();
577 		File b = writeTrashFile("b", "Hello world b");
578 		git.add().addFilepattern("b").call();
579 		git.commit().setMessage("commit file b").call();
580 		File a = writeTrashFile("a", "New Hello world a");
581 		git.add().addFilepattern(".").call();
582 		git.commit().setMessage("modified a").call();
583 		assertArrayEquals(new String[] { "" },
584 				execute("git checkout HEAD~2 -- a"));
585 		assertEquals("Hello world a", read(a));
586 		assertArrayEquals(new String[] { "* branch_1", "  master", "" },
587 				execute("git branch"));
588 		assertEquals("Hello world b", read(b));
589 	}
590 }