View Javadoc
1   /*
2    * Copyright (C) 2011, Kevin Sawicki <kevin@github.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.assertTrue;
47  
48  import java.io.File;
49  import java.io.IOException;
50  
51  import org.eclipse.jgit.api.CheckoutCommand.Stage;
52  import org.eclipse.jgit.api.errors.JGitInternalException;
53  import org.eclipse.jgit.dircache.DirCache;
54  import org.eclipse.jgit.dircache.DirCacheEntry;
55  import org.eclipse.jgit.errors.NoWorkTreeException;
56  import org.eclipse.jgit.junit.RepositoryTestCase;
57  import org.eclipse.jgit.lib.ConfigConstants;
58  import org.eclipse.jgit.lib.ObjectReader;
59  import org.eclipse.jgit.lib.RepositoryState;
60  import org.eclipse.jgit.lib.StoredConfig;
61  import org.eclipse.jgit.revwalk.RevCommit;
62  import org.junit.Before;
63  import org.junit.Test;
64  
65  /**
66   * Unit tests of path-based uses of {@link CheckoutCommand}
67   */
68  public class PathCheckoutCommandTest extends RepositoryTestCase {
69  
70  	private static final String FILE1 = "f/Test.txt";
71  
72  	private static final String FILE2 = "Test2.txt";
73  
74  	private static final String FILE3 = "Test3.txt";
75  
76  	Git git;
77  
78  	RevCommit initialCommit;
79  
80  	RevCommit secondCommit;
81  
82  	@Override
83  	@Before
84  	public void setUp() throws Exception {
85  		super.setUp();
86  		git = new Git(db);
87  		writeTrashFile(FILE1, "1");
88  		writeTrashFile(FILE2, "a");
89  		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
90  		initialCommit = git.commit().setMessage("Initial commit").call();
91  		writeTrashFile(FILE1, "2");
92  		writeTrashFile(FILE2, "b");
93  		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
94  		secondCommit = git.commit().setMessage("Second commit").call();
95  		writeTrashFile(FILE1, "3");
96  		writeTrashFile(FILE2, "c");
97  		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
98  		git.commit().setMessage("Third commit").call();
99  	}
100 
101 	@Test
102 	public void testUpdateWorkingDirectory() throws Exception {
103 		CheckoutCommand co = git.checkout();
104 		File written = writeTrashFile(FILE1, "");
105 		assertEquals("", read(written));
106 		co.addPath(FILE1).call();
107 		assertEquals("3", read(written));
108 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
109 	}
110 
111 	@Test
112 	public void testCheckoutFirst() throws Exception {
113 		CheckoutCommand co = git.checkout();
114 		File written = writeTrashFile(FILE1, "");
115 		co.setStartPoint(initialCommit).addPath(FILE1).call();
116 		assertEquals("1", read(written));
117 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
118 	}
119 
120 	@Test
121 	public void testCheckoutSecond() throws Exception {
122 		CheckoutCommand co = git.checkout();
123 		File written = writeTrashFile(FILE1, "");
124 		co.setStartPoint("HEAD~1").addPath(FILE1).call();
125 		assertEquals("2", read(written));
126 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
127 	}
128 
129 	@Test
130 	public void testCheckoutMultiple() throws Exception {
131 		CheckoutCommand co = git.checkout();
132 		File test = writeTrashFile(FILE1, "");
133 		File test2 = writeTrashFile(FILE2, "");
134 		co.setStartPoint("HEAD~2").addPath(FILE1).addPath(FILE2).call();
135 		assertEquals("1", read(test));
136 		assertEquals("a", read(test2));
137 	}
138 
139 	@Test
140 	public void testUpdateWorkingDirectoryFromIndex() throws Exception {
141 		CheckoutCommand co = git.checkout();
142 		File written = writeTrashFile(FILE1, "3a");
143 		git.add().addFilepattern(FILE1).call();
144 		written = writeTrashFile(FILE1, "");
145 		assertEquals("", read(written));
146 		co.addPath(FILE1).call();
147 		assertEquals("3a", read(written));
148 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
149 	}
150 
151 	@Test
152 	public void testUpdateWorkingDirectoryFromHeadWithIndexChange()
153 			throws Exception {
154 		CheckoutCommand co = git.checkout();
155 		File written = writeTrashFile(FILE1, "3a");
156 		git.add().addFilepattern(FILE1).call();
157 		written = writeTrashFile(FILE1, "");
158 		assertEquals("", read(written));
159 		co.addPath(FILE1).setStartPoint("HEAD").call();
160 		assertEquals("3", read(written));
161 		assertEquals("c", read(new File(db.getWorkTree(), FILE2)));
162 	}
163 
164 	@Test
165 	public void testUpdateWorkingDirectoryFromIndex2() throws Exception {
166 		CheckoutCommand co = git.checkout();
167 		fsTick(git.getRepository().getIndexFile());
168 
169 		File written1 = writeTrashFile(FILE1, "3(modified)");
170 		File written2 = writeTrashFile(FILE2, "a(modified)");
171 		fsTick(written2);
172 
173 		// make sure that we get unsmudged entries for FILE1 and FILE2
174 		writeTrashFile(FILE3, "foo");
175 		git.add().addFilepattern(FILE3).call();
176 		fsTick(git.getRepository().getIndexFile());
177 
178 		git.add().addFilepattern(FILE1).addFilepattern(FILE2).call();
179 		fsTick(git.getRepository().getIndexFile());
180 
181 		writeTrashFile(FILE1, "3(modified again)");
182 		writeTrashFile(FILE2, "a(modified again)");
183 		fsTick(written2);
184 
185 		co.addPath(FILE1).setStartPoint(secondCommit).call();
186 
187 		assertEquals("2", read(written1));
188 		assertEquals("a(modified again)", read(written2));
189 
190 		validateIndex(git);
191 	}
192 
193 	public static void validateIndex(Git git) throws NoWorkTreeException,
194 			IOException {
195 		DirCache dc = git.getRepository().lockDirCache();
196 		try (ObjectReader r = git.getRepository().getObjectDatabase()
197 				.newReader()) {
198 			for (int i = 0; i < dc.getEntryCount(); ++i) {
199 				DirCacheEntry entry = dc.getEntry(i);
200 				if (entry.getLength() > 0)
201 					assertEquals(entry.getLength(), r.getObjectSize(
202 							entry.getObjectId(), ObjectReader.OBJ_ANY));
203 			}
204 		} finally {
205 			dc.unlock();
206 		}
207 	}
208 
209 	public void testCheckoutMixedNewlines() throws Exception {
210 		// "git config core.autocrlf true"
211 		StoredConfig config = git.getRepository().getConfig();
212 		config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
213 				ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
214 		config.save();
215 		// edit <FILE1>
216 		File written = writeTrashFile(FILE1, "4\r\n4");
217 		assertEquals("4\r\n4", read(written));
218 		// "git add <FILE1>"
219 		git.add().addFilepattern(FILE1).call();
220 		// "git commit -m 'CRLF'"
221 		git.commit().setMessage("CRLF").call();
222 		// edit <FILE1>
223 		written = writeTrashFile(FILE1, "4\n4");
224 		assertEquals("4\n4", read(written));
225 		// "git add <FILE1>"
226 		git.add().addFilepattern(FILE1).call();
227 		// "git checkout -- <FILE1>
228 		git.checkout().addPath(FILE1).call();
229 		// "git status" => clean
230 		Status status = git.status().call();
231 		assertEquals(0, status.getAdded().size());
232 		assertEquals(0, status.getChanged().size());
233 		assertEquals(0, status.getConflicting().size());
234 		assertEquals(0, status.getMissing().size());
235 		assertEquals(0, status.getModified().size());
236 		assertEquals(0, status.getRemoved().size());
237 		assertEquals(0, status.getUntracked().size());
238 	}
239 
240 	@Test
241 	public void testCheckoutRepository() throws Exception {
242 		CheckoutCommand co = git.checkout();
243 		File test = writeTrashFile(FILE1, "");
244 		File test2 = writeTrashFile(FILE2, "");
245 		co.setStartPoint("HEAD~2").setAllPaths(true).call();
246 		assertEquals("1", read(test));
247 		assertEquals("a", read(test2));
248 	}
249 
250 
251 	@Test(expected = JGitInternalException.class)
252 	public void testCheckoutOfConflictingFileShouldThrow()
253 			throws Exception {
254 		setupConflictingState();
255 
256 		git.checkout().addPath(FILE1).call();
257 	}
258 
259 	@Test
260 	public void testCheckoutOurs() throws Exception {
261 		setupConflictingState();
262 
263 		git.checkout().setStage(Stage.OURS).addPath(FILE1).call();
264 
265 		assertEquals("3", read(FILE1));
266 		assertStageOneToThree(FILE1);
267 	}
268 
269 	@Test
270 	public void testCheckoutTheirs() throws Exception {
271 		setupConflictingState();
272 
273 		git.checkout().setStage(Stage.THEIRS).addPath(FILE1).call();
274 
275 		assertEquals("Conflicting", read(FILE1));
276 		assertStageOneToThree(FILE1);
277 	}
278 
279 	@Test
280 	public void testCheckoutOursWhenNoBase() throws Exception {
281 		String file = "added.txt";
282 
283 		git.checkout().setCreateBranch(true).setName("side")
284 				.setStartPoint(initialCommit).call();
285 		writeTrashFile(file, "Added on side");
286 		git.add().addFilepattern(file).call();
287 		RevCommit side = git.commit().setMessage("Commit on side").call();
288 
289 		git.checkout().setName("master").call();
290 		writeTrashFile(file, "Added on master");
291 		git.add().addFilepattern(file).call();
292 		git.commit().setMessage("Commit on master").call();
293 
294 		git.merge().include(side).call();
295 		assertEquals(RepositoryState.MERGING, db.getRepositoryState());
296 
297 		DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
298 		assertEquals("Expected add/add file to not have base stage",
299 				DirCacheEntry.STAGE_2, cache.getEntry(file).getStage());
300 
301 		assertTrue(read(file).startsWith("<<<<<<< HEAD"));
302 
303 		git.checkout().setStage(Stage.OURS).addPath(file).call();
304 
305 		assertEquals("Added on master", read(file));
306 
307 		cache = DirCache.read(db.getIndexFile(), db.getFS());
308 		assertEquals("Expected conflict stages to still exist after checkout",
309 				DirCacheEntry.STAGE_2, cache.getEntry(file).getStage());
310 	}
311 
312 	@Test(expected = IllegalStateException.class)
313 	public void testStageNotPossibleWithBranch() throws Exception {
314 		git.checkout().setStage(Stage.OURS).setStartPoint("master").call();
315 	}
316 
317 	private void setupConflictingState() throws Exception {
318 		git.checkout().setCreateBranch(true).setName("conflict")
319 				.setStartPoint(initialCommit).call();
320 		writeTrashFile(FILE1, "Conflicting");
321 		RevCommit conflict = git.commit().setAll(true)
322 				.setMessage("Conflicting change").call();
323 
324 		git.checkout().setName("master").call();
325 
326 		git.merge().include(conflict).call();
327 		assertEquals(RepositoryState.MERGING, db.getRepositoryState());
328 		assertStageOneToThree(FILE1);
329 	}
330 
331 	private void assertStageOneToThree(String name) throws Exception {
332 		DirCache cache = DirCache.read(db.getIndexFile(), db.getFS());
333 		int i = cache.findEntry(name);
334 		DirCacheEntry stage1 = cache.getEntry(i);
335 		DirCacheEntry stage2 = cache.getEntry(i + 1);
336 		DirCacheEntry stage3 = cache.getEntry(i + 2);
337 
338 		assertEquals(DirCacheEntry.STAGE_1, stage1.getStage());
339 		assertEquals(DirCacheEntry.STAGE_2, stage2.getStage());
340 		assertEquals(DirCacheEntry.STAGE_3, stage3.getStage());
341 	}
342 }