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