View Javadoc
1   /*
2    * Copyright (C) 2012, 2021 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.assertTrue;
16  import static org.junit.Assert.fail;
17  
18  import java.io.File;
19  import java.text.MessageFormat;
20  
21  import org.eclipse.jgit.api.errors.InvalidRefNameException;
22  import org.eclipse.jgit.api.errors.JGitInternalException;
23  import org.eclipse.jgit.api.errors.NoHeadException;
24  import org.eclipse.jgit.api.errors.StashApplyFailureException;
25  import org.eclipse.jgit.events.ChangeRecorder;
26  import org.eclipse.jgit.events.ListenerHandle;
27  import org.eclipse.jgit.internal.JGitText;
28  import org.eclipse.jgit.junit.RepositoryTestCase;
29  import org.eclipse.jgit.lib.ObjectId;
30  import org.eclipse.jgit.lib.Repository;
31  import org.eclipse.jgit.merge.ContentMergeStrategy;
32  import org.eclipse.jgit.merge.MergeStrategy;
33  import org.eclipse.jgit.revwalk.RevCommit;
34  import org.eclipse.jgit.util.FileUtils;
35  import org.junit.After;
36  import org.junit.Before;
37  import org.junit.Test;
38  
39  /**
40   * Unit tests of {@link StashApplyCommand}
41   */
42  public class StashApplyCommandTest extends RepositoryTestCase {
43  
44  	private static final String PATH = "file.txt";
45  
46  	private RevCommit head;
47  
48  	private Git git;
49  
50  	private File committedFile;
51  
52  	private ChangeRecorder recorder;
53  
54  	private ListenerHandle handle;
55  
56  	@Override
57  	@Before
58  	public void setUp() throws Exception {
59  		super.setUp();
60  		git = Git.wrap(db);
61  		recorder = new ChangeRecorder();
62  		handle = db.getListenerList().addWorkingTreeModifiedListener(recorder);
63  		committedFile = writeTrashFile(PATH, "content");
64  		git.add().addFilepattern(PATH).call();
65  		head = git.commit().setMessage("add file").call();
66  		assertNotNull(head);
67  		recorder.assertNoEvent();
68  	}
69  
70  	@Override
71  	@After
72  	public void tearDown() throws Exception {
73  		if (handle != null) {
74  			handle.remove();
75  		}
76  		super.tearDown();
77  	}
78  
79  	@Test
80  	public void workingDirectoryDelete() throws Exception {
81  		deleteTrashFile(PATH);
82  		assertFalse(committedFile.exists());
83  		RevCommit stashed = git.stashCreate().call();
84  		assertNotNull(stashed);
85  		assertEquals("content", read(committedFile));
86  		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
87  
88  		ObjectId unstashed = git.stashApply().call();
89  		assertEquals(stashed, unstashed);
90  		assertFalse(committedFile.exists());
91  		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
92  
93  		Status status = git.status().call();
94  		assertTrue(status.getAdded().isEmpty());
95  		assertTrue(status.getChanged().isEmpty());
96  		assertTrue(status.getConflicting().isEmpty());
97  		assertTrue(status.getModified().isEmpty());
98  		assertTrue(status.getUntracked().isEmpty());
99  		assertTrue(status.getRemoved().isEmpty());
100 
101 		assertEquals(1, status.getMissing().size());
102 		assertTrue(status.getMissing().contains(PATH));
103 	}
104 
105 	@Test
106 	public void indexAdd() throws Exception {
107 		String addedPath = "file2.txt";
108 		File addedFile = writeTrashFile(addedPath, "content2");
109 		git.add().addFilepattern(addedPath).call();
110 
111 		RevCommit stashed = git.stashCreate().call();
112 		assertNotNull(stashed);
113 		assertFalse(addedFile.exists());
114 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { addedPath });
115 
116 		ObjectId unstashed = git.stashApply().call();
117 		assertEquals(stashed, unstashed);
118 		assertTrue(addedFile.exists());
119 		assertEquals("content2", read(addedFile));
120 		recorder.assertEvent(new String[] { addedPath }, ChangeRecorder.EMPTY);
121 
122 		Status status = git.status().call();
123 		assertTrue(status.getChanged().isEmpty());
124 		assertTrue(status.getConflicting().isEmpty());
125 		assertTrue(status.getMissing().isEmpty());
126 		assertTrue(status.getModified().isEmpty());
127 		assertTrue(status.getRemoved().isEmpty());
128 		assertTrue(status.getUntracked().isEmpty());
129 
130 		assertEquals(1, status.getAdded().size());
131 		assertTrue(status.getAdded().contains(addedPath));
132 	}
133 
134 	@Test
135 	public void indexDelete() throws Exception {
136 		git.rm().addFilepattern("file.txt").call();
137 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
138 
139 		RevCommit stashed = git.stashCreate().call();
140 		assertNotNull(stashed);
141 		assertEquals("content", read(committedFile));
142 		recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
143 
144 		ObjectId unstashed = git.stashApply().call();
145 		assertEquals(stashed, unstashed);
146 		assertFalse(committedFile.exists());
147 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
148 
149 		Status status = git.status().call();
150 		assertTrue(status.getAdded().isEmpty());
151 		assertTrue(status.getChanged().isEmpty());
152 		assertTrue(status.getConflicting().isEmpty());
153 		assertTrue(status.getModified().isEmpty());
154 		assertTrue(status.getMissing().isEmpty());
155 		assertTrue(status.getUntracked().isEmpty());
156 
157 		assertEquals(1, status.getRemoved().size());
158 		assertTrue(status.getRemoved().contains(PATH));
159 	}
160 
161 	@Test
162 	public void workingDirectoryModify() throws Exception {
163 		writeTrashFile("file.txt", "content2");
164 
165 		RevCommit stashed = git.stashCreate().call();
166 		assertNotNull(stashed);
167 		assertEquals("content", read(committedFile));
168 		recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
169 
170 		ObjectId unstashed = git.stashApply().call();
171 		assertEquals(stashed, unstashed);
172 		assertEquals("content2", read(committedFile));
173 		recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
174 
175 		Status status = git.status().call();
176 		assertTrue(status.getAdded().isEmpty());
177 		assertTrue(status.getChanged().isEmpty());
178 		assertTrue(status.getConflicting().isEmpty());
179 		assertTrue(status.getMissing().isEmpty());
180 		assertTrue(status.getRemoved().isEmpty());
181 		assertTrue(status.getUntracked().isEmpty());
182 
183 		assertEquals(1, status.getModified().size());
184 		assertTrue(status.getModified().contains(PATH));
185 	}
186 
187 	@Test
188 	public void workingDirectoryModifyInSubfolder() throws Exception {
189 		String path = "d1/d2/f.txt";
190 		File subfolderFile = writeTrashFile(path, "content");
191 		git.add().addFilepattern(path).call();
192 		head = git.commit().setMessage("add file").call();
193 		recorder.assertNoEvent();
194 
195 		writeTrashFile(path, "content2");
196 
197 		RevCommit stashed = git.stashCreate().call();
198 		assertNotNull(stashed);
199 		assertEquals("content", read(subfolderFile));
200 		recorder.assertEvent(new String[] { "d1/d2/f.txt" },
201 				ChangeRecorder.EMPTY);
202 
203 		ObjectId unstashed = git.stashApply().call();
204 		assertEquals(stashed, unstashed);
205 		assertEquals("content2", read(subfolderFile));
206 		recorder.assertEvent(new String[] { "d1/d2/f.txt" },
207 				ChangeRecorder.EMPTY);
208 
209 		Status status = git.status().call();
210 		assertTrue(status.getAdded().isEmpty());
211 		assertTrue(status.getChanged().isEmpty());
212 		assertTrue(status.getConflicting().isEmpty());
213 		assertTrue(status.getMissing().isEmpty());
214 		assertTrue(status.getRemoved().isEmpty());
215 		assertTrue(status.getUntracked().isEmpty());
216 
217 		assertEquals(1, status.getModified().size());
218 		assertTrue(status.getModified().contains(path));
219 	}
220 
221 	@Test
222 	public void workingDirectoryModifyIndexChanged() throws Exception {
223 		writeTrashFile("file.txt", "content2");
224 		git.add().addFilepattern("file.txt").call();
225 		writeTrashFile("file.txt", "content3");
226 
227 		RevCommit stashed = git.stashCreate().call();
228 		assertNotNull(stashed);
229 		assertEquals("content", read(committedFile));
230 		recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
231 
232 		ObjectId unstashed = git.stashApply().call();
233 		assertEquals(stashed, unstashed);
234 		assertEquals("content3", read(committedFile));
235 		recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
236 
237 		Status status = git.status().call();
238 		assertTrue(status.getAdded().isEmpty());
239 		assertTrue(status.getConflicting().isEmpty());
240 		assertTrue(status.getMissing().isEmpty());
241 		assertTrue(status.getRemoved().isEmpty());
242 		assertTrue(status.getUntracked().isEmpty());
243 
244 		assertEquals(1, status.getChanged().size());
245 		assertTrue(status.getChanged().contains(PATH));
246 		assertEquals(1, status.getModified().size());
247 		assertTrue(status.getModified().contains(PATH));
248 	}
249 
250 	@Test
251 	public void workingDirectoryCleanIndexModify() throws Exception {
252 		writeTrashFile("file.txt", "content2");
253 		git.add().addFilepattern("file.txt").call();
254 		writeTrashFile("file.txt", "content");
255 
256 		RevCommit stashed = git.stashCreate().call();
257 		assertNotNull(stashed);
258 		assertEquals("content", read(committedFile));
259 		recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
260 
261 		ObjectId unstashed = git.stashApply().call();
262 		assertEquals(stashed, unstashed);
263 		assertEquals("content2", read(committedFile));
264 		recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
265 
266 		Status status = git.status().call();
267 		assertTrue(status.getAdded().isEmpty());
268 		assertTrue(status.getConflicting().isEmpty());
269 		assertTrue(status.getMissing().isEmpty());
270 		assertTrue(status.getModified().isEmpty());
271 		assertTrue(status.getRemoved().isEmpty());
272 		assertTrue(status.getUntracked().isEmpty());
273 
274 		assertEquals(1, status.getChanged().size());
275 		assertTrue(status.getChanged().contains(PATH));
276 	}
277 
278 	@Test
279 	public void workingDirectoryDeleteIndexAdd() throws Exception {
280 		String path = "file2.txt";
281 		File added = writeTrashFile(path, "content2");
282 		assertTrue(added.exists());
283 		git.add().addFilepattern(path).call();
284 		FileUtils.delete(added);
285 		assertFalse(added.exists());
286 
287 		RevCommit stashed = git.stashCreate().call();
288 		assertNotNull(stashed);
289 		assertFalse(added.exists());
290 		recorder.assertNoEvent();
291 
292 		ObjectId unstashed = git.stashApply().call();
293 		assertEquals(stashed, unstashed);
294 		assertEquals("content2", read(added));
295 		recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
296 
297 		Status status = git.status().call();
298 		assertTrue(status.getChanged().isEmpty());
299 		assertTrue(status.getConflicting().isEmpty());
300 		assertTrue(status.getMissing().isEmpty());
301 		assertTrue(status.getModified().isEmpty());
302 		assertTrue(status.getRemoved().isEmpty());
303 		assertTrue(status.getUntracked().isEmpty());
304 
305 		assertEquals(1, status.getAdded().size());
306 		assertTrue(status.getAdded().contains(path));
307 	}
308 
309 	@Test
310 	public void workingDirectoryDeleteIndexEdit() throws Exception {
311 		writeTrashFile(PATH, "content2");
312 		git.add().addFilepattern(PATH).call();
313 		FileUtils.delete(committedFile);
314 		assertFalse(committedFile.exists());
315 
316 		RevCommit stashed = git.stashCreate().call();
317 		assertNotNull(stashed);
318 		assertEquals("content", read(committedFile));
319 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
320 
321 		ObjectId unstashed = git.stashApply().call();
322 		assertEquals(stashed, unstashed);
323 		assertFalse(committedFile.exists());
324 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
325 
326 		Status status = git.status().call();
327 		assertTrue(status.getAdded().isEmpty());
328 		assertEquals(1, status.getChanged().size());
329 		assertTrue(status.getChanged().contains(PATH));
330 		assertTrue(status.getConflicting().isEmpty());
331 		assertEquals(1, status.getMissing().size());
332 		assertTrue(status.getMissing().contains(PATH));
333 		assertTrue(status.getModified().isEmpty());
334 		assertTrue(status.getUntracked().isEmpty());
335 
336 		assertTrue(status.getRemoved().isEmpty());
337 	}
338 
339 	@Test
340 	public void multipleEdits() throws Exception {
341 		String addedPath = "file2.txt";
342 		git.rm().addFilepattern(PATH).call();
343 		File addedFile = writeTrashFile(addedPath, "content2");
344 		git.add().addFilepattern(addedPath).call();
345 
346 		RevCommit stashed = git.stashCreate().call();
347 		assertNotNull(stashed);
348 		assertTrue(committedFile.exists());
349 		assertFalse(addedFile.exists());
350 		recorder.assertEvent(new String[] { PATH },
351 				new String[] { "file2.txt" });
352 
353 		ObjectId unstashed = git.stashApply().call();
354 		assertEquals(stashed, unstashed);
355 		recorder.assertEvent(new String[] { "file2.txt" },
356 				new String[] { PATH });
357 
358 		Status status = git.status().call();
359 		assertTrue(status.getChanged().isEmpty());
360 		assertTrue(status.getConflicting().isEmpty());
361 		assertTrue(status.getMissing().isEmpty());
362 		assertTrue(status.getModified().isEmpty());
363 		assertTrue(status.getUntracked().isEmpty());
364 
365 		assertEquals(1, status.getRemoved().size());
366 		assertTrue(status.getRemoved().contains(PATH));
367 		assertEquals(1, status.getAdded().size());
368 		assertTrue(status.getAdded().contains(addedPath));
369 	}
370 
371 	@Test
372 	public void workingDirectoryContentConflict() throws Exception {
373 		writeTrashFile(PATH, "content2");
374 
375 		RevCommit stashed = git.stashCreate().call();
376 		assertNotNull(stashed);
377 		assertEquals("content", read(committedFile));
378 		assertTrue(git.status().call().isClean());
379 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
380 
381 		writeTrashFile(PATH, "content3");
382 
383 		try {
384 			git.stashApply().call();
385 			fail("Exception not thrown");
386 		} catch (StashApplyFailureException e) {
387 			// expected
388  		}
389 		assertEquals("content3", read(PATH));
390 		recorder.assertNoEvent();
391 	}
392 
393 	@Test
394 	public void stashedContentMerge() throws Exception {
395 		writeTrashFile(PATH, "content\nmore content\n");
396 		git.add().addFilepattern(PATH).call();
397 		git.commit().setMessage("more content").call();
398 
399 		writeTrashFile(PATH, "content\nhead change\nmore content\n");
400 		git.add().addFilepattern(PATH).call();
401 		git.commit().setMessage("even content").call();
402 
403 		writeTrashFile(PATH, "content\nstashed change\nmore content\n");
404 
405 		RevCommit stashed = git.stashCreate().call();
406 		assertNotNull(stashed);
407 		assertEquals("content\nhead change\nmore content\n",
408 				read(committedFile));
409 		assertTrue(git.status().call().isClean());
410 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
411 
412 		writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
413 		git.add().addFilepattern(PATH).call();
414 		git.commit().setMessage("committed change").call();
415 		recorder.assertNoEvent();
416 
417 		try {
418 			git.stashApply().call();
419 			fail("Expected conflict");
420 		} catch (StashApplyFailureException e) {
421 			// expected
422 		}
423 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
424 		Status status = new StatusCommand(db).call();
425 		assertEquals(1, status.getConflicting().size());
426 		assertEquals(
427 				"content\n<<<<<<< HEAD\n=======\nstashed change\n>>>>>>> stash\nmore content\ncommitted change\n",
428 				read(PATH));
429 	}
430 
431 	@Test
432 	public void stashedContentMergeXtheirs() throws Exception {
433 		writeTrashFile(PATH, "content\nmore content\n");
434 		git.add().addFilepattern(PATH).call();
435 		git.commit().setMessage("more content").call();
436 
437 		writeTrashFile(PATH, "content\nhead change\nmore content\n");
438 		git.add().addFilepattern(PATH).call();
439 		git.commit().setMessage("even content").call();
440 
441 		writeTrashFile(PATH, "content\nstashed change\nmore content\n");
442 
443 		RevCommit stashed = git.stashCreate().call();
444 		assertNotNull(stashed);
445 		assertEquals("content\nhead change\nmore content\n",
446 				read(committedFile));
447 		assertTrue(git.status().call().isClean());
448 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
449 
450 		writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
451 		git.add().addFilepattern(PATH).call();
452 		git.commit().setMessage("committed change").call();
453 		recorder.assertNoEvent();
454 
455 		git.stashApply().setContentMergeStrategy(ContentMergeStrategy.THEIRS)
456 				.call();
457 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
458 		Status status = new StatusCommand(db).call();
459 		assertEquals('[' + PATH + ']', status.getModified().toString());
460 		assertEquals(
461 				"content\nstashed change\nmore content\ncommitted change\n",
462 				read(PATH));
463 	}
464 
465 	@Test
466 	public void stashedContentMergeXours() throws Exception {
467 		writeTrashFile(PATH, "content\nmore content\n");
468 		git.add().addFilepattern(PATH).call();
469 		git.commit().setMessage("more content").call();
470 
471 		writeTrashFile(PATH, "content\nhead change\nmore content\n");
472 		git.add().addFilepattern(PATH).call();
473 		git.commit().setMessage("even content").call();
474 
475 		writeTrashFile(PATH, "content\nstashed change\nmore content\n");
476 
477 		RevCommit stashed = git.stashCreate().call();
478 		assertNotNull(stashed);
479 		assertEquals("content\nhead change\nmore content\n",
480 				read(committedFile));
481 		assertTrue(git.status().call().isClean());
482 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
483 
484 		writeTrashFile(PATH,
485 				"content\nnew head\nmore content\ncommitted change\n");
486 		git.add().addFilepattern(PATH).call();
487 		git.commit().setMessage("committed change").call();
488 		recorder.assertNoEvent();
489 
490 		git.stashApply().setContentMergeStrategy(ContentMergeStrategy.OURS)
491 				.call();
492 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
493 		assertTrue(git.status().call().isClean());
494 		assertEquals("content\nnew head\nmore content\ncommitted change\n",
495 				read(PATH));
496 	}
497 
498 	@Test
499 	public void stashedContentMergeTheirs() throws Exception {
500 		writeTrashFile(PATH, "content\nmore content\n");
501 		git.add().addFilepattern(PATH).call();
502 		git.commit().setMessage("more content").call();
503 
504 		writeTrashFile(PATH, "content\nhead change\nmore content\n");
505 		git.add().addFilepattern(PATH).call();
506 		git.commit().setMessage("even content").call();
507 
508 		writeTrashFile(PATH, "content\nstashed change\nmore content\n");
509 
510 		RevCommit stashed = git.stashCreate().call();
511 		assertNotNull(stashed);
512 		assertEquals("content\nhead change\nmore content\n",
513 				read(committedFile));
514 		assertTrue(git.status().call().isClean());
515 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
516 
517 		writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
518 		git.add().addFilepattern(PATH).call();
519 		git.commit().setMessage("committed change").call();
520 		recorder.assertNoEvent();
521 
522 		git.stashApply().setStrategy(MergeStrategy.THEIRS).call();
523 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
524 		Status status = new StatusCommand(db).call();
525 		assertEquals('[' + PATH + ']', status.getModified().toString());
526 		assertEquals("content\nstashed change\nmore content\n", read(PATH));
527 	}
528 
529 	@Test
530 	public void stashedContentMergeOurs() throws Exception {
531 		writeTrashFile(PATH, "content\nmore content\n");
532 		git.add().addFilepattern(PATH).call();
533 		git.commit().setMessage("more content").call();
534 
535 		writeTrashFile(PATH, "content\nhead change\nmore content\n");
536 		git.add().addFilepattern(PATH).call();
537 		git.commit().setMessage("even content").call();
538 
539 		writeTrashFile(PATH, "content\nstashed change\nmore content\n");
540 
541 		RevCommit stashed = git.stashCreate().call();
542 		assertNotNull(stashed);
543 		assertEquals("content\nhead change\nmore content\n",
544 				read(committedFile));
545 		assertTrue(git.status().call().isClean());
546 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
547 
548 		writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
549 		git.add().addFilepattern(PATH).call();
550 		git.commit().setMessage("committed change").call();
551 		recorder.assertNoEvent();
552 
553 		// Doesn't make any sense... should be a no-op
554 		git.stashApply().setStrategy(MergeStrategy.OURS).call();
555 		recorder.assertNoEvent();
556 		assertTrue(git.status().call().isClean());
557 		assertEquals("content\nmore content\ncommitted change\n", read(PATH));
558 	}
559 
560 	@Test
561 	public void stashedApplyOnOtherBranch() throws Exception {
562 		writeTrashFile(PATH, "content\nmore content\n");
563 		git.add().addFilepattern(PATH).call();
564 		git.commit().setMessage("more content").call();
565 		String path2 = "file2.txt";
566 		File file2 = writeTrashFile(path2, "content\nmore content\n");
567 		git.add().addFilepattern(PATH).call();
568 		git.add().addFilepattern(path2).call();
569 		git.commit().setMessage("even content").call();
570 
571 		String otherBranch = "otherBranch";
572 		git.branchCreate().setName(otherBranch).call();
573 
574 		writeTrashFile(PATH, "master content");
575 		git.add().addFilepattern(PATH).call();
576 		git.commit().setMessage("even content").call();
577 		recorder.assertNoEvent();
578 
579 		git.checkout().setName(otherBranch).call();
580 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
581 
582 		writeTrashFile(PATH, "otherBranch content");
583 		git.add().addFilepattern(PATH).call();
584 		git.commit().setMessage("even more content").call();
585 		recorder.assertNoEvent();
586 
587 		writeTrashFile(path2, "content\nstashed change\nmore content\n");
588 
589 		RevCommit stashed = git.stashCreate().call();
590 
591 		assertNotNull(stashed);
592 		assertEquals("content\nmore content\n", read(file2));
593 		assertEquals("otherBranch content",
594 				read(committedFile));
595 		assertTrue(git.status().call().isClean());
596 		recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
597 
598 		git.checkout().setName("master").call();
599 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
600 		git.stashApply().call();
601 		assertEquals("content\nstashed change\nmore content\n", read(file2));
602 		assertEquals("master content",
603 				read(committedFile));
604 		recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
605 	}
606 
607 	@Test
608 	public void stashedApplyOnOtherBranchWithStagedChange() throws Exception {
609 		writeTrashFile(PATH, "content\nmore content\n");
610 		git.add().addFilepattern(PATH).call();
611 		git.commit().setMessage("more content").call();
612 		String path2 = "file2.txt";
613 		File file2 = writeTrashFile(path2, "content\nmore content\n");
614 		git.add().addFilepattern(PATH).call();
615 		git.add().addFilepattern(path2).call();
616 		git.commit().setMessage("even content").call();
617 
618 		String otherBranch = "otherBranch";
619 		git.branchCreate().setName(otherBranch).call();
620 
621 		writeTrashFile(PATH, "master content");
622 		git.add().addFilepattern(PATH).call();
623 		git.commit().setMessage("even content").call();
624 		recorder.assertNoEvent();
625 
626 		git.checkout().setName(otherBranch).call();
627 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
628 
629 		writeTrashFile(PATH, "otherBranch content");
630 		git.add().addFilepattern(PATH).call();
631 		git.commit().setMessage("even more content").call();
632 		recorder.assertNoEvent();
633 
634 		writeTrashFile(path2,
635 				"content\nstashed change in index\nmore content\n");
636 		git.add().addFilepattern(path2).call();
637 		writeTrashFile(path2, "content\nstashed change\nmore content\n");
638 
639 		RevCommit stashed = git.stashCreate().call();
640 
641 		assertNotNull(stashed);
642 		assertEquals("content\nmore content\n", read(file2));
643 		assertEquals("otherBranch content", read(committedFile));
644 		assertTrue(git.status().call().isClean());
645 		recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
646 
647 		git.checkout().setName("master").call();
648 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
649 		git.stashApply().call();
650 		assertEquals("content\nstashed change\nmore content\n", read(file2));
651 		assertEquals(
652 				"[file.txt, mode:100644, content:master content]"
653 						+ "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]",
654 				indexState(CONTENT));
655 		assertEquals("master content", read(committedFile));
656 		recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
657 	}
658 
659 	@Test
660 	public void workingDirectoryContentMerge() throws Exception {
661 		writeTrashFile(PATH, "content\nmore content\n");
662 		git.add().addFilepattern(PATH).call();
663 		git.commit().setMessage("more content").call();
664 		recorder.assertNoEvent();
665 
666 		writeTrashFile(PATH, "content\nstashed change\nmore content\n");
667 
668 		RevCommit stashed = git.stashCreate().call();
669 		assertNotNull(stashed);
670 		assertEquals("content\nmore content\n", read(committedFile));
671 		assertTrue(git.status().call().isClean());
672 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
673 
674 		writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
675 		git.add().addFilepattern(PATH).call();
676 		git.commit().setMessage("committed change").call();
677 		recorder.assertNoEvent();
678 
679 		git.stashApply().call();
680 		assertEquals(
681 				"content\nstashed change\nmore content\ncommitted change\n",
682 				read(committedFile));
683 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
684 	}
685 
686 	@Test
687 	public void indexContentConflict() throws Exception {
688 		writeTrashFile(PATH, "content2");
689 
690 		RevCommit stashed = git.stashCreate().call();
691 		assertNotNull(stashed);
692 		assertEquals("content", read(committedFile));
693 		assertTrue(git.status().call().isClean());
694 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
695 
696 		writeTrashFile(PATH, "content3");
697 		git.add().addFilepattern(PATH).call();
698 		writeTrashFile(PATH, "content2");
699 
700 		try {
701 			git.stashApply().call();
702 			fail("Exception not thrown");
703 		} catch (StashApplyFailureException e) {
704 			// expected
705 		}
706 		recorder.assertNoEvent();
707 		assertEquals("content2", read(PATH));
708 	}
709 
710 	@Test
711 	public void workingDirectoryEditPreCommit() throws Exception {
712 		writeTrashFile(PATH, "content2");
713 
714 		RevCommit stashed = git.stashCreate().call();
715 		assertNotNull(stashed);
716 		assertEquals("content", read(committedFile));
717 		assertTrue(git.status().call().isClean());
718 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
719 
720 		String path2 = "file2.txt";
721 		writeTrashFile(path2, "content3");
722 		git.add().addFilepattern(path2).call();
723 		assertNotNull(git.commit().setMessage("adding file").call());
724 
725 		ObjectId unstashed = git.stashApply().call();
726 		assertEquals(stashed, unstashed);
727 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
728 
729 		Status status = git.status().call();
730 		assertTrue(status.getAdded().isEmpty());
731 		assertTrue(status.getChanged().isEmpty());
732 		assertTrue(status.getConflicting().isEmpty());
733 		assertTrue(status.getMissing().isEmpty());
734 		assertTrue(status.getRemoved().isEmpty());
735 		assertTrue(status.getUntracked().isEmpty());
736 
737 		assertEquals(1, status.getModified().size());
738 		assertTrue(status.getModified().contains(PATH));
739 	}
740 
741 	@Test
742 	public void stashChangeInANewSubdirectory() throws Exception {
743 		String subdir = "subdir";
744 		String fname = "file2.txt";
745 		String path = subdir + "/" + fname;
746 		String otherBranch = "otherbranch";
747 
748 		writeTrashFile(subdir, fname, "content2");
749 
750 		git.add().addFilepattern(path).call();
751 		RevCommit stashed = git.stashCreate().call();
752 		assertNotNull(stashed);
753 		assertTrue(git.status().call().isClean());
754 		recorder.assertEvent(ChangeRecorder.EMPTY,
755 				new String[] { subdir, path });
756 
757 		git.branchCreate().setName(otherBranch).call();
758 		git.checkout().setName(otherBranch).call();
759 
760 		ObjectId unstashed = git.stashApply().call();
761 		assertEquals(stashed, unstashed);
762 		recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
763 
764 		Status status = git.status().call();
765 		assertTrue(status.getChanged().isEmpty());
766 		assertTrue(status.getConflicting().isEmpty());
767 		assertTrue(status.getMissing().isEmpty());
768 		assertTrue(status.getRemoved().isEmpty());
769 		assertTrue(status.getModified().isEmpty());
770 		assertTrue(status.getUntracked().isEmpty());
771 
772 		assertEquals(1, status.getAdded().size());
773 		assertTrue(status.getAdded().contains(path));
774 	}
775 
776 	@Test
777 	public void unstashNonStashCommit() throws Exception {
778 		try {
779 			git.stashApply().setStashRef(head.name()).call();
780 			fail("Exception not thrown");
781 		} catch (JGitInternalException e) {
782 			assertEquals(MessageFormat.format(
783 					JGitText.get().stashCommitIncorrectNumberOfParents,
784 					head.name(), "0"),
785 					e.getMessage());
786 		}
787 	}
788 
789 	@Test
790 	public void unstashNoHead() throws Exception {
791 		Repository repo = createWorkRepository();
792 		try {
793 			Git.wrap(repo).stashApply().call();
794 			fail("Exception not thrown");
795 		} catch (NoHeadException e) {
796 			assertNotNull(e.getMessage());
797 		}
798 	}
799 
800 	@Test
801 	public void noStashedCommits() throws Exception {
802 		try {
803 			git.stashApply().call();
804 			fail("Exception not thrown");
805 		} catch (InvalidRefNameException e) {
806 			assertNotNull(e.getMessage());
807 		}
808 	}
809 
810 	@Test
811 	public void testApplyStashWithDeletedFile() throws Exception {
812 		File file = writeTrashFile("file", "content");
813 		git.add().addFilepattern("file").call();
814 		git.commit().setMessage("x").call();
815 		file.delete();
816 		git.rm().addFilepattern("file").call();
817 		recorder.assertNoEvent();
818 		git.stashCreate().call();
819 		recorder.assertEvent(new String[] { "file" }, ChangeRecorder.EMPTY);
820 		file.delete();
821 
822 		git.stashApply().setStashRef("stash@{0}").call();
823 
824 		assertFalse(file.exists());
825 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file" });
826 	}
827 
828 	@Test
829 	public void untrackedFileNotIncluded() throws Exception {
830 		String untrackedPath = "untracked.txt";
831 		File untrackedFile = writeTrashFile(untrackedPath, "content");
832 		// at least one modification needed
833 		writeTrashFile(PATH, "content2");
834 		git.add().addFilepattern(PATH).call();
835 		git.stashCreate().call();
836 		assertTrue(untrackedFile.exists());
837 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
838 
839 		git.stashApply().setStashRef("stash@{0}").call();
840 		assertTrue(untrackedFile.exists());
841 		recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
842 
843 		Status status = git.status().call();
844 		assertEquals(1, status.getUntracked().size());
845 		assertTrue(status.getUntracked().contains(untrackedPath));
846 		assertEquals(1, status.getChanged().size());
847 		assertTrue(status.getChanged().contains(PATH));
848 		assertTrue(status.getAdded().isEmpty());
849 		assertTrue(status.getConflicting().isEmpty());
850 		assertTrue(status.getMissing().isEmpty());
851 		assertTrue(status.getRemoved().isEmpty());
852 		assertTrue(status.getModified().isEmpty());
853 	}
854 
855 	@Test
856 	public void untrackedFileIncluded() throws Exception {
857 		String path = "a/b/untracked.txt";
858 		File untrackedFile = writeTrashFile(path, "content");
859 		RevCommit stashedCommit = git.stashCreate().setIncludeUntracked(true)
860 				.call();
861 		assertNotNull(stashedCommit);
862 		assertFalse(untrackedFile.exists());
863 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
864 
865 		deleteTrashFile("a/b"); // checkout should create parent dirs
866 
867 		git.stashApply().setStashRef("stash@{0}").call();
868 		assertTrue(untrackedFile.exists());
869 		assertEquals("content", read(path));
870 		recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
871 
872 		Status status = git.status().call();
873 		assertEquals(1, status.getUntracked().size());
874 		assertTrue(status.getAdded().isEmpty());
875 		assertTrue(status.getChanged().isEmpty());
876 		assertTrue(status.getConflicting().isEmpty());
877 		assertTrue(status.getMissing().isEmpty());
878 		assertTrue(status.getRemoved().isEmpty());
879 		assertTrue(status.getModified().isEmpty());
880 		assertTrue(status.getUntracked().contains(path));
881 	}
882 
883 	@Test
884 	public void untrackedFileConflictsWithCommit() throws Exception {
885 		String path = "untracked.txt";
886 		writeTrashFile(path, "untracked");
887 		git.stashCreate().setIncludeUntracked(true).call();
888 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
889 
890 		writeTrashFile(path, "committed");
891 		head = git.commit().setMessage("add file").call();
892 		git.add().addFilepattern(path).call();
893 		git.commit().setMessage("conflicting commit").call();
894 
895 		try {
896 			git.stashApply().setStashRef("stash@{0}").call();
897 			fail("StashApplyFailureException should be thrown.");
898 		} catch (StashApplyFailureException e) {
899 			assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
900 		}
901 		assertEquals("committed", read(path));
902 		recorder.assertNoEvent();
903 	}
904 
905 	@Test
906 	public void untrackedFileConflictsWithWorkingDirectory()
907 			throws Exception {
908 		String path = "untracked.txt";
909 		writeTrashFile(path, "untracked");
910 		git.stashCreate().setIncludeUntracked(true).call();
911 		recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
912 
913 		writeTrashFile(path, "working-directory");
914 		try {
915 			git.stashApply().setStashRef("stash@{0}").call();
916 			fail("StashApplyFailureException should be thrown.");
917 		} catch (StashApplyFailureException e) {
918 			assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
919 		}
920 		assertEquals("working-directory", read(path));
921 		recorder.assertNoEvent();
922 	}
923 
924 	@Test
925 	public void untrackedAndTrackedChanges() throws Exception {
926 		writeTrashFile(PATH, "changed");
927 		String path = "untracked.txt";
928 		writeTrashFile(path, "untracked");
929 		git.stashCreate().setIncludeUntracked(true).call();
930 		assertTrue(PATH + " should exist", check(PATH));
931 		assertEquals(PATH + " should have been reset", "content", read(PATH));
932 		assertFalse(path + " should not exist", check(path));
933 		recorder.assertEvent(new String[] { PATH }, new String[] { path });
934 		git.stashApply().setStashRef("stash@{0}").call();
935 		assertTrue(PATH + " should exist", check(PATH));
936 		assertEquals(PATH + " should have new content", "changed", read(PATH));
937 		assertTrue(path + " should exist", check(path));
938 		assertEquals(path + " should have new content", "untracked",
939 				read(path));
940 		recorder.assertEvent(new String[] { PATH, path }, ChangeRecorder.EMPTY);
941 	}
942 }