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.errors.ConcurrentRefUpdateException;
19 import org.eclipse.jgit.api.errors.GitAPIException;
20 import org.eclipse.jgit.api.errors.JGitInternalException;
21 import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException;
22 import org.eclipse.jgit.api.errors.NoHeadException;
23 import org.eclipse.jgit.api.errors.NoMessageException;
24 import org.eclipse.jgit.api.errors.UnmergedPathsException;
25 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
26 import org.eclipse.jgit.dircache.DirCacheCheckout;
27 import org.eclipse.jgit.errors.MissingObjectException;
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.ContentMergeStrategy;
40 import org.eclipse.jgit.merge.MergeMessageFormatter;
41 import org.eclipse.jgit.merge.MergeStrategy;
42 import org.eclipse.jgit.merge.Merger;
43 import org.eclipse.jgit.merge.ResolveMerger;
44 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
45 import org.eclipse.jgit.revwalk.RevCommit;
46 import org.eclipse.jgit.revwalk.RevWalk;
47 import org.eclipse.jgit.treewalk.FileTreeIterator;
48
49
50
51
52
53
54
55
56
57
58
59 public class CherryPickCommand extends GitCommand<CherryPickResult> {
60 private String reflogPrefix = "cherry-pick:";
61
62 private List<Ref> commits = new LinkedList<>();
63
64 private String ourCommitName = null;
65
66 private MergeStrategy strategy = MergeStrategy.RECURSIVE;
67
68 private ContentMergeStrategy contentStrategy;
69
70 private Integer mainlineParentNumber;
71
72 private boolean noCommit = false;
73
74 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
75
76
77
78
79
80
81
82 protected CherryPickCommand(Repository repo) {
83 super(repo);
84 }
85
86
87
88
89
90
91
92
93
94 @Override
95 public CherryPickResult call() throws GitAPIException, NoMessageException,
96 UnmergedPathsException, ConcurrentRefUpdateException,
97 WrongRepositoryStateException, NoHeadException {
98 RevCommit newHead = null;
99 List<Ref> cherryPickedRefs = new LinkedList<>();
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 }
110
111 newHead = revWalk.parseCommit(headRef.getObjectId());
112
113
114 for (Ref src : commits) {
115
116
117 ObjectId srcObjectId = src.getPeeledObjectId();
118 if (srcObjectId == null) {
119 srcObjectId = src.getObjectId();
120 }
121 RevCommit srcCommit = revWalk.parseCommit(srcObjectId);
122
123
124 final RevCommit srcParent = getParentCommit(srcCommit, revWalk);
125
126 String ourName = calculateOurName(headRef);
127 String cherryPickName = srcCommit.getId().abbreviate(7).name()
128 + " " + srcCommit.getShortMessage();
129
130 Merger merger = strategy.newMerger(repo);
131 merger.setProgressMonitor(monitor);
132 boolean noProblems;
133 Map<String, MergeFailureReason> failingPaths = null;
134 List<String> unmergedPaths = null;
135 if (merger instanceof ResolveMerger) {
136 ResolveMerger resolveMerger = (ResolveMerger) merger;
137 resolveMerger.setContentMergeStrategy(contentStrategy);
138 resolveMerger.setCommitNames(
139 new String[] { "BASE", ourName, cherryPickName });
140 resolveMerger
141 .setWorkingTreeIterator(new FileTreeIterator(repo));
142 resolveMerger.setBase(srcParent.getTree());
143 noProblems = merger.merge(newHead, srcCommit);
144 failingPaths = resolveMerger.getFailingPaths();
145 unmergedPaths = resolveMerger.getUnmergedPaths();
146 if (!resolveMerger.getModifiedFiles().isEmpty()) {
147 repo.fireEvent(new WorkingTreeModifiedEvent(
148 resolveMerger.getModifiedFiles(), null));
149 }
150 } else {
151 noProblems = merger.merge(newHead, srcCommit);
152 }
153 if (noProblems) {
154 if (AnyObjectId.isEqual(newHead.getTree().getId(),
155 merger.getResultTreeId())) {
156 continue;
157 }
158 DirCacheCheckout dco = new DirCacheCheckout(repo,
159 newHead.getTree(), repo.lockDirCache(),
160 merger.getResultTreeId());
161 dco.setFailOnConflict(true);
162 dco.setProgressMonitor(monitor);
163 dco.checkout();
164 if (!noCommit) {
165 try (Git git = new Git(getRepository())) {
166 newHead = git.commit()
167 .setMessage(srcCommit.getFullMessage())
168 .setReflogComment(reflogPrefix + " "
169 + srcCommit.getShortMessage())
170 .setAuthor(srcCommit.getAuthorIdent())
171 .setNoVerify(true).call();
172 }
173 }
174 cherryPickedRefs.add(src);
175 } else {
176 if (failingPaths != null && !failingPaths.isEmpty()) {
177 return new CherryPickResult(failingPaths);
178 }
179
180
181
182 String message;
183 if (unmergedPaths != null) {
184 message = new MergeMessageFormatter()
185 .formatWithConflicts(srcCommit.getFullMessage(),
186 unmergedPaths);
187 } else {
188 message = srcCommit.getFullMessage();
189 }
190
191 if (!noCommit) {
192 repo.writeCherryPickHead(srcCommit.getId());
193 }
194 repo.writeMergeCommitMsg(message);
195
196 return CherryPickResult.CONFLICT;
197 }
198 }
199 } catch (IOException e) {
200 throw new JGitInternalException(
201 MessageFormat.format(
202 JGitText.get().exceptionCaughtDuringExecutionOfCherryPickCommand,
203 e), e);
204 }
205 return new CherryPickResult(newHead, cherryPickedRefs);
206 }
207
208 private RevCommit getParentCommit(RevCommit srcCommit, RevWalk revWalk)
209 throws MultipleParentsNotAllowedException, MissingObjectException,
210 IOException {
211 final RevCommit srcParent;
212 if (mainlineParentNumber == null) {
213 if (srcCommit.getParentCount() != 1)
214 throw new MultipleParentsNotAllowedException(
215 MessageFormat.format(
216 JGitText.get().canOnlyCherryPickCommitsWithOneParent,
217 srcCommit.name(),
218 Integer.valueOf(srcCommit.getParentCount())));
219 srcParent = srcCommit.getParent(0);
220 } else {
221 if (mainlineParentNumber.intValue() > srcCommit.getParentCount()) {
222 throw new JGitInternalException(MessageFormat.format(
223 JGitText.get().commitDoesNotHaveGivenParent, srcCommit,
224 mainlineParentNumber));
225 }
226 srcParent = srcCommit
227 .getParent(mainlineParentNumber.intValue() - 1);
228 }
229
230 revWalk.parseHeaders(srcParent);
231 return srcParent;
232 }
233
234
235
236
237
238
239
240
241
242 public CherryPickCommand include(Ref commit) {
243 checkCallable();
244 commits.add(commit);
245 return this;
246 }
247
248
249
250
251
252
253
254
255 public CherryPickCommand include(AnyObjectId commit) {
256 return include(commit.getName(), commit);
257 }
258
259
260
261
262
263
264
265
266
267
268 public CherryPickCommand include(String name, AnyObjectId commit) {
269 return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
270 commit.copy()));
271 }
272
273
274
275
276
277
278
279
280
281 public CherryPickCommand setOurCommitName(String ourCommitName) {
282 this.ourCommitName = ourCommitName;
283 return this;
284 }
285
286
287
288
289
290
291
292
293
294
295
296
297 public CherryPickCommand setReflogPrefix(String prefix) {
298 this.reflogPrefix = prefix;
299 return this;
300 }
301
302
303
304
305
306
307
308
309
310 public CherryPickCommand setStrategy(MergeStrategy strategy) {
311 this.strategy = strategy;
312 return this;
313 }
314
315
316
317
318
319
320
321
322
323
324
325 public CherryPickCommand setContentMergeStrategy(
326 ContentMergeStrategy strategy) {
327 this.contentStrategy = strategy;
328 return this;
329 }
330
331
332
333
334
335
336
337
338
339
340 public CherryPickCommand setMainlineParentNumber(int mainlineParentNumber) {
341 this.mainlineParentNumber = Integer.valueOf(mainlineParentNumber);
342 return this;
343 }
344
345
346
347
348
349
350
351
352
353
354
355
356
357 public CherryPickCommand setNoCommit(boolean noCommit) {
358 this.noCommit = noCommit;
359 return this;
360 }
361
362
363
364
365
366
367
368
369
370
371
372 public CherryPickCommand setProgressMonitor(ProgressMonitor monitor) {
373 if (monitor == null) {
374 monitor = NullProgressMonitor.INSTANCE;
375 }
376 this.monitor = monitor;
377 return this;
378 }
379
380 private String calculateOurName(Ref headRef) {
381 if (ourCommitName != null)
382 return ourCommitName;
383
384 String targetRefName = headRef.getTarget().getName();
385 String headName = Repository.shortenRefName(targetRefName);
386 return headName;
387 }
388
389
390 @SuppressWarnings("nls")
391 @Override
392 public String toString() {
393 return "CherryPickCommand [repo=" + repo + ",\ncommits=" + commits
394 + ",\nmainlineParentNumber=" + mainlineParentNumber
395 + ", noCommit=" + noCommit + ", ourCommitName=" + ourCommitName
396 + ", reflogPrefix=" + reflogPrefix + ", strategy=" + strategy
397 + "]";
398 }
399
400 }