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