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