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