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