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