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