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