View Javadoc
1   /*
2    * Copyright (C) 2011, Robin Rosenberg 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.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertFalse;
15  import static org.junit.Assert.assertNotNull;
16  import static org.junit.Assert.assertNull;
17  import static org.junit.Assert.assertTrue;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.util.Iterator;
22  
23  import org.eclipse.jgit.api.MergeResult.MergeStatus;
24  import org.eclipse.jgit.api.ResetCommand.ResetType;
25  import org.eclipse.jgit.api.errors.GitAPIException;
26  import org.eclipse.jgit.api.errors.JGitInternalException;
27  import org.eclipse.jgit.dircache.DirCache;
28  import org.eclipse.jgit.junit.RepositoryTestCase;
29  import org.eclipse.jgit.lib.ConfigConstants;
30  import org.eclipse.jgit.lib.Constants;
31  import org.eclipse.jgit.lib.FileMode;
32  import org.eclipse.jgit.lib.ReflogReader;
33  import org.eclipse.jgit.lib.RepositoryState;
34  import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
35  import org.eclipse.jgit.revwalk.RevCommit;
36  import org.junit.Test;
37  
38  /**
39   * Test revert command
40   */
41  public class RevertCommandTest extends RepositoryTestCase {
42  	@Test
43  	public void testRevert() throws IOException, JGitInternalException,
44  			GitAPIException {
45  		try (Git git = new Git(db)) {
46  			writeTrashFile("a", "first line\nsec. line\nthird line\n");
47  			git.add().addFilepattern("a").call();
48  			git.commit().setMessage("create a").call();
49  
50  			writeTrashFile("b", "content\n");
51  			git.add().addFilepattern("b").call();
52  			git.commit().setMessage("create b").call();
53  
54  			writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
55  			git.add().addFilepattern("a").call();
56  			git.commit().setMessage("enlarged a").call();
57  
58  			writeTrashFile("a",
59  					"first line\nsecond line\nthird line\nfourth line\n");
60  			git.add().addFilepattern("a").call();
61  			RevCommit fixingA = git.commit().setMessage("fixed a").call();
62  
63  			writeTrashFile("b", "first line\n");
64  			git.add().addFilepattern("b").call();
65  			git.commit().setMessage("fixed b").call();
66  
67  			git.revert().include(fixingA).call();
68  
69  			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
70  
71  			assertTrue(new File(db.getWorkTree(), "b").exists());
72  			checkFile(new File(db.getWorkTree(), "a"),
73  					"first line\nsec. line\nthird line\nfourth line\n");
74  			Iterator<RevCommit> history = git.log().call().iterator();
75  			RevCommit revertCommit = history.next();
76  			String expectedMessage = "Revert \"fixed a\"\n\n"
77  					+ "This reverts commit " + fixingA.getId().getName() + ".\n";
78  			assertEquals(expectedMessage, revertCommit.getFullMessage());
79  			assertEquals("fixed b", history.next().getFullMessage());
80  			assertEquals("fixed a", history.next().getFullMessage());
81  			assertEquals("enlarged a", history.next().getFullMessage());
82  			assertEquals("create b", history.next().getFullMessage());
83  			assertEquals("create a", history.next().getFullMessage());
84  			assertFalse(history.hasNext());
85  
86  			ReflogReader reader = db.getReflogReader(Constants.HEAD);
87  			assertTrue(reader.getLastEntry().getComment()
88  					.startsWith("revert: Revert \""));
89  			reader = db.getReflogReader(db.getBranch());
90  			assertTrue(reader.getLastEntry().getComment()
91  					.startsWith("revert: Revert \""));
92  		}
93  
94  	}
95  
96  	@Test
97  	public void testRevertMultiple() throws IOException, JGitInternalException,
98  			GitAPIException {
99  		try (Git git = new Git(db)) {
100 			writeTrashFile("a", "first\n");
101 			git.add().addFilepattern("a").call();
102 			git.commit().setMessage("add first").call();
103 
104 			writeTrashFile("a", "first\nsecond\n");
105 			git.add().addFilepattern("a").call();
106 			RevCommit secondCommit = git.commit().setMessage("add second").call();
107 
108 			writeTrashFile("a", "first\nsecond\nthird\n");
109 			git.add().addFilepattern("a").call();
110 			RevCommit thirdCommit = git.commit().setMessage("add third").call();
111 
112 			git.revert().include(thirdCommit).include(secondCommit).call();
113 
114 			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
115 
116 			checkFile(new File(db.getWorkTree(), "a"), "first\n");
117 			Iterator<RevCommit> history = git.log().call().iterator();
118 			RevCommit revertCommit = history.next();
119 			String expectedMessage = "Revert \"add second\"\n\n"
120 					+ "This reverts commit "
121 					+ secondCommit.getId().getName() + ".\n";
122 			assertEquals(expectedMessage, revertCommit.getFullMessage());
123 			revertCommit = history.next();
124 			expectedMessage = "Revert \"add third\"\n\n"
125 					+ "This reverts commit " + thirdCommit.getId().getName()
126 					+ ".\n";
127 			assertEquals(expectedMessage, revertCommit.getFullMessage());
128 			assertEquals("add third", history.next().getFullMessage());
129 			assertEquals("add second", history.next().getFullMessage());
130 			assertEquals("add first", history.next().getFullMessage());
131 			assertFalse(history.hasNext());
132 
133 			ReflogReader reader = db.getReflogReader(Constants.HEAD);
134 			assertTrue(reader.getLastEntry().getComment()
135 					.startsWith("revert: Revert \""));
136 			reader = db.getReflogReader(db.getBranch());
137 			assertTrue(reader.getLastEntry().getComment()
138 					.startsWith("revert: Revert \""));
139 		}
140 
141 	}
142 
143 	@Test
144 	public void testRevertMultipleWithFail() throws IOException,
145 			JGitInternalException, GitAPIException {
146 		try (Git git = new Git(db)) {
147 			writeTrashFile("a", "first\n");
148 			git.add().addFilepattern("a").call();
149 			git.commit().setMessage("add first").call();
150 
151 			writeTrashFile("a", "first\nsecond\n");
152 			git.add().addFilepattern("a").call();
153 			RevCommit secondCommit = git.commit().setMessage("add second").call();
154 
155 			writeTrashFile("a", "first\nsecond\nthird\n");
156 			git.add().addFilepattern("a").call();
157 			git.commit().setMessage("add third").call();
158 
159 			writeTrashFile("a", "first\nsecond\nthird\nfourth\n");
160 			git.add().addFilepattern("a").call();
161 			RevCommit fourthCommit = git.commit().setMessage("add fourth").call();
162 
163 			git.revert().include(fourthCommit).include(secondCommit).call();
164 
165 			// not SAFE because it failed
166 			assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
167 
168 			checkFile(new File(db.getWorkTree(), "a"), "first\n"
169 					+ "<<<<<<< master\n" + "second\n" + "third\n" + "=======\n"
170 					+ ">>>>>>> "
171 					+ secondCommit.getId()
172 							.abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name()
173 					+ " add second\n");
174 			Iterator<RevCommit> history = git.log().call().iterator();
175 			RevCommit revertCommit = history.next();
176 			String expectedMessage = "Revert \"add fourth\"\n\n"
177 					+ "This reverts commit " + fourthCommit.getId().getName()
178 					+ ".\n";
179 			assertEquals(expectedMessage, revertCommit.getFullMessage());
180 			assertEquals("add fourth", history.next().getFullMessage());
181 			assertEquals("add third", history.next().getFullMessage());
182 			assertEquals("add second", history.next().getFullMessage());
183 			assertEquals("add first", history.next().getFullMessage());
184 			assertFalse(history.hasNext());
185 
186 			ReflogReader reader = db.getReflogReader(Constants.HEAD);
187 			assertTrue(reader.getLastEntry().getComment()
188 					.startsWith("revert: Revert \""));
189 			reader = db.getReflogReader(db.getBranch());
190 			assertTrue(reader.getLastEntry().getComment()
191 					.startsWith("revert: Revert \""));
192 		}
193 
194 	}
195 
196 	@Test
197 	public void testRevertDirtyIndex() throws Exception {
198 		try (Git git = new Git(db)) {
199 			RevCommit sideCommit = prepareRevert(git);
200 
201 			// modify and add file a
202 			writeTrashFile("a", "a(modified)");
203 			git.add().addFilepattern("a").call();
204 			// do not commit
205 
206 			doRevertAndCheckResult(git, sideCommit,
207 					MergeFailureReason.DIRTY_INDEX);
208 		}
209 }
210 
211 	@Test
212 	public void testRevertDirtyWorktree() throws Exception {
213 		try (Git git = new Git(db)) {
214 			RevCommit sideCommit = prepareRevert(git);
215 
216 			// modify file a
217 			writeTrashFile("a", "a(modified)");
218 			// do not add and commit
219 
220 			doRevertAndCheckResult(git, sideCommit,
221 					MergeFailureReason.DIRTY_WORKTREE);
222 		}
223 	}
224 
225 	@Test
226 	public void testRevertConflictResolution() throws Exception {
227 		try (Git git = new Git(db)) {
228 			RevCommit sideCommit = prepareRevert(git);
229 
230 			RevertCommand revert = git.revert();
231 			RevCommit newHead = revert.include(sideCommit.getId()).call();
232 			assertNull(newHead);
233 			MergeResult result = revert.getFailingResult();
234 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
235 			assertTrue(new File(db.getDirectory(), Constants.MERGE_MSG).exists());
236 			assertEquals("Revert \"" + sideCommit.getShortMessage()
237 					+ "\"\n\nThis reverts commit " + sideCommit.getId().getName()
238 					+ ".\n\n# Conflicts:\n#\ta\n",
239 					db.readMergeCommitMsg());
240 			assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD)
241 					.exists());
242 			assertEquals(sideCommit.getId(), db.readRevertHead());
243 			assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
244 
245 			// Resolve
246 			writeTrashFile("a", "a");
247 			git.add().addFilepattern("a").call();
248 
249 			assertEquals(RepositoryState.REVERTING_RESOLVED,
250 					db.getRepositoryState());
251 
252 			git.commit().setOnly("a").setMessage("resolve").call();
253 
254 			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
255 		}
256 	}
257 
258 	@Test
259 	public void testRevertConflictReset() throws Exception {
260 		try (Git git = new Git(db)) {
261 			RevCommit sideCommit = prepareRevert(git);
262 
263 			RevertCommand revert = git.revert();
264 			RevCommit newHead = revert.include(sideCommit.getId()).call();
265 			assertNull(newHead);
266 			MergeResult result = revert.getFailingResult();
267 
268 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
269 			assertEquals(RepositoryState.REVERTING, db.getRepositoryState());
270 			assertTrue(new File(db.getDirectory(), Constants.REVERT_HEAD)
271 					.exists());
272 
273 			git.reset().setMode(ResetType.MIXED).setRef("HEAD").call();
274 
275 			assertEquals(RepositoryState.SAFE, db.getRepositoryState());
276 			assertFalse(new File(db.getDirectory(), Constants.REVERT_HEAD)
277 					.exists());
278 		}
279 	}
280 
281 	@Test
282 	public void testRevertOverExecutableChangeOnNonExectuableFileSystem()
283 			throws Exception {
284 		try (Git git = new Git(db)) {
285 			File file = writeTrashFile("test.txt", "a");
286 			assertNotNull(git.add().addFilepattern("test.txt").call());
287 			assertNotNull(git.commit().setMessage("commit1").call());
288 
289 			assertNotNull(git.checkout().setCreateBranch(true).setName("a").call());
290 
291 			writeTrashFile("test.txt", "b");
292 			assertNotNull(git.add().addFilepattern("test.txt").call());
293 			RevCommit commit2 = git.commit().setMessage("commit2").call();
294 			assertNotNull(commit2);
295 
296 			assertNotNull(git.checkout().setName(Constants.MASTER).call());
297 
298 			DirCache cache = db.lockDirCache();
299 			cache.getEntry("test.txt").setFileMode(FileMode.EXECUTABLE_FILE);
300 			cache.write();
301 			assertTrue(cache.commit());
302 			cache.unlock();
303 
304 			assertNotNull(git.commit().setMessage("commit3").call());
305 
306 			db.getFS().setExecute(file, false);
307 			git.getRepository()
308 					.getConfig()
309 					.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
310 							ConfigConstants.CONFIG_KEY_FILEMODE, false);
311 
312 			RevertCommand revert = git.revert();
313 			RevCommit newHead = revert.include(commit2).call();
314 			assertNotNull(newHead);
315 		}
316 	}
317 
318 	@Test
319 	public void testRevertConflictMarkers() throws Exception {
320 		try (Git git = new Git(db)) {
321 			RevCommit sideCommit = prepareRevert(git);
322 
323 			RevertCommand revert = git.revert();
324 			RevCommit newHead = revert.include(sideCommit.getId())
325 					.call();
326 			assertNull(newHead);
327 			MergeResult result = revert.getFailingResult();
328 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
329 
330 			String expected = "<<<<<<< master\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n";
331 			checkFile(new File(db.getWorkTree(), "a"), expected);
332 		}
333 	}
334 
335 	@Test
336 	public void testRevertOurCommitName() throws Exception {
337 		try (Git git = new Git(db)) {
338 			RevCommit sideCommit = prepareRevert(git);
339 
340 			RevertCommand revert = git.revert();
341 			RevCommit newHead = revert.include(sideCommit.getId())
342 					.setOurCommitName("custom name").call();
343 			assertNull(newHead);
344 			MergeResult result = revert.getFailingResult();
345 			assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
346 
347 			String expected = "<<<<<<< custom name\na(latest)\n=======\na\n>>>>>>> ca96c31 second master\n";
348 			checkFile(new File(db.getWorkTree(), "a"), expected);
349 		}
350 	}
351 
352 	private RevCommit prepareRevert(Git git) throws Exception {
353 		// create, add and commit file a
354 		writeTrashFile("a", "a");
355 		git.add().addFilepattern("a").call();
356 		git.commit().setMessage("first master").call();
357 
358 		// First commit
359 		checkoutBranch("refs/heads/master");
360 		// modify, add and commit file a
361 		writeTrashFile("a", "a(previous)");
362 		git.add().addFilepattern("a").call();
363 		RevCommit oldCommit = git.commit().setMessage("second master").call();
364 
365 		// modify, add and commit file a
366 		writeTrashFile("a", "a(latest)");
367 		git.add().addFilepattern("a").call();
368 		git.commit().setMessage("side").call();
369 
370 		return oldCommit;
371 	}
372 
373 	private void doRevertAndCheckResult(final Git git,
374 			final RevCommit sideCommit, final MergeFailureReason reason)
375 			throws Exception {
376 		// get current index state
377 		String indexState = indexState(CONTENT);
378 
379 		// revert
380 		RevertCommand revert = git.revert();
381 		RevCommit resultCommit = revert.include(sideCommit.getId()).call();
382 		assertNull(resultCommit);
383 		MergeResult result = revert.getFailingResult();
384 		assertEquals(MergeStatus.FAILED, result.getMergeStatus());
385 		// staged file a causes DIRTY_INDEX
386 		assertEquals(1, result.getFailingPaths().size());
387 		assertEquals(reason, result.getFailingPaths().get("a"));
388 		assertEquals("a(modified)", read(new File(db.getWorkTree(), "a")));
389 		// index shall be unchanged
390 		assertEquals(indexState, indexState(CONTENT));
391 		assertEquals(RepositoryState.SAFE, db.getRepositoryState());
392 
393 		if (reason == null) {
394 			ReflogReader reader = db.getReflogReader(Constants.HEAD);
395 			assertTrue(reader.getLastEntry().getComment()
396 					.startsWith("revert: "));
397 			reader = db.getReflogReader(db.getBranch());
398 			assertTrue(reader.getLastEntry().getComment()
399 					.startsWith("revert: "));
400 		}
401 	}
402 }