Java (JGIT) Files.delete() fails to delete a file, but file.delete() succeeds

I’m cloning a git repo into a temp directory using jgit (version 4.8.0.201706111038-r) and adding a shutdown hook to delete the temp directory after termination. However, the shutdown hook fails to delete some files from within the .git subdirectory (despite closing the Git object, as required by jgit).

The funny thing though, is that the deletion fails only if I use the Path API (Files.delete(<PATH>)), but not if I delete using the old file.delete().

Here’s a minimal standalone example whose only dependency is jgit 4.8.0.201706111038-r:

public static void main(String... args) throws Exception {
    String gitRepo = "https://github.com/netgloo/spring-boot-samples.git";
    Path localDir = Files.createTempDirectory(null);

    // Clone repo
    Git git = Git.cloneRepository().setURI(gitRepo).setBranch("master").setDirectory(localDir.toFile()).call();

    // Print some stuff to make sure that the git repo actually works
    for (RevCommit c : git.log().call()) {
        System.out.println(c);
    }

    git.getRepository().close(); // Close all the things!
    git.close(); // Close all the things!

    // Delete
    Files.walkFileTree(localDir, new SimpleFileVisitor<Path>() {
        void safeDelete(Path p) {
            try {
                Files.delete(p);
            } catch (Exception e) {
                try {
                    Files.delete(p);
                } catch (Exception e2) {
                    System.err.println("Failed to delete " + p + " due to " + e.getClass().getSimpleName() + " using Files.detlete().nTrying toFile().delete(): " + p.toFile().delete());
                }
            }
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            safeDelete(file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
            safeDelete(dir);
            return FileVisitResult.CONTINUE;
        }
    });
}

Output:

...
Failed to delete C:UsersmaltAppDataLocalTemp7908805853015958247.gitobjectspackpack-9cc3ec0769e34546bb7683f4e22ef67b3c800444.idx due to AccessDeniedException using Files.detlete().
Trying toFile().delete(): true
Failed to delete C:UsersmaltAppDataLocalTemp7908805853015958247.gitobjectspackpack-9cc3ec0769e34546bb7683f4e22ef67b3c800444.pack due to AccessDeniedException using Files.detlete().
Trying toFile().delete(): true

Can anyone explain why this happens? And is there a way to get JGIT to properly close these files so that Files.delete() works?

Answer

When obtained from a CloneCommand, Git::close alone should be enough to release all file handles, held by JGit for a given repository. It simply delegates to Repository::close in this case.

I think the difference between Files::delete() and File::delete() that you are seeing is explained here:

Difference between Files#delete(Path) and File#delete()

Probably unrelated, but I think nonetheless worth mentioning, is an issue with the recently introduced background thread for auto-gc. It may also prevent a repository from being deleted successfully. See this mailing list thread:

https://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03370.html

To work around the latter issue, I disabled auto-gc in the configuration of the repository like this:

StoredConfig config = repository.getConfig();
config.setBoolean(CONFIG_GC_SECTION, null, CONFIG_KEY_AUTODETACH, false);
config.setInt(CONFIG_GC_SECTION, null, CONFIG_KEY_AUTOPACKLIMIT, -1);
config.setInt(CONFIG_GC_SECTION, null, CONFIG_KEY_AUTO, -1);
config.save();

The constants are imported static from ConfigConstants.

Does that match your problem?

Leave a Reply

Your email address will not be published. Required fields are marked *