Browse Source

Progress reporting for checkout

The reason for the change is LFS: when using a lot of LFS files,
checkout can take quite some time on larger repositories. To avoid
"hanging" UI, provide progress reporting.

Also implement (partial) progress reporting for cherry-pick, reset,
revert which are using checkout internally.

The feature is also useful without LFS, so it is independent of it.

Change-Id: I021e764241f3c107eaf2771f6b5785245b146b42
Signed-off-by: Markus Duft <markus.duft@ssi-schaefer.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
tags/v4.11.0.201803080745-r
Markus Duft 6 years ago
parent
commit
1c43af8b97

+ 3
- 1
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java View File

import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.internal.CLIText;
import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
} }


try (Git git = new Git(db)) { try (Git git = new Git(db)) {
CheckoutCommand command = git.checkout();
CheckoutCommand command = git.checkout()
.setProgressMonitor(new TextProgressMonitor(errw));
if (paths.size() > 0) { if (paths.size() > 0) {
command.setStartPoint(name); command.setStartPoint(name);
if (paths.size() == 1 && paths.get(0).equals(".")) { //$NON-NLS-1$ if (paths.size() == 1 && paths.get(0).equals(".")) { //$NON-NLS-1$

+ 1
- 0
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties View File

cantPassMeATree=Can't pass me a tree! cantPassMeATree=Can't pass me a tree!
channelMustBeInRange1_255=channel {0} must be in range [1, 255] channelMustBeInRange1_255=channel {0} must be in range [1, 255]
characterClassIsNotSupported=The character class {0} is not supported. characterClassIsNotSupported=The character class {0} is not supported.
checkingOutFiles=Checking out files
checkoutConflictWithFile=Checkout conflict with file: {0} checkoutConflictWithFile=Checkout conflict with file: {0}
checkoutConflictWithFiles=Checkout conflict with files: {0} checkoutConflictWithFiles=Checkout conflict with files: {0}
checkoutUnexpectedResult=Checkout returned unexpected result {0} checkoutUnexpectedResult=Checkout returned unexpected result {0}

+ 19
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java View File

import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.RefUpdate.Result;


private Set<String> actuallyModifiedPaths; private Set<String> actuallyModifiedPaths;


private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

/** /**
* Constructor for CheckoutCommand * Constructor for CheckoutCommand
* *
dco = new DirCacheCheckout(repo, headTree, dc, dco = new DirCacheCheckout(repo, headTree, dc,
newCommit.getTree()); newCommit.getTree());
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.setProgressMonitor(monitor);
try { try {
dco.checkout(); dco.checkout();
} catch (org.eclipse.jgit.errors.CheckoutConflictException e) { } catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
return id.getName(); return id.getName();
} }


/**
* @param monitor
* a progress monitor
* @return this instance
* @since 4.11
*/
public CheckoutCommand setProgressMonitor(ProgressMonitor monitor) {
if (monitor == null) {
monitor = NullProgressMonitor.INSTANCE;
}
this.monitor = monitor;
return this;
}

/** /**
* Add a single slash-separated path to the list of paths to check out. To * Add a single slash-separated path to the list of paths to check out. To
* check out all paths, use {@link #setAllPaths(boolean)}. * check out all paths, use {@link #setAllPaths(boolean)}.

+ 23
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java View File

import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;


private boolean noCommit = false; private boolean noCommit = false;


private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

/** /**
* Constructor for CherryPickCommand * Constructor for CherryPickCommand
* *
newHead.getTree(), repo.lockDirCache(), newHead.getTree(), repo.lockDirCache(),
merger.getResultTreeId()); merger.getResultTreeId());
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.setProgressMonitor(monitor);
dco.checkout(); dco.checkout();
if (!noCommit) if (!noCommit)
newHead = new Git(getRepository()).commit() newHead = new Git(getRepository()).commit()
return this; return this;
} }


/**
* The progress monitor associated with the cherry-pick operation. By
* default, this is set to <code>NullProgressMonitor</code>
*
* @see NullProgressMonitor
* @param monitor
* a {@link org.eclipse.jgit.lib.ProgressMonitor}
* @return {@code this}
* @since 4.11
*/
public CherryPickCommand setProgressMonitor(ProgressMonitor monitor) {
if (monitor == null) {
monitor = NullProgressMonitor.INSTANCE;
}
this.monitor = monitor;
return this;
}

private String calculateOurName(Ref headRef) { private String calculateOurName(Ref headRef) {
if (ourCommitName != null) if (ourCommitName != null)
return ourCommitName; return ourCommitName;

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java View File

DirCache dc = clonedRepo.lockDirCache(); DirCache dc = clonedRepo.lockDirCache();
DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc, DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc,
commit.getTree()); commit.getTree());
co.setProgressMonitor(monitor);
co.checkout(); co.checkout();
if (cloneSubmodules) if (cloneSubmodules)
cloneSubmodules(clonedRepo); cloneSubmodules(clonedRepo);

+ 3
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java View File

dco = new DirCacheCheckout(repo, dco = new DirCacheCheckout(repo,
repo.lockDirCache(), srcCommit.getTree()); repo.lockDirCache(), srcCommit.getTree());
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.setProgressMonitor(monitor);
dco.checkout(); dco.checkout();
RefUpdate refUpdate = repo RefUpdate refUpdate = repo
.updateRef(head.getTarget().getName()); .updateRef(head.getTarget().getName());
dco = new DirCacheCheckout(repo, dco = new DirCacheCheckout(repo,
headCommit.getTree(), repo.lockDirCache(), headCommit.getTree(), repo.lockDirCache(),
srcCommit.getTree()); srcCommit.getTree());
dco.setProgressMonitor(monitor);
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.checkout(); dco.checkout();
String msg = null; String msg = null;
headCommit.getTree(), repo.lockDirCache(), headCommit.getTree(), repo.lockDirCache(),
merger.getResultTreeId()); merger.getResultTreeId());
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.setProgressMonitor(monitor);
dco.checkout(); dco.checkout();


String msg = null; String msg = null;

+ 3
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java View File

try { try {
DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree); DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree);
dco.setFailOnConflict(false); dco.setFailOnConflict(false);
dco.setProgressMonitor(monitor);
boolean needsDeleteFiles = dco.checkout(); boolean needsDeleteFiles = dco.checkout();
if (needsDeleteFiles) { if (needsDeleteFiles) {
List<String> fileList = dco.getToBeDeleted(); List<String> fileList = dco.getToBeDeleted();


CheckoutCommand co = new CheckoutCommand(repo); CheckoutCommand co = new CheckoutCommand(repo);
try { try {
co.setProgressMonitor(monitor);
co.setName(newCommit.name()).call(); co.setName(newCommit.name()).call();
if (headName.startsWith(Constants.R_HEADS)) { if (headName.startsWith(Constants.R_HEADS)) {
RefUpdate rup = repo.updateRef(headName); RefUpdate rup = repo.updateRef(headName);
DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(), DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(),
repo.lockDirCache(), commit.getTree()); repo.lockDirCache(), commit.getTree());
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.setProgressMonitor(monitor);
try { try {
dco.checkout(); dco.checkout();
} catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) {

+ 23
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java View File

import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;


private boolean isReflogDisabled; private boolean isReflogDisabled;


private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

/** /**
* <p> * <p>
* Constructor for ResetCommand. * Constructor for ResetCommand.
return Constants.HEAD; return Constants.HEAD;
} }


/**
* The progress monitor associated with the reset operation. By default,
* this is set to <code>NullProgressMonitor</code>
*
* @see NullProgressMonitor
* @param monitor
* a {@link org.eclipse.jgit.lib.ProgressMonitor}
* @return {@code this}
* @since 4.11
*/
public ResetCommand setProgressMonitor(ProgressMonitor monitor) {
if (monitor == null) {
monitor = NullProgressMonitor.INSTANCE;
}
this.monitor = monitor;
return this;
}

private void resetIndexForPaths(ObjectId commitTree) { private void resetIndexForPaths(ObjectId commitTree) {
DirCache dc = null; DirCache dc = null;
try (final TreeWalk tw = new TreeWalk(repo)) { try (final TreeWalk tw = new TreeWalk(repo)) {
DirCacheCheckout checkout = new DirCacheCheckout(repo, dc, DirCacheCheckout checkout = new DirCacheCheckout(repo, dc,
commitTree); commitTree);
checkout.setFailOnConflict(false); checkout.setFailOnConflict(false);
checkout.setProgressMonitor(monitor);
try { try {
checkout.checkout(); checkout.checkout();
} catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { } catch (org.eclipse.jgit.errors.CheckoutConflictException cce) {

+ 23
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java View File

import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;


private MergeStrategy strategy = MergeStrategy.RECURSIVE; private MergeStrategy strategy = MergeStrategy.RECURSIVE;


private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

/** /**
* <p> * <p>
* Constructor for RevertCommand. * Constructor for RevertCommand.
headCommit.getTree(), repo.lockDirCache(), headCommit.getTree(), repo.lockDirCache(),
merger.getResultTreeId()); merger.getResultTreeId());
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.setProgressMonitor(monitor);
dco.checkout(); dco.checkout();
try (Git git = new Git(getRepository())) { try (Git git = new Git(getRepository())) {
newHead = git.commit().setMessage(newMessage) newHead = git.commit().setMessage(newMessage)
this.strategy = strategy; this.strategy = strategy;
return this; return this;
} }

/**
* The progress monitor associated with the revert operation. By default,
* this is set to <code>NullProgressMonitor</code>
*
* @see NullProgressMonitor
* @param monitor
* a {@link org.eclipse.jgit.lib.ProgressMonitor}
* @return {@code this}
* @since 4.11
*/
public RevertCommand setProgressMonitor(ProgressMonitor monitor) {
if (monitor == null) {
monitor = NullProgressMonitor.INSTANCE;
}
this.monitor = monitor;
return this;
}
} }

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java View File

submoduleRepo, submoduleRepo.lockDirCache(), submoduleRepo, submoduleRepo.lockDirCache(),
commit.getTree()); commit.getTree());
co.setFailOnConflict(true); co.setFailOnConflict(true);
co.setProgressMonitor(monitor);
co.checkout(); co.checkout();
RefUpdate refUpdate = submoduleRepo.updateRef( RefUpdate refUpdate = submoduleRepo.updateRef(
Constants.HEAD, true); Constants.HEAD, true);

+ 40
- 1
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java View File

import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;


import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.FilterFailedException;
import org.eclipse.jgit.attributes.FilterCommand; import org.eclipse.jgit.attributes.FilterCommand;
import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.attributes.FilterCommandRegistry;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.CanonicalTreeParser;


private boolean performingCheckout; private boolean performingCheckout;


private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;

/** /**
* Get list of updated paths and smudgeFilterCommands * Get list of updated paths and smudgeFilterCommands
* *
this(repo, null, dc, mergeCommitTree, new FileTreeIterator(repo)); this(repo, null, dc, mergeCommitTree, new FileTreeIterator(repo));
} }


/**
* Set a progress monitor which can be passed to built-in filter commands,
* providing progress information for long running tasks.
*
* @param monitor
* the {@link ProgressMonitor}
* @since 4.11
*/
public void setProgressMonitor(ProgressMonitor monitor) {
this.monitor = monitor != null ? monitor : NullProgressMonitor.INSTANCE;
}

/** /**
* Scan head, index and merge tree. Used during normal checkout or merge * Scan head, index and merge tree. Used during normal checkout or merge
* operations. * operations.
public boolean checkout() throws IOException { public boolean checkout() throws IOException {
try { try {
return doCheckout(); return doCheckout();
} catch (CanceledException ce) {
// should actually be propagated, but this would change a LOT of
// APIs
throw new IOException(ce);
} finally { } finally {
try { try {
dc.unlock(); dc.unlock();


private boolean doCheckout() throws CorruptObjectException, IOException, private boolean doCheckout() throws CorruptObjectException, IOException,
MissingObjectException, IncorrectObjectTypeException, MissingObjectException, IncorrectObjectTypeException,
CheckoutConflictException, IndexWriteException {
CheckoutConflictException, IndexWriteException, CanceledException {
toBeDeleted.clear(); toBeDeleted.clear();
try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) { try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
if (headCommitTree != null) if (headCommitTree != null)
// update our index // update our index
builder.finish(); builder.finish();


// init progress reporting
int numTotal = removed.size() + updated.size();
monitor.beginTask(JGitText.get().checkingOutFiles, numTotal);

performingCheckout = true; performingCheckout = true;
File file = null; File file = null;
String last = null; String last = null;
removeEmptyParents(new File(repo.getWorkTree(), last)); removeEmptyParents(new File(repo.getWorkTree(), last));
last = r; last = r;
} }
monitor.update(1);
if (monitor.isCancelled()) {
throw new CanceledException(MessageFormat.format(
JGitText.get().operationCanceled,
JGitText.get().checkingOutFiles));
}
} }
if (file != null) { if (file != null) {
removeEmptyParents(file); removeEmptyParents(file);
checkoutEntry(repo, entry, objectReader, false, meta); checkoutEntry(repo, entry, objectReader, false, meta);
} }
e = null; e = null;

monitor.update(1);
if (monitor.isCancelled()) {
throw new CanceledException(MessageFormat.format(
JGitText.get().operationCanceled,
JGitText.get().checkingOutFiles));
}
} }
} catch (Exception ex) { } catch (Exception ex) {
// We didn't actually modify the current entry nor any that // We didn't actually modify the current entry nor any that
} }
throw ex; throw ex;
} }
monitor.endTask();

// commit the index builder - a new index is persisted // commit the index builder - a new index is persisted
if (!builder.commit()) if (!builder.commit())
throw new IndexWriteException(); throw new IndexWriteException();

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java View File

/***/ public String cantPassMeATree; /***/ public String cantPassMeATree;
/***/ public String channelMustBeInRange1_255; /***/ public String channelMustBeInRange1_255;
/***/ public String characterClassIsNotSupported; /***/ public String characterClassIsNotSupported;
/***/ public String checkingOutFiles;
/***/ public String checkoutConflictWithFile; /***/ public String checkoutConflictWithFile;
/***/ public String checkoutConflictWithFiles; /***/ public String checkoutConflictWithFiles;
/***/ public String checkoutUnexpectedResult; /***/ public String checkoutUnexpectedResult;

Loading…
Cancel
Save