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