Extend FileUtils.rename to common git semantics
Unlike the OS or Java rename this method will (on *nix) try (on Windows) replace the target with the source provided the target does not exist, the target does exist and is a file, or if it is a directory which only contains directories. In the latter case the directory hierarchy will be deleted. If the initial rename fails and the target is an existing file the the target file will be deleted first and then the rename is retried. Change-Id: Iae75c49c85445ada7795246a02ce02f7c248d956 Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
This commit is contained in:
parent
7aa54967a2
commit
d0e92885e9
|
@ -45,6 +45,7 @@
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
@ -52,6 +53,7 @@
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -321,4 +323,71 @@ public void testDeleteNotEmptyTreeNotOkButIgnoreFail() throws IOException {
|
|||
assertTrue(f.exists());
|
||||
assertFalse(e.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenameOverNonExistingFile() throws IOException {
|
||||
File d = new File(trash, "d");
|
||||
FileUtils.mkdirs(d);
|
||||
File f1 = new File(trash, "d/f");
|
||||
File f2 = new File(trash, "d/g");
|
||||
JGitTestUtil.write(f1, "f1");
|
||||
// test
|
||||
FileUtils.rename(f1, f2);
|
||||
assertFalse(f1.exists());
|
||||
assertTrue(f2.exists());
|
||||
assertEquals("f1", JGitTestUtil.read(f2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenameOverExistingFile() throws IOException {
|
||||
File d = new File(trash, "d");
|
||||
FileUtils.mkdirs(d);
|
||||
File f1 = new File(trash, "d/f");
|
||||
File f2 = new File(trash, "d/g");
|
||||
JGitTestUtil.write(f1, "f1");
|
||||
JGitTestUtil.write(f2, "f2");
|
||||
// test
|
||||
FileUtils.rename(f1, f2);
|
||||
assertFalse(f1.exists());
|
||||
assertTrue(f2.exists());
|
||||
assertEquals("f1", JGitTestUtil.read(f2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenameOverExistingNonEmptyDirectory() throws IOException {
|
||||
File d = new File(trash, "d");
|
||||
FileUtils.mkdirs(d);
|
||||
File f1 = new File(trash, "d/f");
|
||||
File f2 = new File(trash, "d/g");
|
||||
File d1 = new File(trash, "d/g/h/i");
|
||||
File f3 = new File(trash, "d/g/h/f");
|
||||
FileUtils.mkdirs(d1);
|
||||
JGitTestUtil.write(f1, "f1");
|
||||
JGitTestUtil.write(f3, "f3");
|
||||
// test
|
||||
try {
|
||||
FileUtils.rename(f1, f2);
|
||||
fail("rename to non-empty directory should fail");
|
||||
} catch (IOException e) {
|
||||
assertEquals("f1", JGitTestUtil.read(f1)); // untouched source
|
||||
assertEquals("f3", JGitTestUtil.read(f3)); // untouched
|
||||
// empty directories within f2 may or may not have been deleted
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenameOverExistingEmptyDirectory() throws IOException {
|
||||
File d = new File(trash, "d");
|
||||
FileUtils.mkdirs(d);
|
||||
File f1 = new File(trash, "d/f");
|
||||
File f2 = new File(trash, "d/g");
|
||||
File d1 = new File(trash, "d/g/h/i");
|
||||
FileUtils.mkdirs(d1);
|
||||
JGitTestUtil.write(f1, "f1");
|
||||
// test
|
||||
FileUtils.rename(f1, f2);
|
||||
assertFalse(f1.exists());
|
||||
assertTrue(f2.exists());
|
||||
assertEquals("f1", JGitTestUtil.read(f2));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,7 +171,14 @@ public static void delete(final File f, int options) throws IOException {
|
|||
* Rename a file or folder. If the rename fails and if we are running on a
|
||||
* filesystem where it makes sense to repeat a failing rename then repeat
|
||||
* the rename operation up to 9 times with 100ms sleep time between two
|
||||
* calls
|
||||
* calls. Furthermore if the destination exists and is directory hierarchy
|
||||
* with only directories in it, the whole directory hierarchy will be
|
||||
* deleted. If the target represents a non-empty directory structure, empty
|
||||
* subdirectories within that structure may or may not be deleted even if
|
||||
* the method fails. Furthermore if the destination exists and is a file
|
||||
* then the file will be deleted and then the rename is retried.
|
||||
* <p>
|
||||
* This operation is <em>not</me> atomic.
|
||||
*
|
||||
* @see FS#retryFailedLockFileCommit()
|
||||
* @param src
|
||||
|
@ -188,6 +195,15 @@ public static void rename(final File src, final File dst)
|
|||
while (--attempts >= 0) {
|
||||
if (src.renameTo(dst))
|
||||
return;
|
||||
try {
|
||||
if (!dst.delete())
|
||||
delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
|
||||
// On *nix there is no try, you do or do not
|
||||
if (src.renameTo(dst))
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
// ignore and continue retry
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
Loading…
Reference in New Issue