1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.api;
44
45 import java.io.IOException;
46 import java.text.MessageFormat;
47 import java.util.LinkedList;
48 import java.util.List;
49 import java.util.Map;
50
51 import org.eclipse.jgit.api.MergeResult.MergeStatus;
52 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
53 import org.eclipse.jgit.api.errors.GitAPIException;
54 import org.eclipse.jgit.api.errors.JGitInternalException;
55 import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException;
56 import org.eclipse.jgit.api.errors.NoHeadException;
57 import org.eclipse.jgit.api.errors.NoMessageException;
58 import org.eclipse.jgit.api.errors.UnmergedPathsException;
59 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
60 import org.eclipse.jgit.dircache.DirCacheCheckout;
61 import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
62 import org.eclipse.jgit.internal.JGitText;
63 import org.eclipse.jgit.lib.AnyObjectId;
64 import org.eclipse.jgit.lib.Constants;
65 import org.eclipse.jgit.lib.NullProgressMonitor;
66 import org.eclipse.jgit.lib.ObjectId;
67 import org.eclipse.jgit.lib.ObjectIdRef;
68 import org.eclipse.jgit.lib.ProgressMonitor;
69 import org.eclipse.jgit.lib.Ref;
70 import org.eclipse.jgit.lib.Ref.Storage;
71 import org.eclipse.jgit.lib.Repository;
72 import org.eclipse.jgit.merge.MergeMessageFormatter;
73 import org.eclipse.jgit.merge.MergeStrategy;
74 import org.eclipse.jgit.merge.ResolveMerger;
75 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
76 import org.eclipse.jgit.revwalk.RevCommit;
77 import org.eclipse.jgit.revwalk.RevWalk;
78 import org.eclipse.jgit.treewalk.FileTreeIterator;
79
80
81
82
83
84
85
86
87
88
89
90 public class RevertCommand extends GitCommand<RevCommit> {
91 private List<Ref> commits = new LinkedList<>();
92
93 private String ourCommitName = null;
94
95 private List<Ref> revertedRefs = new LinkedList<>();
96
97 private MergeResult failingResult;
98
99 private List<String> unmergedPaths;
100
101 private MergeStrategy strategy = MergeStrategy.RECURSIVE;
102
103 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
104
105
106
107
108
109
110
111
112
113 protected RevertCommand(Repository repo) {
114 super(repo);
115 }
116
117
118
119
120
121
122
123
124
125 @Override
126 public RevCommit call() throws NoMessageException, UnmergedPathsException,
127 ConcurrentRefUpdateException, WrongRepositoryStateException,
128 GitAPIException {
129 RevCommit newHead = null;
130 checkCallable();
131
132 try (RevWalklk.html#RevWalk">RevWalk revWalk = new RevWalk(repo)) {
133
134
135 Ref headRef = repo.exactRef(Constants.HEAD);
136 if (headRef == null)
137 throw new NoHeadException(
138 JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
139 RevCommit headCommit = revWalk.parseCommit(headRef.getObjectId());
140
141 newHead = headCommit;
142
143
144 for (Ref src : commits) {
145
146
147 ObjectId srcObjectId = src.getPeeledObjectId();
148 if (srcObjectId == null)
149 srcObjectId = src.getObjectId();
150 RevCommit srcCommit = revWalk.parseCommit(srcObjectId);
151
152
153 if (srcCommit.getParentCount() != 1)
154 throw new MultipleParentsNotAllowedException(
155 MessageFormat.format(
156 JGitText.get().canOnlyRevertCommitsWithOneParent,
157 srcCommit.name(),
158 Integer.valueOf(srcCommit.getParentCount())));
159
160 RevCommit srcParent = srcCommit.getParent(0);
161 revWalk.parseHeaders(srcParent);
162
163 String ourName = calculateOurName(headRef);
164 String revertName = srcCommit.getId().abbreviate(7).name()
165 + " " + srcCommit.getShortMessage();
166
167 ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
168 merger.setWorkingTreeIterator(new FileTreeIterator(repo));
169 merger.setBase(srcCommit.getTree());
170 merger.setCommitNames(new String[] {
171 "BASE", ourName, revertName });
172
173 String shortMessage = "Revert \"" + srcCommit.getShortMessage()
174 + "\"";
175 String newMessage = shortMessage + "\n\n"
176 + "This reverts commit " + srcCommit.getId().getName()
177 + ".\n";
178 if (merger.merge(headCommit, srcParent)) {
179 if (!merger.getModifiedFiles().isEmpty()) {
180 repo.fireEvent(new WorkingTreeModifiedEvent(
181 merger.getModifiedFiles(), null));
182 }
183 if (AnyObjectId.isEqual(headCommit.getTree().getId(),
184 merger.getResultTreeId()))
185 continue;
186 DirCacheCheckout dco = new DirCacheCheckout(repo,
187 headCommit.getTree(), repo.lockDirCache(),
188 merger.getResultTreeId());
189 dco.setFailOnConflict(true);
190 dco.setProgressMonitor(monitor);
191 dco.checkout();
192 try (Gitit.html#Git">Git git = new Git(getRepository())) {
193 newHead = git.commit().setMessage(newMessage)
194 .setReflogComment("revert: " + shortMessage)
195 .call();
196 }
197 revertedRefs.add(src);
198 headCommit = newHead;
199 } else {
200 unmergedPaths = merger.getUnmergedPaths();
201 Map<String, MergeFailureReason> failingPaths = merger
202 .getFailingPaths();
203 if (failingPaths != null)
204 failingResult = new MergeResult(null,
205 merger.getBaseCommitId(),
206 new ObjectId[] { headCommit.getId(),
207 srcParent.getId() },
208 MergeStatus.FAILED, strategy,
209 merger.getMergeResults(), failingPaths, null);
210 else
211 failingResult = new MergeResult(null,
212 merger.getBaseCommitId(),
213 new ObjectId[] { headCommit.getId(),
214 srcParent.getId() },
215 MergeStatus.CONFLICTING, strategy,
216 merger.getMergeResults(), failingPaths, null);
217 if (!merger.failed() && !unmergedPaths.isEmpty()) {
218 String message = new MergeMessageFormatter()
219 .formatWithConflicts(newMessage,
220 merger.getUnmergedPaths());
221 repo.writeRevertHead(srcCommit.getId());
222 repo.writeMergeCommitMsg(message);
223 }
224 return null;
225 }
226 }
227 } catch (IOException e) {
228 throw new JGitInternalException(
229 MessageFormat.format(
230 JGitText.get().exceptionCaughtDuringExecutionOfRevertCommand,
231 e), e);
232 }
233 return newHead;
234 }
235
236
237
238
239
240
241
242
243 public RevertCommand include(Ref commit) {
244 checkCallable();
245 commits.add(commit);
246 return this;
247 }
248
249
250
251
252
253
254
255
256 public RevertCommand include(AnyObjectId commit) {
257 return include(commit.getName(), commit);
258 }
259
260
261
262
263
264
265
266
267
268
269 public RevertCommand include(String name, AnyObjectId commit) {
270 return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
271 commit.copy()));
272 }
273
274
275
276
277
278
279
280
281
282 public RevertCommand setOurCommitName(String ourCommitName) {
283 this.ourCommitName = ourCommitName;
284 return this;
285 }
286
287 private String calculateOurName(Ref headRef) {
288 if (ourCommitName != null)
289 return ourCommitName;
290
291 String targetRefName = headRef.getTarget().getName();
292 String headName = Repository.shortenRefName(targetRefName);
293 return headName;
294 }
295
296
297
298
299
300
301
302
303 public List<Ref> getRevertedRefs() {
304 return revertedRefs;
305 }
306
307
308
309
310
311
312
313 public MergeResult getFailingResult() {
314 return failingResult;
315 }
316
317
318
319
320
321
322 public List<String> getUnmergedPaths() {
323 return unmergedPaths;
324 }
325
326
327
328
329
330
331
332
333
334 public RevertCommand setStrategy(MergeStrategy strategy) {
335 this.strategy = strategy;
336 return this;
337 }
338
339
340
341
342
343
344
345
346
347
348
349 public RevertCommand setProgressMonitor(ProgressMonitor monitor) {
350 if (monitor == null) {
351 monitor = NullProgressMonitor.INSTANCE;
352 }
353 this.monitor = monitor;
354 return this;
355 }
356 }