FileUtils: improve delete (Windows)

Ensure files are writable before trying to delete them.

Bug: 408846
Change-Id: I930a547594bba853c33634ae54bd64d236afade3
Signed-off-by: Alexander Nittka <alex@nittka.de>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
index f9ec5d8..2b1fb2e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java
@@ -79,6 +79,15 @@
 	}
 
 	@Test
+	public void testDeleteReadOnlyFile() throws IOException {
+		File f = new File(trash, "f");
+		FileUtils.createNewFile(f);
+		assertTrue(f.setReadOnly());
+		FileUtils.delete(f);
+		assertFalse(f.exists());
+	}
+
+	@Test
 	public void testDeleteRecursive() throws IOException {
 		File f1 = new File(trash, "test/test/a");
 		FileUtils.mkdirs(f1.getParentFile());
@@ -339,6 +348,34 @@
 	}
 
 	@Test
+	public void testDeleteNonRecursiveTreeNotOk() throws IOException {
+		File t = new File(trash, "t");
+		FileUtils.mkdir(t);
+		File f = new File(t, "f");
+		FileUtils.createNewFile(f);
+		try {
+			FileUtils.delete(t, FileUtils.EMPTY_DIRECTORIES_ONLY);
+			fail("expected failure to delete f");
+		} catch (IOException e) {
+			assertTrue(e.getMessage().endsWith(t.getAbsolutePath()));
+		}
+		assertTrue(f.exists());
+		assertTrue(t.exists());
+	}
+
+	@Test
+	public void testDeleteNonRecursiveTreeIgnoreError() throws IOException {
+		File t = new File(trash, "t");
+		FileUtils.mkdir(t);
+		File f = new File(t, "f");
+		FileUtils.createNewFile(f);
+		FileUtils.delete(t,
+				FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.IGNORE_ERRORS);
+		assertTrue(f.exists());
+		assertTrue(t.exists());
+	}
+
+	@Test
 	public void testRenameOverNonExistingFile() throws IOException {
 		File d = new File(trash, "d");
 		FileUtils.mkdirs(d);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index 4831fbb..c43956e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -20,6 +20,7 @@
 import java.nio.channels.FileChannel;
 import java.nio.file.AtomicMoveNotSupportedException;
 import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.LinkOption;
@@ -180,21 +181,31 @@
 		}
 
 		if (delete) {
-			Throwable t = null;
+			IOException t = null;
 			Path p = f.toPath();
-			try {
-				Files.delete(p);
-				return;
-			} catch (FileNotFoundException e) {
-				if ((options & (SKIP_MISSING | IGNORE_ERRORS)) == 0) {
-					throw new IOException(MessageFormat.format(
-							JGitText.get().deleteFileFailed,
-							f.getAbsolutePath()), e);
+			boolean tryAgain;
+			do {
+				tryAgain = false;
+				try {
+					Files.delete(p);
+					return;
+				} catch (NoSuchFileException | FileNotFoundException e) {
+					handleDeleteException(f, e, options,
+							SKIP_MISSING | IGNORE_ERRORS);
+					return;
+				} catch (DirectoryNotEmptyException e) {
+					handleDeleteException(f, e, options, IGNORE_ERRORS);
+					return;
+				} catch (IOException e) {
+					if (!f.canWrite()) {
+						tryAgain = f.setWritable(true);
+					}
+					if (!tryAgain) {
+						t = e;
+					}
 				}
-				return;
-			} catch (IOException e) {
-				t = e;
-			}
+			} while (tryAgain);
+
 			if ((options & RETRY) != 0) {
 				for (int i = 1; i < 10; i++) {
 					try {
@@ -210,11 +221,15 @@
 					}
 				}
 			}
-			if ((options & IGNORE_ERRORS) == 0) {
-				throw new IOException(MessageFormat.format(
-						JGitText.get().deleteFileFailed, f.getAbsolutePath()),
-						t);
-			}
+			handleDeleteException(f, t, options, IGNORE_ERRORS);
+		}
+	}
+
+	private static void handleDeleteException(File f, IOException e,
+			int allOptions, int checkOptions) throws IOException {
+		if (e != null && (allOptions & checkOptions) == 0) {
+			throw new IOException(MessageFormat.format(
+					JGitText.get().deleteFileFailed, f.getAbsolutePath()), e);
 		}
 	}