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