View Javadoc
1   /*
2    * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
3    * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  package org.eclipse.jgit.api;
45  
46  import static org.hamcrest.CoreMatchers.is;
47  import static org.hamcrest.MatcherAssert.assertThat;
48  import static org.junit.Assert.assertEquals;
49  import static org.junit.Assert.assertFalse;
50  import static org.junit.Assert.assertNotNull;
51  import static org.junit.Assert.assertNull;
52  import static org.junit.Assert.assertSame;
53  import static org.junit.Assert.assertTrue;
54  import static org.junit.Assert.fail;
55  
56  import java.io.File;
57  import java.io.FileInputStream;
58  import java.io.IOException;
59  import java.net.MalformedURLException;
60  import java.net.URISyntaxException;
61  
62  import org.eclipse.jgit.api.CheckoutResult.Status;
63  import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
64  import org.eclipse.jgit.api.errors.GitAPIException;
65  import org.eclipse.jgit.api.errors.InvalidRefNameException;
66  import org.eclipse.jgit.api.errors.InvalidRemoteException;
67  import org.eclipse.jgit.api.errors.JGitInternalException;
68  import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
69  import org.eclipse.jgit.api.errors.RefNotFoundException;
70  import org.eclipse.jgit.api.errors.TransportException;
71  import org.eclipse.jgit.dircache.DirCache;
72  import org.eclipse.jgit.dircache.DirCacheEntry;
73  import org.eclipse.jgit.junit.RepositoryTestCase;
74  import org.eclipse.jgit.lib.ConfigConstants;
75  import org.eclipse.jgit.lib.Constants;
76  import org.eclipse.jgit.lib.Ref;
77  import org.eclipse.jgit.lib.RefUpdate;
78  import org.eclipse.jgit.lib.Repository;
79  import org.eclipse.jgit.lib.StoredConfig;
80  import org.eclipse.jgit.revwalk.RevCommit;
81  import org.eclipse.jgit.storage.file.FileBasedConfig;
82  import org.eclipse.jgit.transport.RefSpec;
83  import org.eclipse.jgit.transport.RemoteConfig;
84  import org.eclipse.jgit.transport.URIish;
85  import org.eclipse.jgit.util.FileUtils;
86  import org.junit.Before;
87  import org.junit.Test;
88  
89  public class CheckoutCommandTest extends RepositoryTestCase {
90  	private Git git;
91  
92  	RevCommit initialCommit;
93  
94  	RevCommit secondCommit;
95  
96  	@Override
97  	@Before
98  	public void setUp() throws Exception {
99  		super.setUp();
100 		git = new Git(db);
101 		// commit something
102 		writeTrashFile("Test.txt", "Hello world");
103 		git.add().addFilepattern("Test.txt").call();
104 		initialCommit = git.commit().setMessage("Initial commit").call();
105 
106 		// create a master branch and switch to it
107 		git.branchCreate().setName("test").call();
108 		RefUpdate rup = db.updateRef(Constants.HEAD);
109 		rup.link("refs/heads/test");
110 
111 		// commit something on the test branch
112 		writeTrashFile("Test.txt", "Some change");
113 		git.add().addFilepattern("Test.txt").call();
114 		secondCommit = git.commit().setMessage("Second commit").call();
115 	}
116 
117 	@Test
118 	public void testSimpleCheckout() throws Exception {
119 		git.checkout().setName("test").call();
120 	}
121 
122 	@Test
123 	public void testCheckout() throws Exception {
124 		git.checkout().setName("test").call();
125 		assertEquals("[Test.txt, mode:100644, content:Some change]",
126 				indexState(CONTENT));
127 		Ref result = git.checkout().setName("master").call();
128 		assertEquals("[Test.txt, mode:100644, content:Hello world]",
129 				indexState(CONTENT));
130 		assertEquals("refs/heads/master", result.getName());
131 		assertEquals("refs/heads/master", git.getRepository().getFullBranch());
132 	}
133 
134 	@Test
135 	public void testCreateBranchOnCheckout() throws Exception {
136 		git.checkout().setCreateBranch(true).setName("test2").call();
137 		assertNotNull(db.getRef("test2"));
138 	}
139 
140 	@Test
141 	public void testCheckoutToNonExistingBranch() throws GitAPIException {
142 		try {
143 			git.checkout().setName("badbranch").call();
144 			fail("Should have failed");
145 		} catch (RefNotFoundException e) {
146 			// except to hit here
147 		}
148 	}
149 
150 	@Test
151 	public void testCheckoutWithConflict() {
152 		CheckoutCommand co = git.checkout();
153 		try {
154 			writeTrashFile("Test.txt", "Another change");
155 			assertEquals(Status.NOT_TRIED, co.getResult().getStatus());
156 			co.setName("master").call();
157 			fail("Should have failed");
158 		} catch (Exception e) {
159 			assertEquals(Status.CONFLICTS, co.getResult().getStatus());
160 			assertTrue(co.getResult().getConflictList().contains("Test.txt"));
161 		}
162 	}
163 
164 	@Test
165 	public void testCheckoutWithNonDeletedFiles() throws Exception {
166 		File testFile = writeTrashFile("temp", "");
167 		FileInputStream fis = new FileInputStream(testFile);
168 		try {
169 			FileUtils.delete(testFile);
170 			return;
171 		} catch (IOException e) {
172 			// the test makes only sense if deletion of
173 			// a file with open stream fails
174 		} finally {
175 			fis.close();
176 		}
177 		FileUtils.delete(testFile);
178 		CheckoutCommand co = git.checkout();
179 		// delete Test.txt in branch test
180 		testFile = new File(db.getWorkTree(), "Test.txt");
181 		assertTrue(testFile.exists());
182 		FileUtils.delete(testFile);
183 		assertFalse(testFile.exists());
184 		git.add().addFilepattern("Test.txt");
185 		git.commit().setMessage("Delete Test.txt").setAll(true).call();
186 		git.checkout().setName("master").call();
187 		assertTrue(testFile.exists());
188 		// lock the file so it can't be deleted (in Windows, that is)
189 		fis = new FileInputStream(testFile);
190 		try {
191 			assertEquals(Status.NOT_TRIED, co.getResult().getStatus());
192 			co.setName("test").call();
193 			assertTrue(testFile.exists());
194 			assertEquals(Status.NONDELETED, co.getResult().getStatus());
195 			assertTrue(co.getResult().getUndeletedList().contains("Test.txt"));
196 		} finally {
197 			fis.close();
198 		}
199 	}
200 
201 	@Test
202 	public void testCheckoutCommit() throws Exception {
203 		Ref result = git.checkout().setName(initialCommit.name()).call();
204 		assertEquals("[Test.txt, mode:100644, content:Hello world]",
205 				indexState(CONTENT));
206 		assertNull(result);
207 		assertEquals(initialCommit.name(), git.getRepository().getFullBranch());
208 	}
209 
210 	@Test
211 	public void testCheckoutLightweightTag() throws Exception {
212 		git.tag().setAnnotated(false).setName("test-tag")
213 				.setObjectId(initialCommit).call();
214 		Ref result = git.checkout().setName("test-tag").call();
215 
216 		assertNull(result);
217 		assertEquals(initialCommit.getId(), db.resolve(Constants.HEAD));
218 		assertHeadDetached();
219 	}
220 
221 	@Test
222 	public void testCheckoutAnnotatedTag() throws Exception {
223 		git.tag().setAnnotated(true).setName("test-tag")
224 				.setObjectId(initialCommit).call();
225 		Ref result = git.checkout().setName("test-tag").call();
226 
227 		assertNull(result);
228 		assertEquals(initialCommit.getId(), db.resolve(Constants.HEAD));
229 		assertHeadDetached();
230 	}
231 
232 	@Test
233 	public void testCheckoutRemoteTrackingWithUpstream() throws Exception {
234 		Repository db2 = createRepositoryWithRemote();
235 
236 		Git.wrap(db2).checkout().setCreateBranch(true).setName("test")
237 				.setStartPoint("origin/test")
238 				.setUpstreamMode(SetupUpstreamMode.TRACK).call();
239 
240 		assertEquals("refs/heads/test", db2.getRef(Constants.HEAD).getTarget()
241 				.getName());
242 		StoredConfig config = db2.getConfig();
243 		assertEquals("origin", config.getString(
244 				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
245 				ConfigConstants.CONFIG_KEY_REMOTE));
246 		assertEquals("refs/heads/test", config.getString(
247 				ConfigConstants.CONFIG_BRANCH_SECTION, "test",
248 				ConfigConstants.CONFIG_KEY_MERGE));
249 	}
250 
251 	@Test
252 	public void testCheckoutRemoteTrackingWithoutLocalBranch() throws Exception {
253 		Repository db2 = createRepositoryWithRemote();
254 
255 		// checkout remote tracking branch in second repository
256 		// (no local branches exist yet in second repository)
257 		Git.wrap(db2).checkout().setName("remotes/origin/test").call();
258 		assertEquals("[Test.txt, mode:100644, content:Some change]",
259 				indexState(db2, CONTENT));
260 	}
261 
262 
263 
264 	@Test
265 	public void testCheckoutOfFileWithInexistentParentDir() throws Exception {
266 		File a = writeTrashFile("dir/a.txt", "A");
267 		writeTrashFile("dir/b.txt", "A");
268 		git.add().addFilepattern("dir/a.txt").addFilepattern("dir/b.txt")
269 				.call();
270 		git.commit().setMessage("Added dir").call();
271 
272 		File dir = new File(db.getWorkTree(), "dir");
273 		FileUtils.delete(dir, FileUtils.RECURSIVE);
274 
275 		git.checkout().addPath("dir/a.txt").call();
276 		assertTrue(a.exists());
277 	}
278 
279 	@Test
280 	public void testCheckoutOfDirectoryShouldBeRecursive() throws Exception {
281 		File a = writeTrashFile("dir/a.txt", "A");
282 		File b = writeTrashFile("dir/sub/b.txt", "B");
283 		git.add().addFilepattern("dir").call();
284 		git.commit().setMessage("Added dir").call();
285 
286 		write(a, "modified");
287 		write(b, "modified");
288 		git.checkout().addPath("dir").call();
289 
290 		assertThat(read(a), is("A"));
291 		assertThat(read(b), is("B"));
292 	}
293 
294 	@Test
295 	public void testCheckoutAllPaths() throws Exception {
296 		File a = writeTrashFile("dir/a.txt", "A");
297 		File b = writeTrashFile("dir/sub/b.txt", "B");
298 		git.add().addFilepattern("dir").call();
299 		git.commit().setMessage("Added dir").call();
300 
301 		write(a, "modified");
302 		write(b, "modified");
303 		git.checkout().setAllPaths(true).call();
304 
305 		assertThat(read(a), is("A"));
306 		assertThat(read(b), is("B"));
307 	}
308 
309 	@Test
310 	public void testCheckoutWithStartPoint() throws Exception {
311 		File a = writeTrashFile("a.txt", "A");
312 		git.add().addFilepattern("a.txt").call();
313 		RevCommit first = git.commit().setMessage("Added a").call();
314 
315 		write(a, "other");
316 		git.commit().setAll(true).setMessage("Other").call();
317 
318 		git.checkout().setCreateBranch(true).setName("a")
319 				.setStartPoint(first.getId().getName()).call();
320 
321 		assertThat(read(a), is("A"));
322 	}
323 
324 	@Test
325 	public void testCheckoutWithStartPointOnlyCertainFiles() throws Exception {
326 		File a = writeTrashFile("a.txt", "A");
327 		File b = writeTrashFile("b.txt", "B");
328 		git.add().addFilepattern("a.txt").addFilepattern("b.txt").call();
329 		RevCommit first = git.commit().setMessage("First").call();
330 
331 		write(a, "other");
332 		write(b, "other");
333 		git.commit().setAll(true).setMessage("Other").call();
334 
335 		git.checkout().setCreateBranch(true).setName("a")
336 				.setStartPoint(first.getId().getName()).addPath("a.txt").call();
337 
338 		assertThat(read(a), is("A"));
339 		assertThat(read(b), is("other"));
340 	}
341 
342 	@Test
343 	public void testDetachedHeadOnCheckout() throws JGitInternalException,
344 			IOException, GitAPIException {
345 		CheckoutCommand co = git.checkout();
346 		co.setName("master").call();
347 
348 		String commitId = db.getRef(Constants.MASTER).getObjectId().name();
349 		co = git.checkout();
350 		co.setName(commitId).call();
351 
352 		assertHeadDetached();
353 	}
354 
355 	@Test
356 	public void testUpdateSmudgedEntries() throws Exception {
357 		git.branchCreate().setName("test2").call();
358 		RefUpdate rup = db.updateRef(Constants.HEAD);
359 		rup.link("refs/heads/test2");
360 
361 		File file = new File(db.getWorkTree(), "Test.txt");
362 		long size = file.length();
363 		long mTime = file.lastModified() - 5000L;
364 		assertTrue(file.setLastModified(mTime));
365 
366 		DirCache cache = DirCache.lock(db.getIndexFile(), db.getFS());
367 		DirCacheEntry entry = cache.getEntry("Test.txt");
368 		assertNotNull(entry);
369 		entry.setLength(0);
370 		entry.setLastModified(0);
371 		cache.write();
372 		assertTrue(cache.commit());
373 
374 		cache = DirCache.read(db.getIndexFile(), db.getFS());
375 		entry = cache.getEntry("Test.txt");
376 		assertNotNull(entry);
377 		assertEquals(0, entry.getLength());
378 		assertEquals(0, entry.getLastModified());
379 
380 		db.getIndexFile().setLastModified(
381 				db.getIndexFile().lastModified() - 5000);
382 
383 		assertNotNull(git.checkout().setName("test").call());
384 
385 		cache = DirCache.read(db.getIndexFile(), db.getFS());
386 		entry = cache.getEntry("Test.txt");
387 		assertNotNull(entry);
388 		assertEquals(size, entry.getLength());
389 		assertEquals(mTime, entry.getLastModified());
390 	}
391 
392 	@Test
393 	public void testCheckoutOrphanBranch() throws Exception {
394 		CheckoutCommand co = newOrphanBranchCommand();
395 		assertCheckoutRef(co.call());
396 
397 		File HEAD = new File(trash, ".git/HEAD");
398 		String headRef = read(HEAD);
399 		assertEquals("ref: refs/heads/orphanbranch\n", headRef);
400 		assertEquals(2, trash.list().length);
401 
402 		File heads = new File(trash, ".git/refs/heads");
403 		assertEquals(2, heads.listFiles().length);
404 
405 		this.assertNoHead();
406 		this.assertRepositoryCondition(1);
407 		assertEquals(CheckoutResult.NOT_TRIED_RESULT, co.getResult());
408 	}
409 
410 	private Repository createRepositoryWithRemote() throws IOException,
411 			URISyntaxException, MalformedURLException, GitAPIException,
412 			InvalidRemoteException, TransportException {
413 		// create second repository
414 		Repository db2 = createWorkRepository();
415 		Git git2 = new Git(db2);
416 
417 		// setup the second repository to fetch from the first repository
418 		final StoredConfig config = db2.getConfig();
419 		RemoteConfig remoteConfig = new RemoteConfig(config, "origin");
420 		URIish uri = new URIish(db.getDirectory().toURI().toURL());
421 		remoteConfig.addURI(uri);
422 		remoteConfig.update(config);
423 		config.save();
424 
425 		// fetch from first repository
426 		RefSpec spec = new RefSpec("+refs/heads/*:refs/remotes/origin/*");
427 		git2.fetch().setRemote("origin").setRefSpecs(spec).call();
428 		return db2;
429 	}
430 
431 	private CheckoutCommand newOrphanBranchCommand() {
432 		return git.checkout().setOrphan(true)
433 				.setName("orphanbranch");
434 	}
435 
436 	private static void assertCheckoutRef(Ref ref) {
437 		assertNotNull(ref);
438 		assertEquals("refs/heads/orphanbranch", ref.getTarget().getName());
439 	}
440 
441 	private void assertNoHead() throws IOException {
442 		assertNull(db.resolve("HEAD"));
443 	}
444 
445 	private void assertHeadDetached() throws IOException {
446 		Ref head = db.getRef(Constants.HEAD);
447 		assertFalse(head.isSymbolic());
448 		assertSame(head, head.getTarget());
449 	}
450 
451 	private void assertRepositoryCondition(int files) throws GitAPIException {
452 		org.eclipse.jgit.api.Status status = this.git.status().call();
453 		assertFalse(status.isClean());
454 		assertEquals(files, status.getAdded().size());
455 	}
456 
457 	@Test
458 	public void testCreateOrphanBranchWithStartCommit() throws Exception {
459 		CheckoutCommand co = newOrphanBranchCommand();
460 		Ref ref = co.setStartPoint(initialCommit).call();
461 		assertCheckoutRef(ref);
462 		assertEquals(2, trash.list().length);
463 		this.assertNoHead();
464 		this.assertRepositoryCondition(1);
465 	}
466 
467 	@Test
468 	public void testCreateOrphanBranchWithStartPoint() throws Exception {
469 		CheckoutCommand co = newOrphanBranchCommand();
470 		Ref ref = co.setStartPoint("HEAD^").call();
471 		assertCheckoutRef(ref);
472 
473 		assertEquals(2, trash.list().length);
474 		this.assertNoHead();
475 		this.assertRepositoryCondition(1);
476 	}
477 
478 	@Test
479 	public void testInvalidRefName() throws Exception {
480 		try {
481 			git.checkout().setOrphan(true).setName("../invalidname").call();
482 			fail("Should have failed");
483 		} catch (InvalidRefNameException e) {
484 			// except to hit here
485 		}
486 	}
487 
488 	@Test
489 	public void testNullRefName() throws Exception {
490 		try {
491 			git.checkout().setOrphan(true).setName(null).call();
492 			fail("Should have failed");
493 		} catch (InvalidRefNameException e) {
494 			// except to hit here
495 		}
496 	}
497 
498 	@Test
499 	public void testAlreadyExists() throws Exception {
500 		this.git.checkout().setCreateBranch(true).setName("orphanbranch")
501 				.call();
502 		this.git.checkout().setName("master").call();
503 
504 		try {
505 			newOrphanBranchCommand().call();
506 			fail("Should have failed");
507 		} catch (RefAlreadyExistsException e) {
508 			// except to hit here
509 		}
510 	}
511 
512 	// TODO: write a faster test which depends less on characteristics of
513 	// underlying filesystem/OS.
514 	@Test
515 	public void testCheckoutAutoCrlfTrue() throws Exception {
516 		int nrOfAutoCrlfTestFiles = 200;
517 
518 		FileBasedConfig c = db.getConfig();
519 		c.setString("core", null, "autocrlf", "true");
520 		c.save();
521 
522 		AddCommand add = git.add();
523 		for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) {
524 			writeTrashFile("Test_" + i + ".txt", "Hello " + i
525 					+ " world\nX\nYU\nJK\n");
526 			add.addFilepattern("Test_" + i + ".txt");
527 		}
528 		fsTick(null);
529 		add.call();
530 		RevCommit c1 = git.commit().setMessage("add some lines").call();
531 
532 		add = git.add();
533 		for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) {
534 			writeTrashFile("Test_" + i + ".txt", "Hello " + i
535 					+ " world\nX\nY\n");
536 			add.addFilepattern("Test_" + i + ".txt");
537 		}
538 		fsTick(null);
539 		add.call();
540 		git.commit().setMessage("add more").call();
541 
542 		git.checkout().setName(c1.getName()).call();
543 
544 		boolean foundUnsmudged = false;
545 		DirCache dc = db.readDirCache();
546 		for (int i = 100; i < 100 + nrOfAutoCrlfTestFiles; i++) {
547 			DirCacheEntry entry = dc.getEntry(
548 					"Test_" + i + ".txt");
549 			if (!entry.isSmudged()) {
550 				foundUnsmudged = true;
551 				assertEquals("unexpected file length in git index", 28,
552 						entry.getLength());
553 			}
554 		}
555 		org.junit.Assume.assumeTrue(foundUnsmudged);
556 	}
557 }