Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:\Users\malt\AppData\Local\Temp\7908805853015958247\.git\objects\pack\pack-9cc3ec0769e34546bb7683f4e22ef67b3c800444.idx due to AccessDeniedException using Files.detlete().
Trying toFile().delete(): true
Failed to delete C:\Users\malt\AppData\Local\Temp\7908805853015958247\.git\objects\pack\pack-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?

like image 898
Malt Avatar asked Jul 23 '17 14:07

Malt


2 Answers

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?

like image 140
Rüdiger Herrmann Avatar answered Nov 27 '22 22:11

Rüdiger Herrmann


If somebody still faces the same issue, what worked for me is to force calling gc to release the files locking

gitReference.gc().call()

Here is the full close method of mine:

public void close() {
    if (gitReference != null) {
       try {            
            gitReference.close();           
            gitReference.gc().call();
        } catch (GitAPIException e) {
            throw new RuntimeException(e);
        }
        gitReference = null;
    }
}

Good Luck

Jalal

like image 27
Jalal Kiswani Avatar answered Nov 27 '22 23:11

Jalal Kiswani