Support aborting non-interactive rebase started from C Git

Continuing is trickier, as .git/rebase-apply contains no message file
and no git-rebase-todo.

Bug: 336820
Change-Id: I4eb87c850078ca187b38b81cc91c92afb1176945
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 27f41b5..94577a0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
+ * Copyright (C) 2010, 2013 Mathias Kinzler <mathias.kinzler@sap.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -79,6 +79,7 @@
 import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.FileUtils;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -1507,6 +1508,27 @@ public void testRebaseShouldLeaveWorkspaceUntouchedWithUnstagedChangesConflict()
 	}
 
 	@Test
+	public void testAbortShouldAlsoAbortNonInteractiveRebaseWithRebaseApplyDir()
+			throws Exception {
+		writeTrashFile(FILE1, "initial file");
+		git.add().addFilepattern(FILE1).call();
+		git.commit().setMessage("initial commit").call();
+
+		File applyDir = new File(db.getDirectory(), "rebase-apply");
+		File headName = new File(applyDir, "head-name");
+		FileUtils.mkdir(applyDir);
+		write(headName, "master");
+		db.writeOrigHead(db.resolve(Constants.HEAD));
+
+		git.rebase().setOperation(Operation.ABORT).call();
+
+		assertFalse("Abort should clean up .git/rebase-apply",
+				applyDir.exists());
+		assertEquals(RepositoryState.SAFE, git.getRepository()
+				.getRepositoryState());
+	}
+
+	@Test
 	public void testRebaseShouldBeAbleToHandleEmptyLinesInRebaseTodoFile()
 			throws IOException {
 		String emptyLine = "\n";
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index a100c9d..a206a3b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
+ * Copyright (C) 2010, 2013 Mathias Kinzler <mathias.kinzler@sap.com>
  * and other copyright owners as documented in the project's IP log.
  *
  * This program and the accompanying materials are made available
@@ -108,11 +108,16 @@
  */
 public class RebaseCommand extends GitCommand<RebaseResult> {
 	/**
-	 * The name of the "rebase-merge" folder
+	 * The name of the "rebase-merge" folder for interactive rebases.
 	 */
 	public static final String REBASE_MERGE = "rebase-merge"; //$NON-NLS-1$
 
 	/**
+	 * The name of the "rebase-apply" folder for non-interactive rebases.
+	 */
+	private static final String REBASE_APPLY = "rebase-apply"; //$NON-NLS-1$
+
+	/**
 	 * The name of the "stopped-sha" file
 	 */
 	public static final String STOPPED_SHA = "stopped-sha"; //$NON-NLS-1$
@@ -177,7 +182,7 @@ public enum Operation {
 
 	private final RevWalk walk;
 
-	private final File rebaseDir;
+	private final RebaseState rebaseState;
 
 	private InteractiveHandler interactiveHandler;
 
@@ -187,7 +192,7 @@ public enum Operation {
 	protected RebaseCommand(Repository repo) {
 		super(repo);
 		walk = new RevWalk(repo);
-		rebaseDir = new File(repo.getDirectory(), REBASE_MERGE);
+		rebaseState = new RebaseState(repo.getDirectory());
 	}
 
 	/**
@@ -219,9 +224,9 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
 			case SKIP:
 				// fall through
 			case CONTINUE:
-				String upstreamCommitId = readFile(rebaseDir, ONTO);
+				String upstreamCommitId = rebaseState.readFile(ONTO);
 				try {
-					upstreamCommitName = readFile(rebaseDir, ONTO_NAME);
+					upstreamCommitName = rebaseState.readFile(ONTO_NAME);
 				} catch (FileNotFoundException e) {
 					// Fall back to commit ID if file doesn't exist (e.g. rebase
 					// was started by C Git)
@@ -242,7 +247,7 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
 			if (operation == Operation.CONTINUE) {
 				newHead = continueRebase();
 
-				File amendFile = new File(rebaseDir, AMEND);
+				File amendFile = rebaseState.getFile(AMEND);
 				boolean amendExists = amendFile.exists();
 				if (amendExists) {
 					FileUtils.delete(amendFile);
@@ -265,9 +270,9 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
 			List<Step> steps = loadSteps();
 			if (isInteractive()) {
 				interactiveHandler.prepareSteps(steps);
-				BufferedWriter fw = new BufferedWriter(
-						new OutputStreamWriter(new FileOutputStream(new File(
-								rebaseDir, GIT_REBASE_TODO)),
+				BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(
+						new FileOutputStream(
+								rebaseState.getFile(GIT_REBASE_TODO)),
 								Constants.CHARACTER_ENCODING));
 				fw.newLine();
 				try {
@@ -339,7 +344,7 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
 								.setAmend(true).call();
 						continue;
 					case EDIT:
-						createFile(rebaseDir, AMEND, commitToPick.name());
+						rebaseState.createFile(AMEND, commitToPick.name());
 						return stop(commitToPick);
 					}
 				} finally {
@@ -347,9 +352,9 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
 				}
 			}
 			if (newHead != null) {
-				String headName = readFile(rebaseDir, HEAD_NAME);
+				String headName = rebaseState.readFile(HEAD_NAME);
 				updateHead(headName, newHead);
-				FileUtils.delete(rebaseDir, FileUtils.RECURSIVE);
+				FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
 				if (lastStepWasForward)
 					return RebaseResult.FAST_FORWARD_RESULT;
 				return RebaseResult.OK_RESULT;
@@ -458,7 +463,7 @@ private RevCommit continueRebase() throws GitAPIException, IOException {
 
 		if (needsCommit) {
 			CommitCommand commit = new Git(repo).commit();
-			commit.setMessage(readFile(rebaseDir, MESSAGE));
+			commit.setMessage(rebaseState.readFile(MESSAGE));
 			commit.setAuthor(parseAuthor());
 			return commit.call();
 		}
@@ -466,7 +471,7 @@ private RevCommit continueRebase() throws GitAPIException, IOException {
 	}
 
 	private PersonIdent parseAuthor() throws IOException {
-		File authorScriptFile = new File(rebaseDir, AUTHOR_SCRIPT);
+		File authorScriptFile = rebaseState.getFile(AUTHOR_SCRIPT);
 		byte[] raw;
 		try {
 			raw = IO.readFully(authorScriptFile);
@@ -479,15 +484,17 @@ private PersonIdent parseAuthor() throws IOException {
 	private RebaseResult stop(RevCommit commitToPick) throws IOException {
 		PersonIdent author = commitToPick.getAuthorIdent();
 		String authorScript = toAuthorScript(author);
-		createFile(rebaseDir, AUTHOR_SCRIPT, authorScript);
-		createFile(rebaseDir, MESSAGE, commitToPick.getFullMessage());
+		rebaseState.createFile(AUTHOR_SCRIPT, authorScript);
+		rebaseState.createFile(MESSAGE, commitToPick.getFullMessage());
 		ByteArrayOutputStream bos = new ByteArrayOutputStream();
 		DiffFormatter df = new DiffFormatter(bos);
 		df.setRepository(repo);
 		df.format(commitToPick.getParent(0), commitToPick);
-		createFile(rebaseDir, PATCH, new String(bos.toByteArray(),
+		rebaseState.createFile(PATCH, new String(bos.toByteArray(),
 				Constants.CHARACTER_ENCODING));
-		createFile(rebaseDir, STOPPED_SHA, repo.newObjectReader().abbreviate(
+		rebaseState.createFile(STOPPED_SHA,
+				repo.newObjectReader()
+				.abbreviate(
 				commitToPick).name());
 		// Remove cherry pick state file created by CherryPickCommand, it's not
 		// needed for rebase
@@ -531,8 +538,8 @@ private void popSteps(int numSteps) throws IOException {
 			return;
 		List<String> todoLines = new ArrayList<String>();
 		List<String> poppedLines = new ArrayList<String>();
-		File todoFile = new File(rebaseDir, GIT_REBASE_TODO);
-		File doneFile = new File(rebaseDir, DONE);
+		File todoFile = rebaseState.getFile(GIT_REBASE_TODO);
+		File doneFile = rebaseState.getFile(DONE);
 		BufferedReader br = new BufferedReader(new InputStreamReader(
 				new FileInputStream(todoFile), Constants.CHARACTER_ENCODING));
 		try {
@@ -646,16 +653,16 @@ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
 
 		Collections.reverse(cherryPickList);
 		// create the folder for the meta information
-		FileUtils.mkdir(rebaseDir);
+		FileUtils.mkdir(rebaseState.getDir());
 
 		repo.writeOrigHead(headId);
-		createFile(rebaseDir, REBASE_HEAD, headId.name());
-		createFile(rebaseDir, HEAD_NAME, headName);
-		createFile(rebaseDir, ONTO, upstreamCommit.name());
-		createFile(rebaseDir, ONTO_NAME, upstreamCommitName);
-		createFile(rebaseDir, INTERACTIVE, ""); //$NON-NLS-1$
+		rebaseState.createFile(REBASE_HEAD, headId.name());
+		rebaseState.createFile(HEAD_NAME, headName);
+		rebaseState.createFile(ONTO, upstreamCommit.name());
+		rebaseState.createFile(ONTO_NAME, upstreamCommitName);
+		rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$
 		BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(
-				new FileOutputStream(new File(rebaseDir, GIT_REBASE_TODO)),
+				new FileOutputStream(rebaseState.getFile(GIT_REBASE_TODO)),
 				Constants.CHARACTER_ENCODING));
 		fw.write("# Created by EGit: rebasing " + upstreamCommit.name()
 				+ " onto " + headId.name());
@@ -687,7 +694,7 @@ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
 			checkoutOk = checkoutCommit(upstreamCommit);
 		} finally {
 			if (!checkoutOk)
-				FileUtils.delete(rebaseDir, FileUtils.RECURSIVE);
+				FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
 		}
 		monitor.endTask();
 
@@ -799,18 +806,6 @@ private void checkParameters() throws WrongRepositoryStateException {
 			}
 	}
 
-	private void createFile(File parentDir, String name, String content)
-			throws IOException {
-		File file = new File(parentDir, name);
-		FileOutputStream fos = new FileOutputStream(file);
-		try {
-			fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
-			fos.write('\n');
-		} finally {
-			fos.close();
-		}
-	}
-
 	private RebaseResult abort(RebaseResult result) throws IOException {
 		try {
 			ObjectId origHead = repo.readOrigHead();
@@ -840,7 +835,7 @@ private RebaseResult abort(RebaseResult result) throws IOException {
 			monitor.endTask();
 		}
 		try {
-			String headName = readFile(rebaseDir, HEAD_NAME);
+			String headName = rebaseState.readFile(HEAD_NAME);
 			if (headName.startsWith(Constants.R_REFS)) {
 				monitor.beginTask(MessageFormat.format(
 						JGitText.get().resettingHead, headName),
@@ -860,7 +855,7 @@ private RebaseResult abort(RebaseResult result) throws IOException {
 				}
 			}
 			// cleanup the files
-			FileUtils.delete(rebaseDir, FileUtils.RECURSIVE);
+			FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
 			repo.writeCherryPickHead(null);
 			return result;
 
@@ -869,15 +864,6 @@ private RebaseResult abort(RebaseResult result) throws IOException {
 		}
 	}
 
-	private String readFile(File directory, String fileName) throws IOException {
-		byte[] content = IO.readFully(new File(directory, fileName));
-		// strip off the last LF
-		int end = content.length;
-		while (0 < end && content[end - 1] == '\n')
-			end--;
-		return RawParseUtils.decode(content, 0, end);
-	}
-
 	private boolean checkoutCommit(RevCommit commit) throws IOException,
 			CheckoutConflictException {
 		try {
@@ -911,7 +897,7 @@ private boolean checkoutCommit(RevCommit commit) throws IOException,
 	}
 
 	List<Step> loadSteps() throws IOException {
-		byte[] buf = IO.readFully(new File(rebaseDir, GIT_REBASE_TODO));
+		byte[] buf = IO.readFully(rebaseState.getFile(GIT_REBASE_TODO));
 		int ptr = 0;
 		int tokenBegin = 0;
 		ArrayList<Step> r = new ArrayList<Step>();
@@ -1225,4 +1211,62 @@ PersonIdent parseAuthor(byte[] raw) {
 			return new PersonIdent(name, email, when, tz);
 		return null;
 	}
+
+	private static class RebaseState {
+
+		private final File repoDirectory;
+		private File dir;
+
+		public RebaseState(File repoDirectory) {
+			this.repoDirectory = repoDirectory;
+		}
+
+		public File getDir() {
+			if (dir == null) {
+				File rebaseApply = new File(repoDirectory, REBASE_APPLY);
+				if (rebaseApply.exists()) {
+					dir = rebaseApply;
+				} else {
+					File rebaseMerge = new File(repoDirectory, REBASE_MERGE);
+					dir = rebaseMerge;
+				}
+			}
+			return dir;
+		}
+
+		public String readFile(String name) throws IOException {
+			return readFile(getDir(), name);
+		}
+
+		public void createFile(String name, String content) throws IOException {
+			createFile(getDir(), name, content);
+		}
+
+		public File getFile(String name) {
+			return new File(getDir(), name);
+		}
+
+		private static String readFile(File directory, String fileName)
+				throws IOException {
+			byte[] content = IO.readFully(new File(directory, fileName));
+			// strip off the last LF
+			int end = content.length;
+			while (0 < end && content[end - 1] == '\n')
+				end--;
+			return RawParseUtils.decode(content, 0, end);
+		}
+
+		private static void createFile(File parentDir, String name,
+				String content)
+				throws IOException {
+			File file = new File(parentDir, name);
+			FileOutputStream fos = new FileOutputStream(file);
+			try {
+				fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
+				fos.write('\n');
+			} finally {
+				fos.close();
+			}
+		}
+	}
 }