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