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