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