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