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