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