From 09d96f8d46a31e23fbe5a7b5d8114ae9aaba5056 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Mon, 8 May 2017 08:48:40 +0200 Subject: [PATCH] Clean up the disk when cloning fails CloneCommand.call() has three stages: preparation, then the actual clone (init/fetch), and finally maybe checking out the working directory. Restructure such that if we fail or are cancelled during the actual clone (middle phase), we do clean up the disk again. This prevents leaving behind a partial clone in an inconsistent state: either we have a fully successfully built clone, or nothing at all. Bug: 516303 Change-Id: I9b18c60f8f99816d42a3deb7d4a33a9f22eeb709 Signed-off-by: Thomas Wolf --- .../org/eclipse/jgit/api/CloneCommand.java | 92 ++++++++++++++++--- 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index 2deff07f1..63ca4289a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2013 Chris Aniszczyk + * Copyright (C) 2011, 2017 Chris Aniszczyk * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -76,6 +76,8 @@ import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; /** * Clone a repository into a new working directory @@ -109,6 +111,10 @@ public class CloneCommand extends TransportCommand { private Callback callback; + private boolean directoryExistsInitially; + + private boolean gitDirExistsInitially; + /** * Callback for status of clone operation. * @@ -167,26 +173,51 @@ public CloneCommand() { @Override public Git call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { - Repository repository = null; + URIish u = null; try { - URIish u = new URIish(uri); - repository = init(u); - FetchResult result = fetch(repository, u); - if (!noCheckout) - checkout(repository, result); - return new Git(repository, true); + u = new URIish(uri); + verifyDirectories(u); + } catch (URISyntaxException e) { + throw new InvalidRemoteException( + MessageFormat.format(JGitText.get().invalidURL, uri)); + } + Repository repository = null; + FetchResult fetchResult = null; + try { + repository = init(); + fetchResult = fetch(repository, u); } catch (IOException ioe) { if (repository != null) { repository.close(); } + cleanup(); throw new JGitInternalException(ioe.getMessage(), ioe); } catch (URISyntaxException e) { if (repository != null) { repository.close(); } + cleanup(); throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote)); + } catch (GitAPIException | RuntimeException e) { + if (repository != null) { + repository.close(); + } + cleanup(); + throw e; } + if (!noCheckout) { + try { + checkout(repository, fetchResult); + } catch (IOException ioe) { + repository.close(); + throw new JGitInternalException(ioe.getMessage(), ioe); + } catch (GitAPIException | RuntimeException e) { + repository.close(); + throw e; + } + } + return new Git(repository, true); } private static boolean isNonEmptyDirectory(File dir) { @@ -197,12 +228,12 @@ private static boolean isNonEmptyDirectory(File dir) { return false; } - private Repository init(URIish u) throws GitAPIException { - InitCommand command = Git.init(); - command.setBare(bare); + private void verifyDirectories(URIish u) { if (directory == null && gitDir == null) { directory = new File(u.getHumanishName(), Constants.DOT_GIT); } + directoryExistsInitially = directory != null && directory.exists(); + gitDirExistsInitially = gitDir != null && gitDir.exists(); validateDirs(directory, gitDir, bare); if (isNonEmptyDirectory(directory)) { throw new JGitInternalException(MessageFormat.format( @@ -212,6 +243,11 @@ private Repository init(URIish u) throws GitAPIException { throw new JGitInternalException(MessageFormat.format( JGitText.get().cloneNonEmptyDirectory, gitDir.getName())); } + } + + private Repository init() throws GitAPIException { + InitCommand command = Git.init(); + command.setBare(bare); if (directory != null) { command.setDirectory(directory); } @@ -602,4 +638,38 @@ private static void validateDirs(File directory, File gitDir, boolean bare) } } } + + private void cleanup() { + try { + if (directory != null) { + if (!directoryExistsInitially) { + FileUtils.delete(directory, FileUtils.RECURSIVE + | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS); + } else { + deleteChildren(directory); + } + } + if (gitDir != null) { + if (!gitDirExistsInitially) { + FileUtils.delete(gitDir, FileUtils.RECURSIVE + | FileUtils.SKIP_MISSING | FileUtils.IGNORE_ERRORS); + } else { + deleteChildren(directory); + } + } + } catch (IOException e) { + // Ignore; this is a best-effort cleanup in error cases, and + // IOException should not be raised anyway + } + } + + private void deleteChildren(File file) throws IOException { + if (!FS.DETECTED.isDirectory(file)) { + return; + } + for (File child : file.listFiles()) { + FileUtils.delete(child, FileUtils.RECURSIVE | FileUtils.SKIP_MISSING + | FileUtils.IGNORE_ERRORS); + } + } }