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 try (Gitit.html#Git">Git git = new Git(getRepository())) {
146 newHead = git.commit()
147 .setMessage(srcCommit.getFullMessage())
148 .setReflogComment(reflogPrefix + " "
149 + srcCommit.getShortMessage())
150 .setAuthor(srcCommit.getAuthorIdent())
151 .setNoVerify(true).call();
152 }
153 }
154 cherryPickedRefs.add(src);
155 } else {
156 if (merger.failed()) {
157 return new CherryPickResult(merger.getFailingPaths());
158 }
159
160
161
162 String message = new MergeMessageFormatter()
163 .formatWithConflicts(srcCommit.getFullMessage(),
164 merger.getUnmergedPaths());
165
166 if (!noCommit) {
167 repo.writeCherryPickHead(srcCommit.getId());
168 }
169 repo.writeMergeCommitMsg(message);
170
171 repo.fireEvent(new WorkingTreeModifiedEvent(
172 merger.getModifiedFiles(), null));
173
174 return CherryPickResult.CONFLICT;
175 }
176 }
177 } catch (IOException e) {
178 throw new JGitInternalException(
179 MessageFormat.format(
180 JGitText.get().exceptionCaughtDuringExecutionOfCherryPickCommand,
181 e), e);
182 }
183 return new CherryPickResult(newHead, cherryPickedRefs);
184 }
185
186 private RevCommit../org/eclipse/jgit/revwalk/RevCommit.html#RevCommit">RevCommit getParentCommit(RevCommit srcCommit, RevWalk revWalk)
187 throws MultipleParentsNotAllowedException, MissingObjectException,
188 IOException {
189 final RevCommit srcParent;
190 if (mainlineParentNumber == null) {
191 if (srcCommit.getParentCount() != 1)
192 throw new MultipleParentsNotAllowedException(
193 MessageFormat.format(
194 JGitText.get().canOnlyCherryPickCommitsWithOneParent,
195 srcCommit.name(),
196 Integer.valueOf(srcCommit.getParentCount())));
197 srcParent = srcCommit.getParent(0);
198 } else {
199 if (mainlineParentNumber.intValue() > srcCommit.getParentCount()) {
200 throw new JGitInternalException(MessageFormat.format(
201 JGitText.get().commitDoesNotHaveGivenParent, srcCommit,
202 mainlineParentNumber));
203 }
204 srcParent = srcCommit
205 .getParent(mainlineParentNumber.intValue() - 1);
206 }
207
208 revWalk.parseHeaders(srcParent);
209 return srcParent;
210 }
211
212
213
214
215
216
217
218
219
220 public CherryPickCommand include(Ref commit) {
221 checkCallable();
222 commits.add(commit);
223 return this;
224 }
225
226
227
228
229
230
231
232
233 public CherryPickCommand include(AnyObjectId commit) {
234 return include(commit.getName(), commit);
235 }
236
237
238
239
240
241
242
243
244
245
246 public CherryPickCommand include(String name, AnyObjectId commit) {
247 return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
248 commit.copy()));
249 }
250
251
252
253
254
255
256
257
258
259 public CherryPickCommand setOurCommitName(String ourCommitName) {
260 this.ourCommitName = ourCommitName;
261 return this;
262 }
263
264
265
266
267
268
269
270
271
272
273
274
275 public CherryPickCommand setReflogPrefix(String prefix) {
276 this.reflogPrefix = prefix;
277 return this;
278 }
279
280
281
282
283
284
285
286
287
288 public CherryPickCommand setStrategy(MergeStrategy strategy) {
289 this.strategy = strategy;
290 return this;
291 }
292
293
294
295
296
297
298
299
300
301
302 public CherryPickCommand setMainlineParentNumber(int mainlineParentNumber) {
303 this.mainlineParentNumber = Integer.valueOf(mainlineParentNumber);
304 return this;
305 }
306
307
308
309
310
311
312
313
314
315
316
317
318
319 public CherryPickCommand setNoCommit(boolean noCommit) {
320 this.noCommit = noCommit;
321 return this;
322 }
323
324
325
326
327
328
329
330
331
332
333
334 public CherryPickCommand setProgressMonitor(ProgressMonitor monitor) {
335 if (monitor == null) {
336 monitor = NullProgressMonitor.INSTANCE;
337 }
338 this.monitor = monitor;
339 return this;
340 }
341
342 private String calculateOurName(Ref headRef) {
343 if (ourCommitName != null)
344 return ourCommitName;
345
346 String targetRefName = headRef.getTarget().getName();
347 String headName = Repository.shortenRefName(targetRefName);
348 return headName;
349 }
350
351
352 @SuppressWarnings("nls")
353 @Override
354 public String toString() {
355 return "CherryPickCommand [repo=" + repo + ",\ncommits=" + commits
356 + ",\nmainlineParentNumber=" + mainlineParentNumber
357 + ", noCommit=" + noCommit + ", ourCommitName=" + ourCommitName
358 + ", reflogPrefix=" + reflogPrefix + ", strategy=" + strategy
359 + "]";
360 }
361
362 }