]> source.dussan.org Git - jgit.git/commitdiff
Extend FileUtils.rename to common git semantics 14/11414/3
authorRobin Rosenberg <robin.rosenberg@dewire.com>
Sun, 24 Mar 2013 00:06:29 +0000 (01:06 +0100)
committerChristian Halstrick <christian.halstrick@sap.com>
Mon, 25 Mar 2013 23:48:00 +0000 (00:48 +0100)
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>
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java
org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java

index d0680cab744020e677e7152e232201fe1a211718..f6f6753c758e4e8c42566651925000a555cd9bea 100644 (file)
@@ -45,6 +45,7 @@ package org.eclipse.jgit.util;
 
 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 static org.junit.Assert.fail;
 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 class FileUtilTest {
                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));
+       }
 }
index dc0bbcb380fcc64cdd81af8b8145fb5f786a8d53..5fbbda7c505ec8cf6b99df67bee1c3dce180bbc0 100644 (file)
@@ -171,7 +171,14 @@ public class FileUtils {
         * 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 class FileUtils {
                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) {