View Javadoc
1   /*
2    * Copyright (C) 2012, GitHub Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.api;
11  
12  import static org.junit.Assert.assertEquals;
13  import static org.junit.Assert.assertFalse;
14  import static org.junit.Assert.assertNotNull;
15  import static org.junit.Assert.assertNull;
16  import static org.junit.Assert.assertTrue;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.util.List;
21  
22  import org.eclipse.jgit.api.errors.UnmergedPathsException;
23  import org.eclipse.jgit.diff.DiffEntry;
24  import org.eclipse.jgit.junit.RepositoryTestCase;
25  import org.eclipse.jgit.lib.Constants;
26  import org.eclipse.jgit.lib.ObjectId;
27  import org.eclipse.jgit.lib.PersonIdent;
28  import org.eclipse.jgit.lib.Ref;
29  import org.eclipse.jgit.lib.ReflogEntry;
30  import org.eclipse.jgit.lib.ReflogReader;
31  import org.eclipse.jgit.revwalk.RevCommit;
32  import org.eclipse.jgit.revwalk.RevWalk;
33  import org.eclipse.jgit.treewalk.TreeWalk;
34  import org.eclipse.jgit.treewalk.filter.TreeFilter;
35  import org.eclipse.jgit.util.FileUtils;
36  import org.junit.Before;
37  import org.junit.Test;
38  
39  /**
40   * Unit tests of {@link StashCreateCommand}
41   */
42  public class StashCreateCommandTest extends RepositoryTestCase {
43  
44  	private RevCommit head;
45  
46  	private Git git;
47  
48  	private File committedFile;
49  
50  	private File untrackedFile;
51  
52  	@Override
53  	@Before
54  	public void setUp() throws Exception {
55  		super.setUp();
56  		git = Git.wrap(db);
57  		committedFile = writeTrashFile("file.txt", "content");
58  		git.add().addFilepattern("file.txt").call();
59  		head = git.commit().setMessage("add file").call();
60  		assertNotNull(head);
61  		untrackedFile = writeTrashFile("untracked.txt", "content");
62  	}
63  
64  	private void validateStashedCommit(RevCommit commit)
65  			throws IOException {
66  		validateStashedCommit(commit, 2);
67  	}
68  
69  	/**
70  	 * Core validation to be performed on all stashed commits
71  	 *
72  	 * @param commit
73  	 * @param parentCount
74  	 *            number of parent commits required
75  	 * @throws IOException
76  	 */
77  	private void validateStashedCommit(final RevCommit commit,
78  			int parentCount)
79  			throws IOException {
80  		assertNotNull(commit);
81  		Ref stashRef = db.exactRef(Constants.R_STASH);
82  		assertNotNull(stashRef);
83  		assertEquals(commit, stashRef.getObjectId());
84  		assertNotNull(commit.getAuthorIdent());
85  		assertEquals(commit.getAuthorIdent(), commit.getCommitterIdent());
86  		assertEquals(parentCount, commit.getParentCount());
87  
88  		// Load parents
89  		try (RevWalk walk = new RevWalk(db)) {
90  			for (RevCommit parent : commit.getParents())
91  				walk.parseBody(parent);
92  		}
93  
94  		assertEquals(1, commit.getParent(1).getParentCount());
95  		assertEquals(head, commit.getParent(1).getParent(0));
96  		assertFalse("Head tree matches stashed commit tree", commit.getTree()
97  				.equals(head.getTree()));
98  		assertEquals(head, commit.getParent(0));
99  		assertFalse(commit.getFullMessage().equals(
100 				commit.getParent(1).getFullMessage()));
101 	}
102 
103 	private TreeWalk createTreeWalk() {
104 		TreeWalk walk = new TreeWalk(db);
105 		walk.setRecursive(true);
106 		walk.setFilter(TreeFilter.ANY_DIFF);
107 		return walk;
108 	}
109 
110 	private List<DiffEntry> diffWorkingAgainstHead(RevCommit commit)
111 			throws IOException {
112 		try (TreeWalk walk = createTreeWalk()) {
113 			walk.addTree(commit.getParent(0).getTree());
114 			walk.addTree(commit.getTree());
115 			return DiffEntry.scan(walk);
116 		}
117 	}
118 
119 	private List<DiffEntry> diffIndexAgainstHead(RevCommit commit)
120 			throws IOException {
121 		try (TreeWalk walk = createTreeWalk()) {
122 			walk.addTree(commit.getParent(0).getTree());
123 			walk.addTree(commit.getParent(1).getTree());
124 			return DiffEntry.scan(walk);
125 		}
126 	}
127 
128 	private List<DiffEntry> diffIndexAgainstWorking(RevCommit commit)
129 			throws IOException {
130 		try (TreeWalk walk = createTreeWalk()) {
131 			walk.addTree(commit.getParent(1).getTree());
132 			walk.addTree(commit.getTree());
133 			return DiffEntry.scan(walk);
134 		}
135 	}
136 
137 	@Test
138 	public void noLocalChanges() throws Exception {
139 		assertNull(git.stashCreate().call());
140 	}
141 
142 	@Test
143 	public void workingDirectoryDelete() throws Exception {
144 		deleteTrashFile("file.txt");
145 		RevCommit stashed = git.stashCreate().call();
146 		assertNotNull(stashed);
147 		assertEquals("content", read(committedFile));
148 		validateStashedCommit(stashed);
149 
150 		assertEquals(head.getTree(), stashed.getParent(1).getTree());
151 
152 		List<DiffEntry> diffs = diffWorkingAgainstHead(stashed);
153 		assertEquals(1, diffs.size());
154 		assertEquals(DiffEntry.ChangeType.DELETE, diffs.get(0).getChangeType());
155 		assertEquals("file.txt", diffs.get(0).getOldPath());
156 	}
157 
158 	@Test
159 	public void indexAdd() throws Exception {
160 		File addedFile = writeTrashFile("file2.txt", "content2");
161 		git.add().addFilepattern("file2.txt").call();
162 
163 		RevCommit stashed = Git.wrap(db).stashCreate().call();
164 		assertNotNull(stashed);
165 		assertFalse(addedFile.exists());
166 		validateStashedCommit(stashed);
167 
168 		assertEquals(stashed.getTree(), stashed.getParent(1).getTree());
169 
170 		List<DiffEntry> diffs = diffWorkingAgainstHead(stashed);
171 		assertEquals(1, diffs.size());
172 		assertEquals(DiffEntry.ChangeType.ADD, diffs.get(0).getChangeType());
173 		assertEquals("file2.txt", diffs.get(0).getNewPath());
174 	}
175 
176 	@Test
177 	public void newFileInIndexThenModifiedInWorkTree() throws Exception {
178 		writeTrashFile("file", "content");
179 		git.add().addFilepattern("file").call();
180 		writeTrashFile("file", "content2");
181 		RevCommit stashedWorkTree = Git.wrap(db).stashCreate().call();
182 		validateStashedCommit(stashedWorkTree);
183 		try (RevWalk walk = new RevWalk(db)) {
184 			RevCommit stashedIndex = stashedWorkTree.getParent(1);
185 			walk.parseBody(stashedIndex);
186 			walk.parseBody(stashedIndex.getTree());
187 			walk.parseBody(stashedIndex.getParent(0));
188 		}
189 		List<DiffEntry> workTreeStashAgainstWorkTree = diffWorkingAgainstHead(stashedWorkTree);
190 		assertEquals(1, workTreeStashAgainstWorkTree.size());
191 		List<DiffEntry> workIndexAgainstWorkTree = diffIndexAgainstHead(stashedWorkTree);
192 		assertEquals(1, workIndexAgainstWorkTree.size());
193 		List<DiffEntry> indexStashAgainstWorkTree = diffIndexAgainstWorking(stashedWorkTree);
194 		assertEquals(1, indexStashAgainstWorkTree.size());
195 	}
196 
197 	@Test
198 	public void indexDelete() throws Exception {
199 		git.rm().addFilepattern("file.txt").call();
200 
201 		RevCommit stashed = Git.wrap(db).stashCreate().call();
202 		assertNotNull(stashed);
203 		assertEquals("content", read(committedFile));
204 		validateStashedCommit(stashed);
205 
206 		assertEquals(stashed.getTree(), stashed.getParent(1).getTree());
207 
208 		List<DiffEntry> diffs = diffWorkingAgainstHead(stashed);
209 		assertEquals(1, diffs.size());
210 		assertEquals(DiffEntry.ChangeType.DELETE, diffs.get(0).getChangeType());
211 		assertEquals("file.txt", diffs.get(0).getOldPath());
212 	}
213 
214 	@Test
215 	public void workingDirectoryModify() throws Exception {
216 		writeTrashFile("file.txt", "content2");
217 
218 		RevCommit stashed = Git.wrap(db).stashCreate().call();
219 		assertNotNull(stashed);
220 		assertEquals("content", read(committedFile));
221 		validateStashedCommit(stashed);
222 
223 		assertEquals(head.getTree(), stashed.getParent(1).getTree());
224 
225 		List<DiffEntry> diffs = diffWorkingAgainstHead(stashed);
226 		assertEquals(1, diffs.size());
227 		assertEquals(DiffEntry.ChangeType.MODIFY, diffs.get(0).getChangeType());
228 		assertEquals("file.txt", diffs.get(0).getNewPath());
229 	}
230 
231 	@Test
232 	public void workingDirectoryModifyInSubfolder() throws Exception {
233 		String path = "d1/d2/f.txt";
234 		File subfolderFile = writeTrashFile(path, "content");
235 		git.add().addFilepattern(path).call();
236 		head = git.commit().setMessage("add file").call();
237 
238 		writeTrashFile(path, "content2");
239 
240 		RevCommit stashed = Git.wrap(db).stashCreate().call();
241 		assertNotNull(stashed);
242 		assertEquals("content", read(subfolderFile));
243 		validateStashedCommit(stashed);
244 
245 		assertEquals(head.getTree(), stashed.getParent(1).getTree());
246 
247 		List<DiffEntry> diffs = diffWorkingAgainstHead(stashed);
248 		assertEquals(1, diffs.size());
249 		assertEquals(DiffEntry.ChangeType.MODIFY, diffs.get(0).getChangeType());
250 		assertEquals(path, diffs.get(0).getNewPath());
251 	}
252 
253 	@Test
254 	public void workingDirectoryModifyIndexChanged() throws Exception {
255 		writeTrashFile("file.txt", "content2");
256 		git.add().addFilepattern("file.txt").call();
257 		writeTrashFile("file.txt", "content3");
258 
259 		RevCommit stashed = Git.wrap(db).stashCreate().call();
260 		assertNotNull(stashed);
261 		assertEquals("content", read(committedFile));
262 		validateStashedCommit(stashed);
263 
264 		assertFalse(stashed.getTree().equals(stashed.getParent(1).getTree()));
265 
266 		List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed);
267 		assertEquals(1, workingDiffs.size());
268 		assertEquals(DiffEntry.ChangeType.MODIFY, workingDiffs.get(0)
269 				.getChangeType());
270 		assertEquals("file.txt", workingDiffs.get(0).getNewPath());
271 
272 		List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed);
273 		assertEquals(1, indexDiffs.size());
274 		assertEquals(DiffEntry.ChangeType.MODIFY, indexDiffs.get(0)
275 				.getChangeType());
276 		assertEquals("file.txt", indexDiffs.get(0).getNewPath());
277 
278 		assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0)
279 				.getOldId());
280 		assertFalse(workingDiffs.get(0).getNewId()
281 				.equals(indexDiffs.get(0).getNewId()));
282 	}
283 
284 	@Test
285 	public void workingDirectoryCleanIndexModify() throws Exception {
286 		writeTrashFile("file.txt", "content2");
287 		git.add().addFilepattern("file.txt").call();
288 		writeTrashFile("file.txt", "content");
289 
290 		RevCommit stashed = Git.wrap(db).stashCreate().call();
291 		assertNotNull(stashed);
292 		assertEquals("content", read(committedFile));
293 		validateStashedCommit(stashed);
294 
295 		assertEquals(stashed.getParent(1).getTree(), stashed.getTree());
296 
297 		List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed);
298 		assertEquals(1, workingDiffs.size());
299 		assertEquals(DiffEntry.ChangeType.MODIFY, workingDiffs.get(0)
300 				.getChangeType());
301 		assertEquals("file.txt", workingDiffs.get(0).getNewPath());
302 
303 		List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed);
304 		assertEquals(1, indexDiffs.size());
305 		assertEquals(DiffEntry.ChangeType.MODIFY, indexDiffs.get(0)
306 				.getChangeType());
307 		assertEquals("file.txt", indexDiffs.get(0).getNewPath());
308 
309 		assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0)
310 				.getOldId());
311 		assertTrue(workingDiffs.get(0).getNewId()
312 				.equals(indexDiffs.get(0).getNewId()));
313 	}
314 
315 	@Test
316 	public void workingDirectoryDeleteIndexAdd() throws Exception {
317 		String path = "file2.txt";
318 		File added = writeTrashFile(path, "content2");
319 		assertTrue(added.exists());
320 		git.add().addFilepattern(path).call();
321 		FileUtils.delete(added);
322 		assertFalse(added.exists());
323 
324 		RevCommit stashed = Git.wrap(db).stashCreate().call();
325 		assertNotNull(stashed);
326 		assertFalse(added.exists());
327 
328 		validateStashedCommit(stashed);
329 
330 		assertEquals(stashed.getParent(1).getTree(), stashed.getTree());
331 
332 		List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed);
333 		assertEquals(1, workingDiffs.size());
334 		assertEquals(DiffEntry.ChangeType.ADD, workingDiffs.get(0)
335 				.getChangeType());
336 		assertEquals(path, workingDiffs.get(0).getNewPath());
337 
338 		List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed);
339 		assertEquals(1, indexDiffs.size());
340 		assertEquals(DiffEntry.ChangeType.ADD, indexDiffs.get(0)
341 				.getChangeType());
342 		assertEquals(path, indexDiffs.get(0).getNewPath());
343 
344 		assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0)
345 				.getOldId());
346 		assertTrue(workingDiffs.get(0).getNewId()
347 				.equals(indexDiffs.get(0).getNewId()));
348 	}
349 
350 	@Test
351 	public void workingDirectoryDeleteIndexEdit() throws Exception {
352 		File edited = writeTrashFile("file.txt", "content2");
353 		git.add().addFilepattern("file.txt").call();
354 		FileUtils.delete(edited);
355 		assertFalse(edited.exists());
356 
357 		RevCommit stashed = Git.wrap(db).stashCreate().call();
358 		assertNotNull(stashed);
359 		assertEquals("content", read(committedFile));
360 		validateStashedCommit(stashed);
361 
362 		assertFalse(stashed.getTree().equals(stashed.getParent(1).getTree()));
363 
364 		List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed);
365 		assertEquals(1, workingDiffs.size());
366 		assertEquals(DiffEntry.ChangeType.DELETE, workingDiffs.get(0)
367 				.getChangeType());
368 		assertEquals("file.txt", workingDiffs.get(0).getOldPath());
369 
370 		List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed);
371 		assertEquals(1, indexDiffs.size());
372 		assertEquals(DiffEntry.ChangeType.MODIFY, indexDiffs.get(0)
373 				.getChangeType());
374 		assertEquals("file.txt", indexDiffs.get(0).getNewPath());
375 
376 		assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0)
377 				.getOldId());
378 		assertFalse(workingDiffs.get(0).getNewId()
379 				.equals(indexDiffs.get(0).getNewId()));
380 	}
381 
382 	@Test
383 	public void multipleEdits() throws Exception {
384 		git.rm().addFilepattern("file.txt").call();
385 		File addedFile = writeTrashFile("file2.txt", "content2");
386 		git.add().addFilepattern("file2.txt").call();
387 
388 		RevCommit stashed = Git.wrap(db).stashCreate().call();
389 		assertNotNull(stashed);
390 		assertFalse(addedFile.exists());
391 		validateStashedCommit(stashed);
392 
393 		assertEquals(stashed.getTree(), stashed.getParent(1).getTree());
394 
395 		List<DiffEntry> diffs = diffWorkingAgainstHead(stashed);
396 		assertEquals(2, diffs.size());
397 		assertEquals(DiffEntry.ChangeType.DELETE, diffs.get(0).getChangeType());
398 		assertEquals("file.txt", diffs.get(0).getOldPath());
399 		assertEquals(DiffEntry.ChangeType.ADD, diffs.get(1).getChangeType());
400 		assertEquals("file2.txt", diffs.get(1).getNewPath());
401 	}
402 
403 	@Test
404 	public void refLogIncludesCommitMessage() throws Exception {
405 		PersonIdent who = new PersonIdent("user", "user@email.com");
406 		deleteTrashFile("file.txt");
407 		RevCommit stashed = git.stashCreate().setPerson(who).call();
408 		assertNotNull(stashed);
409 		assertEquals("content", read(committedFile));
410 		validateStashedCommit(stashed);
411 
412 		ReflogReader reader = git.getRepository().getReflogReader(
413 				Constants.R_STASH);
414 		ReflogEntry entry = reader.getLastEntry();
415 		assertNotNull(entry);
416 		assertEquals(ObjectId.zeroId(), entry.getOldId());
417 		assertEquals(stashed, entry.getNewId());
418 		assertEquals(who, entry.getWho());
419 		assertEquals(stashed.getFullMessage(), entry.getComment());
420 	}
421 
422 	@Test(expected = UnmergedPathsException.class)
423 	public void unmergedPathsShouldCauseException() throws Exception {
424 		commitFile("file.txt", "master", "base");
425 		RevCommit side = commitFile("file.txt", "side", "side");
426 		commitFile("file.txt", "master", "master");
427 		git.merge().include(side).call();
428 
429 		git.stashCreate().call();
430 	}
431 
432 	@Test
433 	public void untrackedFileIncluded() throws Exception {
434 		String trackedPath = "tracked.txt";
435 		writeTrashFile(trackedPath, "content2");
436 		git.add().addFilepattern(trackedPath).call();
437 
438 		RevCommit stashed = git.stashCreate()
439 				.setIncludeUntracked(true).call();
440 		validateStashedCommit(stashed, 3);
441 
442 		assertEquals(
443 				"Expected commits for workingDir,stashedIndex and untrackedFiles.",
444 				3, stashed.getParentCount());
445 		assertFalse("untracked file should be deleted.", untrackedFile.exists());
446 	}
447 
448 	@Test
449 	public void untrackedFileNotIncluded() throws Exception {
450 		String trackedPath = "tracked.txt";
451 		// at least one modification needed
452 		writeTrashFile(trackedPath, "content2");
453 		git.add().addFilepattern(trackedPath).call();
454 
455 		RevCommit stashed = git.stashCreate().call();
456 		validateStashedCommit(stashed);
457 
458 		assertTrue("untracked file should be left untouched.",
459 				untrackedFile.exists());
460 		assertEquals("content", read(untrackedFile));
461 	}
462 }