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