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