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