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 <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2017-05-08 08:48:40 +02:00 committed by Matthias Sohn
parent 3f712aa980
commit 09d96f8d46
1 changed files with 81 additions and 11 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011, 2013 Chris Aniszczyk <caniszczyk@gmail.com> * Copyright (C) 2011, 2017 Chris Aniszczyk <caniszczyk@gmail.com>
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -76,6 +76,8 @@
import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.TagOpt;
import org.eclipse.jgit.transport.URIish; 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 * Clone a repository into a new working directory
@ -109,6 +111,10 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private Callback callback; private Callback callback;
private boolean directoryExistsInitially;
private boolean gitDirExistsInitially;
/** /**
* Callback for status of clone operation. * Callback for status of clone operation.
* *
@ -167,26 +173,51 @@ public CloneCommand() {
@Override @Override
public Git call() throws GitAPIException, InvalidRemoteException, public Git call() throws GitAPIException, InvalidRemoteException,
org.eclipse.jgit.api.errors.TransportException { org.eclipse.jgit.api.errors.TransportException {
Repository repository = null; URIish u = null;
try { try {
URIish u = new URIish(uri); u = new URIish(uri);
repository = init(u); verifyDirectories(u);
FetchResult result = fetch(repository, u); } catch (URISyntaxException e) {
if (!noCheckout) throw new InvalidRemoteException(
checkout(repository, result); MessageFormat.format(JGitText.get().invalidURL, uri));
return new Git(repository, true); }
Repository repository = null;
FetchResult fetchResult = null;
try {
repository = init();
fetchResult = fetch(repository, u);
} catch (IOException ioe) { } catch (IOException ioe) {
if (repository != null) { if (repository != null) {
repository.close(); repository.close();
} }
cleanup();
throw new JGitInternalException(ioe.getMessage(), ioe); throw new JGitInternalException(ioe.getMessage(), ioe);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
if (repository != null) { if (repository != null) {
repository.close(); repository.close();
} }
cleanup();
throw new InvalidRemoteException(MessageFormat.format( throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote)); 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) { private static boolean isNonEmptyDirectory(File dir) {
@ -197,12 +228,12 @@ private static boolean isNonEmptyDirectory(File dir) {
return false; return false;
} }
private Repository init(URIish u) throws GitAPIException { private void verifyDirectories(URIish u) {
InitCommand command = Git.init();
command.setBare(bare);
if (directory == null && gitDir == null) { if (directory == null && gitDir == null) {
directory = new File(u.getHumanishName(), Constants.DOT_GIT); directory = new File(u.getHumanishName(), Constants.DOT_GIT);
} }
directoryExistsInitially = directory != null && directory.exists();
gitDirExistsInitially = gitDir != null && gitDir.exists();
validateDirs(directory, gitDir, bare); validateDirs(directory, gitDir, bare);
if (isNonEmptyDirectory(directory)) { if (isNonEmptyDirectory(directory)) {
throw new JGitInternalException(MessageFormat.format( throw new JGitInternalException(MessageFormat.format(
@ -212,6 +243,11 @@ private Repository init(URIish u) throws GitAPIException {
throw new JGitInternalException(MessageFormat.format( throw new JGitInternalException(MessageFormat.format(
JGitText.get().cloneNonEmptyDirectory, gitDir.getName())); JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
} }
}
private Repository init() throws GitAPIException {
InitCommand command = Git.init();
command.setBare(bare);
if (directory != null) { if (directory != null) {
command.setDirectory(directory); 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);
}
}
} }