From 548ba66a377e74e4dce35545798738509fea129b Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Thu, 23 Apr 2015 01:11:58 +0200 Subject: [PATCH] Use NIO2 to implement FileUtils.rename() and expose options - use NIO2's Files.move() to reimplement rename() - provide a second method accepting CopyOptions which can be used to request atomic move. Change-Id: Ibcf722978e65745218a1ccda45344ca295911659 Signed-off-by: Matthias Sohn --- .../org/eclipse/jgit/util/FileUtils7Test.java | 12 ++++ .../src/org/eclipse/jgit/util/FileUtils.java | 69 +++++++++++++++---- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java index 9dc5fac5d..4625f3068 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java @@ -48,6 +48,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import org.junit.After; import org.junit.Before; @@ -83,4 +85,14 @@ public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget() assertTrue(dir.exists()); assertTrue(file.exists()); } + + @Test + public void testAtomicMove() throws IOException { + File src = new File(trash, "src"); + Files.createFile(src.toPath()); + File dst = new File(trash, "dst"); + FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); + assertFalse(Files.exists(src.toPath())); + assertTrue(Files.exists(dst.toPath())); + } } 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 df80567d5..56eecc48d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -48,9 +48,12 @@ import java.io.File; import java.io.IOException; import java.nio.channels.FileLock; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.text.Normalizer; import java.text.Normalizer.Form; @@ -212,30 +215,68 @@ public static void delete(final File f, int options) throws IOException { */ public static void rename(final File src, final File dst) throws IOException { + rename(src, dst, StandardCopyOption.REPLACE_EXISTING); + } + + /** + * Rename a file or folder using the passed {@link CopyOption}s. 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. Furthermore if the destination + * exists and is a 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 replaced if + * {@link StandardCopyOption#REPLACE_EXISTING} has been set. If + * {@link StandardCopyOption#ATOMIC_MOVE} has been set the rename will be + * done atomically or fail with an {@link AtomicMoveNotSupportedException} + * + * @param src + * the old file + * @param dst + * the new file + * @param options + * options to pass to + * {@link Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)} + * @throws AtomicMoveNotSupportedException + * if file cannot be moved as an atomic file system operation + * @throws IOException + * @since 4.1 + */ + public static void rename(final File src, final File dst, + CopyOption... options) + throws AtomicMoveNotSupportedException, IOException { int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1; 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; + Files.move(src.toPath(), dst.toPath(), options); + return; + } catch (AtomicMoveNotSupportedException e) { + throw e; } catch (IOException e) { - // ignore and continue retry + try { + if (!dst.delete()) { + delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE); + } + // On *nix there is no try, you do or do not + Files.move(src.toPath(), dst.toPath(), options); + return; + } catch (IOException e2) { + // ignore and continue retry + } } try { Thread.sleep(100); } catch (InterruptedException e) { - throw new IOException(MessageFormat.format( - JGitText.get().renameFileFailed, src.getAbsolutePath(), - dst.getAbsolutePath())); + throw new IOException( + MessageFormat.format(JGitText.get().renameFileFailed, + src.getAbsolutePath(), dst.getAbsolutePath())); } } - throw new IOException(MessageFormat.format( - JGitText.get().renameFileFailed, src.getAbsolutePath(), - dst.getAbsolutePath())); + throw new IOException( + MessageFormat.format(JGitText.get().renameFileFailed, + src.getAbsolutePath(), dst.getAbsolutePath())); } /**