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