From aebfc70cc84b1492cd3f5df04dd9b057d0bec1f7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 9 Jan 2012 08:46:13 -0800 Subject: [PATCH] Provide helper for unlocking a file This will allow recovery from a LockFailedException where the file associated with an exception is passed to FileUtils.unlock to attempt an unlock on the file so the operation can be retried Change-Id: I580166d386126bfb54a318a65253070a6e325936 --- .../jgit/storage/file/LockFileTest.java | 83 +++++++++++++++++++ .../eclipse/jgit/storage/file/LockFile.java | 36 +++++++- 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/LockFileTest.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/LockFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/LockFileTest.java new file mode 100644 index 000000000..57770d322 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/LockFileTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012, GitHub Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.storage.file; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.LockFailedException; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; + +/** + * Unit tests of {@link LockFile} + */ +public class LockFileTest extends RepositoryTestCase { + + @Test + public void lockFailedExceptionRecovery() throws Exception { + Git git = new Git(db); + writeTrashFile("file.txt", "content"); + git.add().addFilepattern("file.txt").call(); + RevCommit commit1 = git.commit().setMessage("create file").call(); + + assertNotNull(commit1); + writeTrashFile("file.txt", "content2"); + git.add().addFilepattern("file.txt").call(); + assertNotNull(git.commit().setMessage("edit file").call()); + + assertTrue(new LockFile(db.getIndexFile(), db.getFS()).lock()); + try { + git.checkout().setName(commit1.name()).call(); + fail("JGitInternalException not thrown"); + } catch (JGitInternalException e) { + assertTrue(e.getCause() instanceof LockFailedException); + LockFile.unlock(((LockFailedException) e.getCause()).getFile()); + git.checkout().setName(commit1.name()).call(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java index 08dfd7e19..dd58fecf5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/LockFile.java @@ -57,6 +57,7 @@ import java.nio.channels.FileChannel; import java.text.MessageFormat; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.util.FS; @@ -75,6 +76,37 @@ import org.eclipse.jgit.util.FileUtils; public class LockFile { static final String SUFFIX = ".lock"; //$NON-NLS-1$ + /** + * Unlock the given file. + *

+ * This method can be used for recovering from a thrown + * {@link LockFailedException} . This method does not validate that the lock + * is or is not currently held before attempting to unlock it. + * + * @param file + * @return true if unlocked, false if unlocking failed + */ + public static boolean unlock(final File file) { + final File lockFile = getLockFile(file); + final int flags = FileUtils.RETRY | FileUtils.SKIP_MISSING; + try { + FileUtils.delete(lockFile, flags); + } catch (IOException ignored) { + // Ignore and return whether lock file still exists + } + return !lockFile.exists(); + } + + /** + * Get the lock file corresponding to the given file. + * + * @param file + * @return lock file + */ + static File getLockFile(File file) { + return new File(file.getParentFile(), file.getName() + SUFFIX); + } + /** Filter to skip over active lock files when listing a directory. */ static final FilenameFilter FILTER = new FilenameFilter() { public boolean accept(File dir, String name) { @@ -107,9 +139,9 @@ public class LockFile { * the file system abstraction which will be necessary to perform * certain file system operations. */ - public LockFile(final File f, FS fs) { + public LockFile(final File f, final FS fs) { ref = f; - lck = new File(ref.getParentFile(), ref.getName() + SUFFIX); + lck = getLockFile(ref); this.fs = fs; }