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