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