1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.api;
12
13 import static java.nio.charset.StandardCharsets.UTF_8;
14
15 import java.io.ByteArrayOutputStream;
16 import java.io.File;
17 import java.io.FileNotFoundException;
18 import java.io.FileOutputStream;
19 import java.io.IOException;
20 import java.text.MessageFormat;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import org.eclipse.jgit.api.RebaseResult.Status;
33 import org.eclipse.jgit.api.ResetCommand.ResetType;
34 import org.eclipse.jgit.api.errors.CheckoutConflictException;
35 import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
36 import org.eclipse.jgit.api.errors.GitAPIException;
37 import org.eclipse.jgit.api.errors.InvalidRebaseStepException;
38 import org.eclipse.jgit.api.errors.InvalidRefNameException;
39 import org.eclipse.jgit.api.errors.JGitInternalException;
40 import org.eclipse.jgit.api.errors.NoHeadException;
41 import org.eclipse.jgit.api.errors.NoMessageException;
42 import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
43 import org.eclipse.jgit.api.errors.RefNotFoundException;
44 import org.eclipse.jgit.api.errors.StashApplyFailureException;
45 import org.eclipse.jgit.api.errors.UnmergedPathsException;
46 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
47 import org.eclipse.jgit.diff.DiffFormatter;
48 import org.eclipse.jgit.dircache.DirCache;
49 import org.eclipse.jgit.dircache.DirCacheCheckout;
50 import org.eclipse.jgit.dircache.DirCacheIterator;
51 import org.eclipse.jgit.errors.RevisionSyntaxException;
52 import org.eclipse.jgit.internal.JGitText;
53 import org.eclipse.jgit.lib.AbbreviatedObjectId;
54 import org.eclipse.jgit.lib.AnyObjectId;
55 import org.eclipse.jgit.lib.ConfigConstants;
56 import org.eclipse.jgit.lib.Constants;
57 import org.eclipse.jgit.lib.NullProgressMonitor;
58 import org.eclipse.jgit.lib.ObjectId;
59 import org.eclipse.jgit.lib.ObjectReader;
60 import org.eclipse.jgit.lib.PersonIdent;
61 import org.eclipse.jgit.lib.ProgressMonitor;
62 import org.eclipse.jgit.lib.RebaseTodoLine;
63 import org.eclipse.jgit.lib.RebaseTodoLine.Action;
64 import org.eclipse.jgit.lib.Ref;
65 import org.eclipse.jgit.lib.RefUpdate;
66 import org.eclipse.jgit.lib.RefUpdate.Result;
67 import org.eclipse.jgit.lib.Repository;
68 import org.eclipse.jgit.merge.ContentMergeStrategy;
69 import org.eclipse.jgit.merge.MergeStrategy;
70 import org.eclipse.jgit.revwalk.RevCommit;
71 import org.eclipse.jgit.revwalk.RevSort;
72 import org.eclipse.jgit.revwalk.RevWalk;
73 import org.eclipse.jgit.revwalk.filter.RevFilter;
74 import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
75 import org.eclipse.jgit.treewalk.TreeWalk;
76 import org.eclipse.jgit.treewalk.filter.TreeFilter;
77 import org.eclipse.jgit.util.FileUtils;
78 import org.eclipse.jgit.util.IO;
79 import org.eclipse.jgit.util.RawParseUtils;
80
81
82
83
84
85
86
87
88
89
90
91
92 public class RebaseCommand extends GitCommand<RebaseResult> {
93
94
95
96 public static final String REBASE_MERGE = "rebase-merge";
97
98
99
100
101 private static final String REBASE_APPLY = "rebase-apply";
102
103
104
105
106 public static final String STOPPED_SHA = "stopped-sha";
107
108 private static final String AUTHOR_SCRIPT = "author-script";
109
110 private static final String DONE = "done";
111
112 private static final String GIT_AUTHOR_DATE = "GIT_AUTHOR_DATE";
113
114 private static final String GIT_AUTHOR_EMAIL = "GIT_AUTHOR_EMAIL";
115
116 private static final String GIT_AUTHOR_NAME = "GIT_AUTHOR_NAME";
117
118 private static final String GIT_REBASE_TODO = "git-rebase-todo";
119
120 private static final String HEAD_NAME = "head-name";
121
122 private static final String INTERACTIVE = "interactive";
123
124 private static final String QUIET = "quiet";
125
126 private static final String MESSAGE = "message";
127
128 private static final String ONTO = "onto";
129
130 private static final String ONTO_NAME = "onto_name";
131
132 private static final String PATCH = "patch";
133
134 private static final String REBASE_HEAD = "orig-head";
135
136
137 private static final String REBASE_HEAD_LEGACY = "head";
138
139 private static final String AMEND = "amend";
140
141 private static final String MESSAGE_FIXUP = "message-fixup";
142
143 private static final String MESSAGE_SQUASH = "message-squash";
144
145 private static final String AUTOSTASH = "autostash";
146
147 private static final String AUTOSTASH_MSG = "On {0}: autostash";
148
149
150
151
152
153
154
155
156
157 private static final String REWRITTEN = "rewritten";
158
159
160
161
162
163 private static final String CURRENT_COMMIT = "current-commit";
164
165 private static final String REFLOG_PREFIX = "rebase:";
166
167
168
169
170 public enum Operation {
171
172
173
174 BEGIN,
175
176
177
178 CONTINUE,
179
180
181
182 SKIP,
183
184
185
186 ABORT,
187
188
189
190
191 PROCESS_STEPS;
192 }
193
194 private Operation operation = Operation.BEGIN;
195
196 private RevCommit upstreamCommit;
197
198 private String upstreamCommitName;
199
200 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
201
202 private final RevWalk walk;
203
204 private final RebaseState rebaseState;
205
206 private InteractiveHandler interactiveHandler;
207
208 private boolean stopAfterInitialization = false;
209
210 private RevCommit newHead;
211
212 private boolean lastStepWasForward;
213
214 private MergeStrategy strategy = MergeStrategy.RECURSIVE;
215
216 private ContentMergeStrategy contentStrategy;
217
218 private boolean preserveMerges = false;
219
220
221
222
223
224
225
226
227
228 protected RebaseCommand(Repository repo) {
229 super(repo);
230 walk = new RevWalk(repo);
231 rebaseState = new RebaseState(repo.getDirectory());
232 }
233
234
235
236
237
238
239
240
241
242 @Override
243 public RebaseResult call() throws GitAPIException, NoHeadException,
244 RefNotFoundException, WrongRepositoryStateException {
245 newHead = null;
246 lastStepWasForward = false;
247 checkCallable();
248 checkParameters();
249 try {
250 switch (operation) {
251 case ABORT:
252 try {
253 return abort(RebaseResult.ABORTED_RESULT);
254 } catch (IOException ioe) {
255 throw new JGitInternalException(ioe.getMessage(), ioe);
256 }
257 case PROCESS_STEPS:
258 case SKIP:
259 case CONTINUE:
260 String upstreamCommitId = rebaseState.readFile(ONTO);
261 try {
262 upstreamCommitName = rebaseState.readFile(ONTO_NAME);
263 } catch (FileNotFoundException e) {
264
265
266 upstreamCommitName = upstreamCommitId;
267 }
268 this.upstreamCommit = walk.parseCommit(repo
269 .resolve(upstreamCommitId));
270 preserveMerges = rebaseState.getRewrittenDir().isDirectory();
271 break;
272 case BEGIN:
273 autoStash();
274 if (stopAfterInitialization
275 || !walk.isMergedInto(
276 walk.parseCommit(repo.resolve(Constants.HEAD)),
277 upstreamCommit)) {
278 org.eclipse.jgit.api.Status status = Git.wrap(repo)
279 .status().setIgnoreSubmodules(IgnoreSubmoduleMode.ALL).call();
280 if (status.hasUncommittedChanges()) {
281 List<String> list = new ArrayList<>();
282 list.addAll(status.getUncommittedChanges());
283 return RebaseResult.uncommittedChanges(list);
284 }
285 }
286 RebaseResult res = initFilesAndRewind();
287 if (stopAfterInitialization)
288 return RebaseResult.INTERACTIVE_PREPARED_RESULT;
289 if (res != null) {
290 autoStashApply();
291 if (rebaseState.getDir().exists())
292 FileUtils.delete(rebaseState.getDir(),
293 FileUtils.RECURSIVE);
294 return res;
295 }
296 }
297
298 if (monitor.isCancelled())
299 return abort(RebaseResult.ABORTED_RESULT);
300
301 if (operation == Operation.CONTINUE) {
302 newHead = continueRebase();
303 List<RebaseTodoLine> doneLines = repo.readRebaseTodo(
304 rebaseState.getPath(DONE), true);
305 RebaseTodoLine step = doneLines.get(doneLines.size() - 1);
306 if (newHead != null
307 && step.getAction() != Action.PICK) {
308 RebaseTodoLine newStep = new RebaseTodoLine(
309 step.getAction(),
310 AbbreviatedObjectId.fromObjectId(newHead),
311 step.getShortMessage());
312 RebaseResult result = processStep(newStep, false);
313 if (result != null)
314 return result;
315 }
316 File amendFile = rebaseState.getFile(AMEND);
317 boolean amendExists = amendFile.exists();
318 if (amendExists) {
319 FileUtils.delete(amendFile);
320 }
321 if (newHead == null && !amendExists) {
322
323
324
325
326
327 return RebaseResult.NOTHING_TO_COMMIT_RESULT;
328 }
329 }
330
331 if (operation == Operation.SKIP)
332 newHead = checkoutCurrentHead();
333
334 List<RebaseTodoLine> steps = repo.readRebaseTodo(
335 rebaseState.getPath(GIT_REBASE_TODO), false);
336 if (steps.isEmpty()) {
337 return finishRebase(walk.parseCommit(repo.resolve(Constants.HEAD)), false);
338 }
339 if (isInteractive()) {
340 interactiveHandler.prepareSteps(steps);
341 repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO),
342 steps, false);
343 }
344 checkSteps(steps);
345 for (RebaseTodoLine step : steps) {
346 popSteps(1);
347 RebaseResult result = processStep(step, true);
348 if (result != null) {
349 return result;
350 }
351 }
352 return finishRebase(newHead, lastStepWasForward);
353 } catch (CheckoutConflictException cce) {
354 return RebaseResult.conflicts(cce.getConflictingPaths());
355 } catch (IOException ioe) {
356 throw new JGitInternalException(ioe.getMessage(), ioe);
357 }
358 }
359
360 private void autoStash() throws GitAPIException, IOException {
361 if (repo.getConfig().getBoolean(ConfigConstants.CONFIG_REBASE_SECTION,
362 ConfigConstants.CONFIG_KEY_AUTOSTASH, false)) {
363 String message = MessageFormat.format(
364 AUTOSTASH_MSG,
365 Repository
366 .shortenRefName(getHeadName(getHead())));
367 RevCommit stashCommit = Git.wrap(repo).stashCreate().setRef(null)
368 .setWorkingDirectoryMessage(
369 message)
370 .call();
371 if (stashCommit != null) {
372 FileUtils.mkdir(rebaseState.getDir());
373 rebaseState.createFile(AUTOSTASH, stashCommit.getName());
374 }
375 }
376 }
377
378 private boolean autoStashApply() throws IOException, GitAPIException {
379 boolean conflicts = false;
380 if (rebaseState.getFile(AUTOSTASH).exists()) {
381 String stash = rebaseState.readFile(AUTOSTASH);
382 try (Git git = Git.wrap(repo)) {
383 git.stashApply().setStashRef(stash)
384 .ignoreRepositoryState(true).setStrategy(strategy)
385 .call();
386 } catch (StashApplyFailureException e) {
387 conflicts = true;
388 try (RevWalk rw = new RevWalk(repo)) {
389 ObjectId stashId = repo.resolve(stash);
390 RevCommit commit = rw.parseCommit(stashId);
391 updateStashRef(commit, commit.getAuthorIdent(),
392 commit.getShortMessage());
393 }
394 }
395 }
396 return conflicts;
397 }
398
399 private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
400 String refLogMessage) throws IOException {
401 Ref currentRef = repo.exactRef(Constants.R_STASH);
402 RefUpdate refUpdate = repo.updateRef(Constants.R_STASH);
403 refUpdate.setNewObjectId(commitId);
404 refUpdate.setRefLogIdent(refLogIdent);
405 refUpdate.setRefLogMessage(refLogMessage, false);
406 refUpdate.setForceRefLog(true);
407 if (currentRef != null)
408 refUpdate.setExpectedOldObjectId(currentRef.getObjectId());
409 else
410 refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
411 refUpdate.forceUpdate();
412 }
413
414 private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick)
415 throws IOException, GitAPIException {
416 if (Action.COMMENT.equals(step.getAction()))
417 return null;
418 if (preserveMerges
419 && shouldPick
420 && (Action.EDIT.equals(step.getAction()) || Action.PICK
421 .equals(step.getAction()))) {
422 writeRewrittenHashes();
423 }
424 ObjectReader or = repo.newObjectReader();
425
426 Collection<ObjectId> ids = or.resolve(step.getCommit());
427 if (ids.size() != 1)
428 throw new JGitInternalException(
429 JGitText.get().cannotResolveUniquelyAbbrevObjectId);
430 RevCommit commitToPick = walk.parseCommit(ids.iterator().next());
431 if (shouldPick) {
432 if (monitor.isCancelled())
433 return RebaseResult.result(Status.STOPPED, commitToPick);
434 RebaseResult result = cherryPickCommit(commitToPick);
435 if (result != null)
436 return result;
437 }
438 boolean isSquash = false;
439 switch (step.getAction()) {
440 case PICK:
441 return null;
442 case REWORD:
443 String oldMessage = commitToPick.getFullMessage();
444 String newMessage = interactiveHandler
445 .modifyCommitMessage(oldMessage);
446 try (Git git = new Git(repo)) {
447 newHead = git.commit().setMessage(newMessage).setAmend(true)
448 .setNoVerify(true).call();
449 }
450 return null;
451 case EDIT:
452 rebaseState.createFile(AMEND, commitToPick.name());
453 return stop(commitToPick, Status.EDIT);
454 case COMMENT:
455 break;
456 case SQUASH:
457 isSquash = true;
458
459 case FIXUP:
460 resetSoftToParent();
461 List<RebaseTodoLine> steps = repo.readRebaseTodo(
462 rebaseState.getPath(GIT_REBASE_TODO), false);
463 RebaseTodoLine nextStep = steps.isEmpty() ? null : steps.get(0);
464 File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP);
465 File messageSquashFile = rebaseState.getFile(MESSAGE_SQUASH);
466 if (isSquash && messageFixupFile.exists())
467 messageFixupFile.delete();
468 newHead = doSquashFixup(isSquash, commitToPick, nextStep,
469 messageFixupFile, messageSquashFile);
470 }
471 return null;
472 }
473
474 private RebaseResult cherryPickCommit(RevCommit commitToPick)
475 throws IOException, GitAPIException, NoMessageException,
476 UnmergedPathsException, ConcurrentRefUpdateException,
477 WrongRepositoryStateException, NoHeadException {
478 try {
479 monitor.beginTask(MessageFormat.format(
480 JGitText.get().applyingCommit,
481 commitToPick.getShortMessage()), ProgressMonitor.UNKNOWN);
482 if (preserveMerges) {
483 return cherryPickCommitPreservingMerges(commitToPick);
484 }
485 return cherryPickCommitFlattening(commitToPick);
486 } finally {
487 monitor.endTask();
488 }
489 }
490
491 private RebaseResult cherryPickCommitFlattening(RevCommit commitToPick)
492 throws IOException, GitAPIException, NoMessageException,
493 UnmergedPathsException, ConcurrentRefUpdateException,
494 WrongRepositoryStateException, NoHeadException {
495
496
497
498 newHead = tryFastForward(commitToPick);
499 lastStepWasForward = newHead != null;
500 if (!lastStepWasForward) {
501
502
503
504 String ourCommitName = getOurCommitName();
505 try (Git git = new Git(repo)) {
506 CherryPickResult cherryPickResult = git.cherryPick()
507 .include(commitToPick)
508 .setOurCommitName(ourCommitName)
509 .setReflogPrefix(REFLOG_PREFIX)
510 .setStrategy(strategy)
511 .setContentMergeStrategy(contentStrategy)
512 .call();
513 switch (cherryPickResult.getStatus()) {
514 case FAILED:
515 if (operation == Operation.BEGIN) {
516 return abort(RebaseResult
517 .failed(cherryPickResult.getFailingPaths()));
518 }
519 return stop(commitToPick, Status.STOPPED);
520 case CONFLICTING:
521 return stop(commitToPick, Status.STOPPED);
522 case OK:
523 newHead = cherryPickResult.getNewHead();
524 }
525 }
526 }
527 return null;
528 }
529
530 private RebaseResult cherryPickCommitPreservingMerges(RevCommit commitToPick)
531 throws IOException, GitAPIException, NoMessageException,
532 UnmergedPathsException, ConcurrentRefUpdateException,
533 WrongRepositoryStateException, NoHeadException {
534
535 writeCurrentCommit(commitToPick);
536
537 List<RevCommit> newParents = getNewParents(commitToPick);
538 boolean otherParentsUnchanged = true;
539 for (int i = 1; i < commitToPick.getParentCount(); i++)
540 otherParentsUnchanged &= newParents.get(i).equals(
541 commitToPick.getParent(i));
542
543
544
545 newHead = otherParentsUnchanged ? tryFastForward(commitToPick) : null;
546 lastStepWasForward = newHead != null;
547 if (!lastStepWasForward) {
548 ObjectId headId = getHead().getObjectId();
549
550 assert headId != null;
551 if (!AnyObjectId.isEqual(headId, newParents.get(0)))
552 checkoutCommit(headId.getName(), newParents.get(0));
553
554
555
556
557 try (Git git = new Git(repo)) {
558 if (otherParentsUnchanged) {
559 boolean isMerge = commitToPick.getParentCount() > 1;
560 String ourCommitName = getOurCommitName();
561 CherryPickCommand pickCommand = git.cherryPick()
562 .include(commitToPick)
563 .setOurCommitName(ourCommitName)
564 .setReflogPrefix(REFLOG_PREFIX)
565 .setStrategy(strategy)
566 .setContentMergeStrategy(contentStrategy);
567 if (isMerge) {
568 pickCommand.setMainlineParentNumber(1);
569
570 pickCommand.setNoCommit(true);
571 writeMergeInfo(commitToPick, newParents);
572 }
573 CherryPickResult cherryPickResult = pickCommand.call();
574 switch (cherryPickResult.getStatus()) {
575 case FAILED:
576 if (operation == Operation.BEGIN) {
577 return abort(RebaseResult.failed(
578 cherryPickResult.getFailingPaths()));
579 }
580 return stop(commitToPick, Status.STOPPED);
581 case CONFLICTING:
582 return stop(commitToPick, Status.STOPPED);
583 case OK:
584 if (isMerge) {
585
586
587 CommitCommand commit = git.commit();
588 commit.setAuthor(commitToPick.getAuthorIdent());
589 commit.setReflogComment(REFLOG_PREFIX + " "
590 + commitToPick.getShortMessage());
591 newHead = commit.call();
592 } else
593 newHead = cherryPickResult.getNewHead();
594 break;
595 }
596 } else {
597
598
599 MergeCommand merge = git.merge()
600 .setFastForward(MergeCommand.FastForwardMode.NO_FF)
601 .setProgressMonitor(monitor)
602 .setStrategy(strategy)
603 .setContentMergeStrategy(contentStrategy)
604 .setCommit(false);
605 for (int i = 1; i < commitToPick.getParentCount(); i++)
606 merge.include(newParents.get(i));
607 MergeResult mergeResult = merge.call();
608 if (mergeResult.getMergeStatus().isSuccessful()) {
609 CommitCommand commit = git.commit();
610 commit.setAuthor(commitToPick.getAuthorIdent());
611 commit.setMessage(commitToPick.getFullMessage());
612 commit.setReflogComment(REFLOG_PREFIX + " "
613 + commitToPick.getShortMessage());
614 newHead = commit.call();
615 } else {
616 if (operation == Operation.BEGIN && mergeResult
617 .getMergeStatus() == MergeResult.MergeStatus.FAILED)
618 return abort(RebaseResult
619 .failed(mergeResult.getFailingPaths()));
620 return stop(commitToPick, Status.STOPPED);
621 }
622 }
623 }
624 }
625 return null;
626 }
627
628
629 private void writeMergeInfo(RevCommit commitToPick,
630 List<RevCommit> newParents) throws IOException {
631 repo.writeMergeHeads(newParents.subList(1, newParents.size()));
632 repo.writeMergeCommitMsg(commitToPick.getFullMessage());
633 }
634
635
636 private List<RevCommit> getNewParents(RevCommit commitToPick)
637 throws IOException {
638 List<RevCommit> newParents = new ArrayList<>();
639 for (int p = 0; p < commitToPick.getParentCount(); p++) {
640 String parentHash = commitToPick.getParent(p).getName();
641 if (!new File(rebaseState.getRewrittenDir(), parentHash).exists())
642 newParents.add(commitToPick.getParent(p));
643 else {
644 String newParent = RebaseState.readFile(
645 rebaseState.getRewrittenDir(), parentHash);
646 if (newParent.length() == 0)
647 newParents.add(walk.parseCommit(repo
648 .resolve(Constants.HEAD)));
649 else
650 newParents.add(walk.parseCommit(ObjectId
651 .fromString(newParent)));
652 }
653 }
654 return newParents;
655 }
656
657 private void writeCurrentCommit(RevCommit commit) throws IOException {
658 RebaseState.appendToFile(rebaseState.getFile(CURRENT_COMMIT),
659 commit.name());
660 }
661
662 private void writeRewrittenHashes() throws RevisionSyntaxException,
663 IOException, RefNotFoundException {
664 File currentCommitFile = rebaseState.getFile(CURRENT_COMMIT);
665 if (!currentCommitFile.exists())
666 return;
667
668 ObjectId headId = getHead().getObjectId();
669
670 assert headId != null;
671 String head = headId.getName();
672 String currentCommits = rebaseState.readFile(CURRENT_COMMIT);
673 for (String current : currentCommits.split("\n"))
674 RebaseState
675 .createFile(rebaseState.getRewrittenDir(), current, head);
676 FileUtils.delete(currentCommitFile);
677 }
678
679 private RebaseResult finishRebase(RevCommit finalHead,
680 boolean lastStepIsForward) throws IOException, GitAPIException {
681 String headName = rebaseState.readFile(HEAD_NAME);
682 updateHead(headName, finalHead, upstreamCommit);
683 boolean stashConflicts = autoStashApply();
684 getRepository().autoGC(monitor);
685 FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
686 if (stashConflicts)
687 return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
688 if (lastStepIsForward || finalHead == null)
689 return RebaseResult.FAST_FORWARD_RESULT;
690 return RebaseResult.OK_RESULT;
691 }
692
693 private void checkSteps(List<RebaseTodoLine> steps)
694 throws InvalidRebaseStepException, IOException {
695 if (steps.isEmpty())
696 return;
697 if (RebaseTodoLine.Action.SQUASH.equals(steps.get(0).getAction())
698 || RebaseTodoLine.Action.FIXUP.equals(steps.get(0).getAction())) {
699 if (!rebaseState.getFile(DONE).exists()
700 || rebaseState.readFile(DONE).trim().length() == 0) {
701 throw new InvalidRebaseStepException(MessageFormat.format(
702 JGitText.get().cannotSquashFixupWithoutPreviousCommit,
703 steps.get(0).getAction().name()));
704 }
705 }
706
707 }
708
709 private RevCommit doSquashFixup(boolean isSquash, RevCommit commitToPick,
710 RebaseTodoLine nextStep, File messageFixup, File messageSquash)
711 throws IOException, GitAPIException {
712
713 if (!messageSquash.exists()) {
714
715 ObjectId headId = repo.resolve(Constants.HEAD);
716 RevCommit previousCommit = walk.parseCommit(headId);
717
718 initializeSquashFixupFile(MESSAGE_SQUASH,
719 previousCommit.getFullMessage());
720 if (!isSquash)
721 initializeSquashFixupFile(MESSAGE_FIXUP,
722 previousCommit.getFullMessage());
723 }
724 String currSquashMessage = rebaseState
725 .readFile(MESSAGE_SQUASH);
726
727 int count = parseSquashFixupSequenceCount(currSquashMessage) + 1;
728
729 String content = composeSquashMessage(isSquash,
730 commitToPick, currSquashMessage, count);
731 rebaseState.createFile(MESSAGE_SQUASH, content);
732 if (messageFixup.exists())
733 rebaseState.createFile(MESSAGE_FIXUP, content);
734
735 return squashIntoPrevious(
736 !messageFixup.exists(),
737 nextStep);
738 }
739
740 private void resetSoftToParent() throws IOException,
741 GitAPIException, CheckoutConflictException {
742 Ref ref = repo.exactRef(Constants.ORIG_HEAD);
743 ObjectId orig_head = ref == null ? null : ref.getObjectId();
744 try (Git git = Git.wrap(repo)) {
745
746
747
748
749 git.reset().setMode(ResetType.SOFT)
750 .setRef("HEAD~1").call();
751 } finally {
752
753
754 repo.writeOrigHead(orig_head);
755 }
756 }
757
758 private RevCommit squashIntoPrevious(boolean sequenceContainsSquash,
759 RebaseTodoLine nextStep)
760 throws IOException, GitAPIException {
761 RevCommit retNewHead;
762 String commitMessage = rebaseState
763 .readFile(MESSAGE_SQUASH);
764
765 try (Git git = new Git(repo)) {
766 if (nextStep == null || ((nextStep.getAction() != Action.FIXUP)
767 && (nextStep.getAction() != Action.SQUASH))) {
768
769 if (sequenceContainsSquash) {
770 commitMessage = interactiveHandler
771 .modifyCommitMessage(commitMessage);
772 }
773 retNewHead = git.commit()
774 .setMessage(stripCommentLines(commitMessage))
775 .setAmend(true).setNoVerify(true).call();
776 rebaseState.getFile(MESSAGE_SQUASH).delete();
777 rebaseState.getFile(MESSAGE_FIXUP).delete();
778
779 } else {
780
781 retNewHead = git.commit().setMessage(commitMessage)
782 .setAmend(true).setNoVerify(true).call();
783 }
784 }
785 return retNewHead;
786 }
787
788 private static String stripCommentLines(String commitMessage) {
789 StringBuilder result = new StringBuilder();
790 for (String line : commitMessage.split("\n")) {
791 if (!line.trim().startsWith("#"))
792 result.append(line).append("\n");
793 }
794 if (!commitMessage.endsWith("\n")) {
795 int bufferSize = result.length();
796 if (bufferSize > 0 && result.charAt(bufferSize - 1) == '\n') {
797 result.deleteCharAt(bufferSize - 1);
798 }
799 }
800 return result.toString();
801 }
802
803 @SuppressWarnings("nls")
804 private static String composeSquashMessage(boolean isSquash,
805 RevCommit commitToPick, String currSquashMessage, int count) {
806 StringBuilder sb = new StringBuilder();
807 String ordinal = getOrdinal(count);
808 sb.setLength(0);
809 sb.append("# This is a combination of ").append(count)
810 .append(" commits.\n");
811
812 sb.append(currSquashMessage
813 .substring(currSquashMessage.indexOf('\n') + 1));
814 sb.append("\n");
815 if (isSquash) {
816 sb.append("# This is the ").append(count).append(ordinal)
817 .append(" commit message:\n");
818 sb.append(commitToPick.getFullMessage());
819 } else {
820 sb.append("# The ").append(count).append(ordinal)
821 .append(" commit message will be skipped:\n# ");
822 sb.append(commitToPick.getFullMessage().replaceAll("([\n\r])",
823 "$1# "));
824 }
825 return sb.toString();
826 }
827
828 private static String getOrdinal(int count) {
829 switch (count % 10) {
830 case 1:
831 return "st";
832 case 2:
833 return "nd";
834 case 3:
835 return "rd";
836 default:
837 return "th";
838 }
839 }
840
841
842
843
844
845
846
847
848 static int parseSquashFixupSequenceCount(String currSquashMessage) {
849 String regex = "This is a combination of (.*) commits";
850 String firstLine = currSquashMessage.substring(0,
851 currSquashMessage.indexOf('\n'));
852 Pattern pattern = Pattern.compile(regex);
853 Matcher matcher = pattern.matcher(firstLine);
854 if (!matcher.find())
855 throw new IllegalArgumentException();
856 return Integer.parseInt(matcher.group(1));
857 }
858
859 private void initializeSquashFixupFile(String messageFile,
860 String fullMessage) throws IOException {
861 rebaseState
862 .createFile(
863 messageFile,
864 "# This is a combination of 1 commits.\n# The first commit's message is:\n" + fullMessage);
865 }
866
867 private String getOurCommitName() {
868
869
870 String ourCommitName = "Upstream, based on "
871 + Repository.shortenRefName(upstreamCommitName);
872 return ourCommitName;
873 }
874
875 private void updateHead(String headName, RevCommit aNewHead, RevCommit onto)
876 throws IOException {
877
878
879 if (headName.startsWith(Constants.R_REFS)) {
880 RefUpdate rup = repo.updateRef(headName);
881 rup.setNewObjectId(aNewHead);
882 rup.setRefLogMessage("rebase finished: " + headName + " onto "
883 + onto.getName(), false);
884 Result res = rup.forceUpdate();
885 switch (res) {
886 case FAST_FORWARD:
887 case FORCED:
888 case NO_CHANGE:
889 break;
890 default:
891 throw new JGitInternalException(
892 JGitText.get().updatingHeadFailed);
893 }
894 rup = repo.updateRef(Constants.HEAD);
895 rup.setRefLogMessage("rebase finished: returning to " + headName,
896 false);
897 res = rup.link(headName);
898 switch (res) {
899 case FAST_FORWARD:
900 case FORCED:
901 case NO_CHANGE:
902 break;
903 default:
904 throw new JGitInternalException(
905 JGitText.get().updatingHeadFailed);
906 }
907 }
908 }
909
910 private RevCommit checkoutCurrentHead() throws IOException, NoHeadException {
911 ObjectId headTree = repo.resolve(Constants.HEAD + "^{tree}");
912 if (headTree == null)
913 throw new NoHeadException(
914 JGitText.get().cannotRebaseWithoutCurrentHead);
915 DirCache dc = repo.lockDirCache();
916 try {
917 DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree);
918 dco.setFailOnConflict(false);
919 dco.setProgressMonitor(monitor);
920 boolean needsDeleteFiles = dco.checkout();
921 if (needsDeleteFiles) {
922 List<String> fileList = dco.getToBeDeleted();
923 for (String filePath : fileList) {
924 File fileToDelete = new File(repo.getWorkTree(), filePath);
925 if (repo.getFS().exists(fileToDelete))
926 FileUtils.delete(fileToDelete, FileUtils.RECURSIVE
927 | FileUtils.RETRY);
928 }
929 }
930 } finally {
931 dc.unlock();
932 }
933 try (RevWalk rw = new RevWalk(repo)) {
934 RevCommit commit = rw.parseCommit(repo.resolve(Constants.HEAD));
935 return commit;
936 }
937 }
938
939
940
941
942
943
944 private RevCommit continueRebase() throws GitAPIException, IOException {
945
946 DirCache dc = repo.readDirCache();
947 boolean hasUnmergedPaths = dc.hasUnmergedPaths();
948 if (hasUnmergedPaths)
949 throw new UnmergedPathsException();
950
951
952 boolean needsCommit;
953 try (TreeWalk treeWalk = new TreeWalk(repo)) {
954 treeWalk.reset();
955 treeWalk.setRecursive(true);
956 treeWalk.addTree(new DirCacheIterator(dc));
957 ObjectId id = repo.resolve(Constants.HEAD + "^{tree}");
958 if (id == null)
959 throw new NoHeadException(
960 JGitText.get().cannotRebaseWithoutCurrentHead);
961
962 treeWalk.addTree(id);
963
964 treeWalk.setFilter(TreeFilter.ANY_DIFF);
965
966 needsCommit = treeWalk.next();
967 }
968 if (needsCommit) {
969 try (Git git = new Git(repo)) {
970 CommitCommand commit = git.commit();
971 commit.setMessage(rebaseState.readFile(MESSAGE));
972 commit.setAuthor(parseAuthor());
973 return commit.call();
974 }
975 }
976 return null;
977 }
978
979 private PersonIdent parseAuthor() throws IOException {
980 File authorScriptFile = rebaseState.getFile(AUTHOR_SCRIPT);
981 byte[] raw;
982 try {
983 raw = IO.readFully(authorScriptFile);
984 } catch (FileNotFoundException notFound) {
985 if (authorScriptFile.exists()) {
986 throw notFound;
987 }
988 return null;
989 }
990 return parseAuthor(raw);
991 }
992
993 private RebaseResult stop(RevCommit commitToPick, RebaseResult.Status status)
994 throws IOException {
995 PersonIdent author = commitToPick.getAuthorIdent();
996 String authorScript = toAuthorScript(author);
997 rebaseState.createFile(AUTHOR_SCRIPT, authorScript);
998 rebaseState.createFile(MESSAGE, commitToPick.getFullMessage());
999 ByteArrayOutputStream bos = new ByteArrayOutputStream();
1000 try (DiffFormatter df = new DiffFormatter(bos)) {
1001 df.setRepository(repo);
1002 df.format(commitToPick.getParent(0), commitToPick);
1003 }
1004 rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8));
1005 rebaseState.createFile(STOPPED_SHA,
1006 repo.newObjectReader()
1007 .abbreviate(
1008 commitToPick).name());
1009
1010
1011 repo.writeCherryPickHead(null);
1012 return RebaseResult.result(status, commitToPick);
1013 }
1014
1015 String toAuthorScript(PersonIdent author) {
1016 StringBuilder sb = new StringBuilder(100);
1017 sb.append(GIT_AUTHOR_NAME);
1018 sb.append("='");
1019 sb.append(author.getName());
1020 sb.append("'\n");
1021 sb.append(GIT_AUTHOR_EMAIL);
1022 sb.append("='");
1023 sb.append(author.getEmailAddress());
1024 sb.append("'\n");
1025
1026
1027 sb.append(GIT_AUTHOR_DATE);
1028 sb.append("='");
1029 sb.append("@");
1030 String externalString = author.toExternalString();
1031 sb
1032 .append(externalString.substring(externalString
1033 .lastIndexOf('>') + 2));
1034 sb.append("'\n");
1035 return sb.toString();
1036 }
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046 private void popSteps(int numSteps) throws IOException {
1047 if (numSteps == 0)
1048 return;
1049 List<RebaseTodoLine> todoLines = new LinkedList<>();
1050 List<RebaseTodoLine> poppedLines = new LinkedList<>();
1051
1052 for (RebaseTodoLine line : repo.readRebaseTodo(
1053 rebaseState.getPath(GIT_REBASE_TODO), true)) {
1054 if (poppedLines.size() >= numSteps
1055 || RebaseTodoLine.Action.COMMENT.equals(line.getAction()))
1056 todoLines.add(line);
1057 else
1058 poppedLines.add(line);
1059 }
1060
1061 repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO),
1062 todoLines, false);
1063 if (!poppedLines.isEmpty()) {
1064 repo.writeRebaseTodoFile(rebaseState.getPath(DONE), poppedLines,
1065 true);
1066 }
1067 }
1068
1069 private RebaseResult initFilesAndRewind() throws IOException,
1070 GitAPIException {
1071
1072
1073
1074 Ref head = getHead();
1075
1076 ObjectId headId = head.getObjectId();
1077 if (headId == null) {
1078 throw new RefNotFoundException(MessageFormat.format(
1079 JGitText.get().refNotResolved, Constants.HEAD));
1080 }
1081 String headName = getHeadName(head);
1082 RevCommit headCommit = walk.lookupCommit(headId);
1083 RevCommit upstream = walk.lookupCommit(upstreamCommit.getId());
1084
1085 if (!isInteractive() && walk.isMergedInto(upstream, headCommit))
1086 return RebaseResult.UP_TO_DATE_RESULT;
1087 else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
1088
1089 monitor.beginTask(MessageFormat.format(
1090 JGitText.get().resettingHead,
1091 upstreamCommit.getShortMessage()), ProgressMonitor.UNKNOWN);
1092 checkoutCommit(headName, upstreamCommit);
1093 monitor.endTask();
1094
1095 updateHead(headName, upstreamCommit, upstream);
1096 return RebaseResult.FAST_FORWARD_RESULT;
1097 }
1098
1099 monitor.beginTask(JGitText.get().obtainingCommitsForCherryPick,
1100 ProgressMonitor.UNKNOWN);
1101
1102
1103 FileUtils.mkdir(rebaseState.getDir(), true);
1104
1105 repo.writeOrigHead(headId);
1106 rebaseState.createFile(REBASE_HEAD, headId.name());
1107 rebaseState.createFile(REBASE_HEAD_LEGACY, headId.name());
1108 rebaseState.createFile(HEAD_NAME, headName);
1109 rebaseState.createFile(ONTO, upstreamCommit.name());
1110 rebaseState.createFile(ONTO_NAME, upstreamCommitName);
1111 if (isInteractive() || preserveMerges) {
1112
1113
1114
1115 rebaseState.createFile(INTERACTIVE, "");
1116 }
1117 rebaseState.createFile(QUIET, "");
1118
1119 ArrayList<RebaseTodoLine> toDoSteps = new ArrayList<>();
1120 toDoSteps.add(new RebaseTodoLine("# Created by EGit: rebasing " + headId.name()
1121 + " onto " + upstreamCommit.name()));
1122
1123 List<RevCommit> cherryPickList = calculatePickList(headCommit);
1124 ObjectReader reader = walk.getObjectReader();
1125 for (RevCommit commit : cherryPickList)
1126 toDoSteps.add(new RebaseTodoLine(Action.PICK, reader
1127 .abbreviate(commit), commit.getShortMessage()));
1128 repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO),
1129 toDoSteps, false);
1130
1131 monitor.endTask();
1132
1133
1134 monitor.beginTask(MessageFormat.format(JGitText.get().rewinding,
1135 upstreamCommit.getShortMessage()), ProgressMonitor.UNKNOWN);
1136 boolean checkoutOk = false;
1137 try {
1138 checkoutOk = checkoutCommit(headName, upstreamCommit);
1139 } finally {
1140 if (!checkoutOk)
1141 FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
1142 }
1143 monitor.endTask();
1144
1145 return null;
1146 }
1147
1148 private List<RevCommit> calculatePickList(RevCommit headCommit)
1149 throws IOException {
1150 List<RevCommit> cherryPickList = new ArrayList<>();
1151 try (RevWalk r = new RevWalk(repo)) {
1152 r.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true);
1153 r.sort(RevSort.COMMIT_TIME_DESC, true);
1154 r.markUninteresting(r.lookupCommit(upstreamCommit));
1155 r.markStart(r.lookupCommit(headCommit));
1156 Iterator<RevCommit> commitsToUse = r.iterator();
1157 while (commitsToUse.hasNext()) {
1158 RevCommit commit = commitsToUse.next();
1159 if (preserveMerges || commit.getParentCount() == 1) {
1160 cherryPickList.add(commit);
1161 }
1162 }
1163 }
1164 Collections.reverse(cherryPickList);
1165
1166 if (preserveMerges) {
1167
1168
1169 File rewrittenDir = rebaseState.getRewrittenDir();
1170 FileUtils.mkdir(rewrittenDir, false);
1171 walk.reset();
1172 walk.setRevFilter(RevFilter.MERGE_BASE);
1173 walk.markStart(upstreamCommit);
1174 walk.markStart(headCommit);
1175 RevCommit base;
1176 while ((base = walk.next()) != null)
1177 RebaseState.createFile(rewrittenDir, base.getName(),
1178 upstreamCommit.getName());
1179
1180 Iterator<RevCommit> iterator = cherryPickList.iterator();
1181 pickLoop: while(iterator.hasNext()){
1182 RevCommit commit = iterator.next();
1183 for (int i = 0; i < commit.getParentCount(); i++) {
1184 boolean parentRewritten = new File(rewrittenDir, commit
1185 .getParent(i).getName()).exists();
1186 if (parentRewritten) {
1187 new File(rewrittenDir, commit.getName()).createNewFile();
1188 continue pickLoop;
1189 }
1190 }
1191
1192 iterator.remove();
1193 }
1194 }
1195 return cherryPickList;
1196 }
1197
1198 private static String getHeadName(Ref head) {
1199 String headName;
1200 if (head.isSymbolic()) {
1201 headName = head.getTarget().getName();
1202 } else {
1203 ObjectId headId = head.getObjectId();
1204
1205 assert headId != null;
1206 headName = headId.getName();
1207 }
1208 return headName;
1209 }
1210
1211 private Ref getHead() throws IOException, RefNotFoundException {
1212 Ref head = repo.exactRef(Constants.HEAD);
1213 if (head == null || head.getObjectId() == null)
1214 throw new RefNotFoundException(MessageFormat.format(
1215 JGitText.get().refNotResolved, Constants.HEAD));
1216 return head;
1217 }
1218
1219 private boolean isInteractive() {
1220 return interactiveHandler != null;
1221 }
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233 public RevCommit tryFastForward(RevCommit newCommit) throws IOException,
1234 GitAPIException {
1235 Ref head = getHead();
1236
1237 ObjectId headId = head.getObjectId();
1238 if (headId == null)
1239 throw new RefNotFoundException(MessageFormat.format(
1240 JGitText.get().refNotResolved, Constants.HEAD));
1241 RevCommit headCommit = walk.lookupCommit(headId);
1242 if (walk.isMergedInto(newCommit, headCommit))
1243 return newCommit;
1244
1245 String headName = getHeadName(head);
1246 return tryFastForward(headName, headCommit, newCommit);
1247 }
1248
1249 private RevCommit tryFastForward(String headName, RevCommit oldCommit,
1250 RevCommit newCommit) throws IOException, GitAPIException {
1251 boolean tryRebase = false;
1252 for (RevCommit parentCommit : newCommit.getParents())
1253 if (parentCommit.equals(oldCommit))
1254 tryRebase = true;
1255 if (!tryRebase)
1256 return null;
1257
1258 CheckoutCommand co = new CheckoutCommand(repo);
1259 try {
1260 co.setProgressMonitor(monitor);
1261 co.setName(newCommit.name()).call();
1262 if (headName.startsWith(Constants.R_HEADS)) {
1263 RefUpdate rup = repo.updateRef(headName);
1264 rup.setExpectedOldObjectId(oldCommit);
1265 rup.setNewObjectId(newCommit);
1266 rup.setRefLogMessage("Fast-forward from " + oldCommit.name()
1267 + " to " + newCommit.name(), false);
1268 Result res = rup.update(walk);
1269 switch (res) {
1270 case FAST_FORWARD:
1271 case NO_CHANGE:
1272 case FORCED:
1273 break;
1274 default:
1275 throw new IOException("Could not fast-forward");
1276 }
1277 }
1278 return newCommit;
1279 } catch (RefAlreadyExistsException | RefNotFoundException
1280 | InvalidRefNameException | CheckoutConflictException e) {
1281 throw new JGitInternalException(e.getMessage(), e);
1282 }
1283 }
1284
1285 private void checkParameters() throws WrongRepositoryStateException {
1286 if (this.operation == Operation.PROCESS_STEPS) {
1287 if (rebaseState.getFile(DONE).exists())
1288 throw new WrongRepositoryStateException(MessageFormat.format(
1289 JGitText.get().wrongRepositoryState, repo
1290 .getRepositoryState().name()));
1291 }
1292 if (this.operation != Operation.BEGIN) {
1293
1294 switch (repo.getRepositoryState()) {
1295 case REBASING_INTERACTIVE:
1296 case REBASING:
1297 case REBASING_REBASING:
1298 case REBASING_MERGE:
1299 break;
1300 default:
1301 throw new WrongRepositoryStateException(MessageFormat.format(
1302 JGitText.get().wrongRepositoryState, repo
1303 .getRepositoryState().name()));
1304 }
1305 } else
1306 switch (repo.getRepositoryState()) {
1307 case SAFE:
1308 if (this.upstreamCommit == null)
1309 throw new JGitInternalException(MessageFormat
1310 .format(JGitText.get().missingRequiredParameter,
1311 "upstream"));
1312 return;
1313 default:
1314 throw new WrongRepositoryStateException(MessageFormat.format(
1315 JGitText.get().wrongRepositoryState, repo
1316 .getRepositoryState().name()));
1317
1318 }
1319 }
1320
1321 private RebaseResult abort(RebaseResult result) throws IOException,
1322 GitAPIException {
1323 ObjectId origHead = getOriginalHead();
1324 try {
1325 String commitId = origHead != null ? origHead.name() : null;
1326 monitor.beginTask(MessageFormat.format(
1327 JGitText.get().abortingRebase, commitId),
1328 ProgressMonitor.UNKNOWN);
1329
1330 DirCacheCheckout dco;
1331 if (commitId == null)
1332 throw new JGitInternalException(
1333 JGitText.get().abortingRebaseFailedNoOrigHead);
1334 ObjectId id = repo.resolve(commitId);
1335 RevCommit commit = walk.parseCommit(id);
1336 if (result.getStatus().equals(Status.FAILED)) {
1337 RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD));
1338 dco = new DirCacheCheckout(repo, head.getTree(),
1339 repo.lockDirCache(), commit.getTree());
1340 } else {
1341 dco = new DirCacheCheckout(repo, repo.lockDirCache(),
1342 commit.getTree());
1343 }
1344 dco.setFailOnConflict(false);
1345 dco.checkout();
1346 walk.close();
1347 } finally {
1348 monitor.endTask();
1349 }
1350 try {
1351 String headName = rebaseState.readFile(HEAD_NAME);
1352 monitor.beginTask(MessageFormat.format(
1353 JGitText.get().resettingHead, headName),
1354 ProgressMonitor.UNKNOWN);
1355
1356 Result res = null;
1357 RefUpdate refUpdate = repo.updateRef(Constants.HEAD, false);
1358 refUpdate.setRefLogMessage("rebase: aborting", false);
1359 if (headName.startsWith(Constants.R_REFS)) {
1360
1361 res = refUpdate.link(headName);
1362 } else {
1363 refUpdate.setNewObjectId(origHead);
1364 res = refUpdate.forceUpdate();
1365
1366 }
1367 switch (res) {
1368 case FAST_FORWARD:
1369 case FORCED:
1370 case NO_CHANGE:
1371 break;
1372 default:
1373 throw new JGitInternalException(
1374 JGitText.get().abortingRebaseFailed);
1375 }
1376 boolean stashConflicts = autoStashApply();
1377
1378 FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
1379 repo.writeCherryPickHead(null);
1380 repo.writeMergeHeads(null);
1381 if (stashConflicts)
1382 return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
1383 return result;
1384
1385 } finally {
1386 monitor.endTask();
1387 }
1388 }
1389
1390 private ObjectId getOriginalHead() throws IOException {
1391 try {
1392 return ObjectId.fromString(rebaseState.readFile(REBASE_HEAD));
1393 } catch (FileNotFoundException e) {
1394 try {
1395 return ObjectId
1396 .fromString(rebaseState.readFile(REBASE_HEAD_LEGACY));
1397 } catch (FileNotFoundException ex) {
1398 return repo.readOrigHead();
1399 }
1400 }
1401 }
1402
1403 private boolean checkoutCommit(String headName, RevCommit commit)
1404 throws IOException,
1405 CheckoutConflictException {
1406 try {
1407 RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD));
1408 DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(),
1409 repo.lockDirCache(), commit.getTree());
1410 dco.setFailOnConflict(true);
1411 dco.setProgressMonitor(monitor);
1412 try {
1413 dco.checkout();
1414 } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) {
1415 throw new CheckoutConflictException(dco.getConflicts(), cce);
1416 }
1417
1418 RefUpdate refUpdate = repo.updateRef(Constants.HEAD, true);
1419 refUpdate.setExpectedOldObjectId(head);
1420 refUpdate.setNewObjectId(commit);
1421 refUpdate.setRefLogMessage(
1422 "checkout: moving from "
1423 + Repository.shortenRefName(headName)
1424 + " to " + commit.getName(), false);
1425 Result res = refUpdate.forceUpdate();
1426 switch (res) {
1427 case FAST_FORWARD:
1428 case NO_CHANGE:
1429 case FORCED:
1430 break;
1431 default:
1432 throw new IOException(
1433 JGitText.get().couldNotRewindToUpstreamCommit);
1434 }
1435 } finally {
1436 walk.close();
1437 monitor.endTask();
1438 }
1439 return true;
1440 }
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450 public RebaseCommand setUpstream(RevCommit upstream) {
1451 this.upstreamCommit = upstream;
1452 this.upstreamCommitName = upstream.name();
1453 return this;
1454 }
1455
1456
1457
1458
1459
1460
1461
1462
1463 public RebaseCommand setUpstream(AnyObjectId upstream) {
1464 try {
1465 this.upstreamCommit = walk.parseCommit(upstream);
1466 this.upstreamCommitName = upstream.name();
1467 } catch (IOException e) {
1468 throw new JGitInternalException(MessageFormat.format(
1469 JGitText.get().couldNotReadObjectWhileParsingCommit,
1470 upstream.name()), e);
1471 }
1472 return this;
1473 }
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483 public RebaseCommand setUpstream(String upstream)
1484 throws RefNotFoundException {
1485 try {
1486 ObjectId upstreamId = repo.resolve(upstream);
1487 if (upstreamId == null)
1488 throw new RefNotFoundException(MessageFormat.format(JGitText
1489 .get().refNotResolved, upstream));
1490 upstreamCommit = walk.parseCommit(repo.resolve(upstream));
1491 upstreamCommitName = upstream;
1492 return this;
1493 } catch (IOException ioe) {
1494 throw new JGitInternalException(ioe.getMessage(), ioe);
1495 }
1496 }
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506 public RebaseCommand setUpstreamName(String upstreamName) {
1507 if (upstreamCommit == null) {
1508 throw new IllegalStateException(
1509 "setUpstreamName must be called after setUpstream.");
1510 }
1511 this.upstreamCommitName = upstreamName;
1512 return this;
1513 }
1514
1515
1516
1517
1518
1519
1520
1521
1522 public RebaseCommand setOperation(Operation operation) {
1523 this.operation = operation;
1524 return this;
1525 }
1526
1527
1528
1529
1530
1531
1532
1533
1534 public RebaseCommand setProgressMonitor(ProgressMonitor monitor) {
1535 if (monitor == null) {
1536 monitor = NullProgressMonitor.INSTANCE;
1537 }
1538 this.monitor = monitor;
1539 return this;
1540 }
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557 public RebaseCommand runInteractively(InteractiveHandler handler) {
1558 return runInteractively(handler, false);
1559 }
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578 public RebaseCommand runInteractively(InteractiveHandler handler,
1579 final boolean stopAfterRebaseInteractiveInitialization) {
1580 this.stopAfterInitialization = stopAfterRebaseInteractiveInitialization;
1581 this.interactiveHandler = handler;
1582 return this;
1583 }
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593 public RebaseCommand setStrategy(MergeStrategy strategy) {
1594 this.strategy = strategy;
1595 return this;
1596 }
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608 public RebaseCommand setContentMergeStrategy(ContentMergeStrategy strategy) {
1609 this.contentStrategy = strategy;
1610 return this;
1611 }
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622 public RebaseCommand setPreserveMerges(boolean preserve) {
1623 this.preserveMerges = preserve;
1624 return this;
1625 }
1626
1627
1628
1629
1630 public interface InteractiveHandler {
1631
1632
1633
1634
1635
1636
1637 void prepareSteps(List<RebaseTodoLine> steps);
1638
1639
1640
1641
1642
1643
1644
1645 String modifyCommitMessage(String commit);
1646 }
1647
1648
1649 PersonIdent parseAuthor(byte[] raw) {
1650 if (raw.length == 0)
1651 return null;
1652
1653 Map<String, String> keyValueMap = new HashMap<>();
1654 for (int p = 0; p < raw.length;) {
1655 int end = RawParseUtils.nextLF(raw, p);
1656 if (end == p)
1657 break;
1658 int equalsIndex = RawParseUtils.next(raw, p, '=');
1659 if (equalsIndex == end)
1660 break;
1661 String key = RawParseUtils.decode(raw, p, equalsIndex - 1);
1662 String value = RawParseUtils.decode(raw, equalsIndex + 1, end - 2);
1663 p = end;
1664 keyValueMap.put(key, value);
1665 }
1666
1667 String name = keyValueMap.get(GIT_AUTHOR_NAME);
1668 String email = keyValueMap.get(GIT_AUTHOR_EMAIL);
1669 String time = keyValueMap.get(GIT_AUTHOR_DATE);
1670
1671
1672 int timeStart = 0;
1673 if (time.startsWith("@"))
1674 timeStart = 1;
1675 else
1676 timeStart = 0;
1677 long when = Long
1678 .parseLong(time.substring(timeStart, time.indexOf(' '))) * 1000;
1679 String tzOffsetString = time.substring(time.indexOf(' ') + 1);
1680 int multiplier = -1;
1681 if (tzOffsetString.charAt(0) == '+')
1682 multiplier = 1;
1683 int hours = Integer.parseInt(tzOffsetString.substring(1, 3));
1684 int minutes = Integer.parseInt(tzOffsetString.substring(3, 5));
1685
1686
1687 int tz = (hours * 60 + minutes) * multiplier;
1688 if (name != null && email != null)
1689 return new PersonIdent(name, email, when, tz);
1690 return null;
1691 }
1692
1693 private static class RebaseState {
1694
1695 private final File repoDirectory;
1696 private File dir;
1697
1698 public RebaseState(File repoDirectory) {
1699 this.repoDirectory = repoDirectory;
1700 }
1701
1702 public File getDir() {
1703 if (dir == null) {
1704 File rebaseApply = new File(repoDirectory, REBASE_APPLY);
1705 if (rebaseApply.exists()) {
1706 dir = rebaseApply;
1707 } else {
1708 File rebaseMerge = new File(repoDirectory, REBASE_MERGE);
1709 dir = rebaseMerge;
1710 }
1711 }
1712 return dir;
1713 }
1714
1715
1716
1717
1718
1719 public File getRewrittenDir() {
1720 return new File(getDir(), REWRITTEN);
1721 }
1722
1723 public String readFile(String name) throws IOException {
1724 try {
1725 return readFile(getDir(), name);
1726 } catch (FileNotFoundException e) {
1727 if (ONTO_NAME.equals(name)) {
1728
1729
1730
1731 File oldFile = getFile(ONTO_NAME.replace('_', '-'));
1732 if (oldFile.exists()) {
1733 return readFile(oldFile);
1734 }
1735 }
1736 throw e;
1737 }
1738 }
1739
1740 public void createFile(String name, String content) throws IOException {
1741 createFile(getDir(), name, content);
1742 }
1743
1744 public File getFile(String name) {
1745 return new File(getDir(), name);
1746 }
1747
1748 public String getPath(String name) {
1749 return (getDir().getName() + "/" + name);
1750 }
1751
1752 private static String readFile(File file) throws IOException {
1753 byte[] content = IO.readFully(file);
1754
1755 int end = RawParseUtils.prevLF(content, content.length);
1756 return RawParseUtils.decode(content, 0, end + 1);
1757 }
1758
1759 private static String readFile(File directory, String fileName)
1760 throws IOException {
1761 return readFile(new File(directory, fileName));
1762 }
1763
1764 private static void createFile(File parentDir, String name,
1765 String content)
1766 throws IOException {
1767 File file = new File(parentDir, name);
1768 try (FileOutputStream fos = new FileOutputStream(file)) {
1769 fos.write(content.getBytes(UTF_8));
1770 fos.write('\n');
1771 }
1772 }
1773
1774 private static void appendToFile(File file, String content)
1775 throws IOException {
1776 try (FileOutputStream fos = new FileOutputStream(file, true)) {
1777 fos.write(content.getBytes(UTF_8));
1778 fos.write('\n');
1779 }
1780 }
1781 }
1782 }