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