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