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