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