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