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
@@ -57,6 +57,7 @@ import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.TextProgressMonitor; | |||
import org.eclipse.jgit.pgm.internal.CLIText; | |||
import org.kohsuke.args4j.Argument; | |||
import org.kohsuke.args4j.Option; | |||
@@ -90,7 +91,8 @@ class Checkout extends TextBuiltin { | |||
} | |||
try (Git git = new Git(db)) { | |||
CheckoutCommand command = git.checkout(); | |||
CheckoutCommand command = git.checkout() | |||
.setProgressMonitor(new TextProgressMonitor(errw)); | |||
if (paths.size() > 0) { | |||
command.setStartPoint(name); | |||
if (paths.size() == 1 && paths.get(0).equals(".")) { //$NON-NLS-1$ |
@@ -117,6 +117,7 @@ cantFindObjectInReversePackIndexForTheSpecifiedOffset=Can''t find object in (rev | |||
cantPassMeATree=Can't pass me a tree! | |||
channelMustBeInRange1_255=channel {0} must be in range [1, 255] | |||
characterClassIsNotSupported=The character class {0} is not supported. | |||
checkingOutFiles=Checking out files | |||
checkoutConflictWithFile=Checkout conflict with file: {0} | |||
checkoutConflictWithFiles=Checkout conflict with files: {0} | |||
checkoutUnexpectedResult=Checkout returned unexpected result {0} |
@@ -76,8 +76,10 @@ import org.eclipse.jgit.lib.AnyObjectId; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.CoreConfig.EolStreamType; | |||
import org.eclipse.jgit.lib.FileMode; | |||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ObjectReader; | |||
import org.eclipse.jgit.lib.ProgressMonitor; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.RefUpdate; | |||
import org.eclipse.jgit.lib.RefUpdate.Result; | |||
@@ -182,6 +184,8 @@ public class CheckoutCommand extends GitCommand<Ref> { | |||
private Set<String> actuallyModifiedPaths; | |||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; | |||
/** | |||
* Constructor for CheckoutCommand | |||
* | |||
@@ -266,6 +270,7 @@ public class CheckoutCommand extends GitCommand<Ref> { | |||
dco = new DirCacheCheckout(repo, headTree, dc, | |||
newCommit.getTree()); | |||
dco.setFailOnConflict(true); | |||
dco.setProgressMonitor(monitor); | |||
try { | |||
dco.checkout(); | |||
} catch (org.eclipse.jgit.errors.CheckoutConflictException e) { | |||
@@ -346,6 +351,20 @@ public class CheckoutCommand extends GitCommand<Ref> { | |||
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 | |||
* check out all paths, use {@link #setAllPaths(boolean)}. |
@@ -60,8 +60,10 @@ import org.eclipse.jgit.errors.MissingObjectException; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.lib.AnyObjectId; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ObjectIdRef; | |||
import org.eclipse.jgit.lib.ProgressMonitor; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.Ref.Storage; | |||
import org.eclipse.jgit.lib.Repository; | |||
@@ -95,6 +97,8 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { | |||
private boolean noCommit = false; | |||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; | |||
/** | |||
* Constructor for CherryPickCommand | |||
* | |||
@@ -160,6 +164,7 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { | |||
newHead.getTree(), repo.lockDirCache(), | |||
merger.getResultTreeId()); | |||
dco.setFailOnConflict(true); | |||
dco.setProgressMonitor(monitor); | |||
dco.checkout(); | |||
if (!noCommit) | |||
newHead = new Git(getRepository()).commit() | |||
@@ -332,6 +337,24 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { | |||
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) { | |||
if (ourCommitName != null) | |||
return ourCommitName; |
@@ -361,6 +361,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { | |||
DirCache dc = clonedRepo.lockDirCache(); | |||
DirCacheCheckout co = new DirCacheCheckout(clonedRepo, dc, | |||
commit.getTree()); | |||
co.setProgressMonitor(monitor); | |||
co.checkout(); | |||
if (cloneSubmodules) | |||
cloneSubmodules(clonedRepo); |
@@ -268,6 +268,7 @@ public class MergeCommand extends GitCommand<MergeResult> { | |||
dco = new DirCacheCheckout(repo, | |||
repo.lockDirCache(), srcCommit.getTree()); | |||
dco.setFailOnConflict(true); | |||
dco.setProgressMonitor(monitor); | |||
dco.checkout(); | |||
RefUpdate refUpdate = repo | |||
.updateRef(head.getTarget().getName()); | |||
@@ -298,6 +299,7 @@ public class MergeCommand extends GitCommand<MergeResult> { | |||
dco = new DirCacheCheckout(repo, | |||
headCommit.getTree(), repo.lockDirCache(), | |||
srcCommit.getTree()); | |||
dco.setProgressMonitor(monitor); | |||
dco.setFailOnConflict(true); | |||
dco.checkout(); | |||
String msg = null; | |||
@@ -376,6 +378,7 @@ public class MergeCommand extends GitCommand<MergeResult> { | |||
headCommit.getTree(), repo.lockDirCache(), | |||
merger.getResultTreeId()); | |||
dco.setFailOnConflict(true); | |||
dco.setProgressMonitor(monitor); | |||
dco.checkout(); | |||
String msg = null; |
@@ -932,6 +932,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { | |||
try { | |||
DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree); | |||
dco.setFailOnConflict(false); | |||
dco.setProgressMonitor(monitor); | |||
boolean needsDeleteFiles = dco.checkout(); | |||
if (needsDeleteFiles) { | |||
List<String> fileList = dco.getToBeDeleted(); | |||
@@ -1265,6 +1266,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { | |||
CheckoutCommand co = new CheckoutCommand(repo); | |||
try { | |||
co.setProgressMonitor(monitor); | |||
co.setName(newCommit.name()).call(); | |||
if (headName.startsWith(Constants.R_HEADS)) { | |||
RefUpdate rup = repo.updateRef(headName); | |||
@@ -1407,6 +1409,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { | |||
DirCacheCheckout dco = new DirCacheCheckout(repo, head.getTree(), | |||
repo.lockDirCache(), commit.getTree()); | |||
dco.setFailOnConflict(true); | |||
dco.setProgressMonitor(monitor); | |||
try { | |||
dco.checkout(); | |||
} catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { |
@@ -58,7 +58,9 @@ import org.eclipse.jgit.dircache.DirCacheEntry; | |||
import org.eclipse.jgit.dircache.DirCacheIterator; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ProgressMonitor; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.RefUpdate; | |||
import org.eclipse.jgit.lib.Repository; | |||
@@ -125,6 +127,8 @@ public class ResetCommand extends GitCommand<Ref> { | |||
private boolean isReflogDisabled; | |||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; | |||
/** | |||
* <p> | |||
* Constructor for ResetCommand. | |||
@@ -336,6 +340,24 @@ public class ResetCommand extends GitCommand<Ref> { | |||
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) { | |||
DirCache dc = null; | |||
try (final TreeWalk tw = new TreeWalk(repo)) { | |||
@@ -420,6 +442,7 @@ public class ResetCommand extends GitCommand<Ref> { | |||
DirCacheCheckout checkout = new DirCacheCheckout(repo, dc, | |||
commitTree); | |||
checkout.setFailOnConflict(false); | |||
checkout.setProgressMonitor(monitor); | |||
try { | |||
checkout.checkout(); | |||
} catch (org.eclipse.jgit.errors.CheckoutConflictException cce) { |
@@ -61,8 +61,10 @@ import org.eclipse.jgit.dircache.DirCacheCheckout; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.lib.AnyObjectId; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.NullProgressMonitor; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ObjectIdRef; | |||
import org.eclipse.jgit.lib.ProgressMonitor; | |||
import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.Ref.Storage; | |||
import org.eclipse.jgit.lib.Repository; | |||
@@ -97,6 +99,8 @@ public class RevertCommand extends GitCommand<RevCommit> { | |||
private MergeStrategy strategy = MergeStrategy.RECURSIVE; | |||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; | |||
/** | |||
* <p> | |||
* Constructor for RevertCommand. | |||
@@ -178,6 +182,7 @@ public class RevertCommand extends GitCommand<RevCommit> { | |||
headCommit.getTree(), repo.lockDirCache(), | |||
merger.getResultTreeId()); | |||
dco.setFailOnConflict(true); | |||
dco.setProgressMonitor(monitor); | |||
dco.checkout(); | |||
try (Git git = new Git(getRepository())) { | |||
newHead = git.commit().setMessage(newMessage) | |||
@@ -325,4 +330,22 @@ public class RevertCommand extends GitCommand<RevCommit> { | |||
this.strategy = strategy; | |||
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; | |||
} | |||
} |
@@ -226,6 +226,7 @@ public class SubmoduleUpdateCommand extends | |||
submoduleRepo, submoduleRepo.lockDirCache(), | |||
commit.getTree()); | |||
co.setFailOnConflict(true); | |||
co.setProgressMonitor(monitor); | |||
co.checkout(); | |||
RefUpdate refUpdate = submoduleRepo.updateRef( | |||
Constants.HEAD, true); |
@@ -56,6 +56,7 @@ import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.eclipse.jgit.api.errors.CanceledException; | |||
import org.eclipse.jgit.api.errors.FilterFailedException; | |||
import org.eclipse.jgit.attributes.FilterCommand; | |||
import org.eclipse.jgit.attributes.FilterCommandRegistry; | |||
@@ -76,6 +77,7 @@ import org.eclipse.jgit.lib.ObjectChecker; | |||
import org.eclipse.jgit.lib.ObjectId; | |||
import org.eclipse.jgit.lib.ObjectLoader; | |||
import org.eclipse.jgit.lib.ObjectReader; | |||
import org.eclipse.jgit.lib.ProgressMonitor; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.treewalk.AbstractTreeIterator; | |||
import org.eclipse.jgit.treewalk.CanonicalTreeParser; | |||
@@ -158,6 +160,8 @@ public class DirCacheCheckout { | |||
private boolean performingCheckout; | |||
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; | |||
/** | |||
* Get list of updated paths and smudgeFilterCommands | |||
* | |||
@@ -287,6 +291,18 @@ public class DirCacheCheckout { | |||
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 | |||
* operations. | |||
@@ -465,6 +481,10 @@ public class DirCacheCheckout { | |||
public boolean checkout() throws IOException { | |||
try { | |||
return doCheckout(); | |||
} catch (CanceledException ce) { | |||
// should actually be propagated, but this would change a LOT of | |||
// APIs | |||
throw new IOException(ce); | |||
} finally { | |||
try { | |||
dc.unlock(); | |||
@@ -482,7 +502,7 @@ public class DirCacheCheckout { | |||
private boolean doCheckout() throws CorruptObjectException, IOException, | |||
MissingObjectException, IncorrectObjectTypeException, | |||
CheckoutConflictException, IndexWriteException { | |||
CheckoutConflictException, IndexWriteException, CanceledException { | |||
toBeDeleted.clear(); | |||
try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) { | |||
if (headCommitTree != null) | |||
@@ -500,6 +520,10 @@ public class DirCacheCheckout { | |||
// update our index | |||
builder.finish(); | |||
// init progress reporting | |||
int numTotal = removed.size() + updated.size(); | |||
monitor.beginTask(JGitText.get().checkingOutFiles, numTotal); | |||
performingCheckout = true; | |||
File file = null; | |||
String last = null; | |||
@@ -525,6 +549,12 @@ public class DirCacheCheckout { | |||
removeEmptyParents(new File(repo.getWorkTree(), last)); | |||
last = r; | |||
} | |||
monitor.update(1); | |||
if (monitor.isCancelled()) { | |||
throw new CanceledException(MessageFormat.format( | |||
JGitText.get().operationCanceled, | |||
JGitText.get().checkingOutFiles)); | |||
} | |||
} | |||
if (file != null) { | |||
removeEmptyParents(file); | |||
@@ -544,6 +574,13 @@ public class DirCacheCheckout { | |||
checkoutEntry(repo, entry, objectReader, false, meta); | |||
} | |||
e = null; | |||
monitor.update(1); | |||
if (monitor.isCancelled()) { | |||
throw new CanceledException(MessageFormat.format( | |||
JGitText.get().operationCanceled, | |||
JGitText.get().checkingOutFiles)); | |||
} | |||
} | |||
} catch (Exception ex) { | |||
// We didn't actually modify the current entry nor any that | |||
@@ -557,6 +594,8 @@ public class DirCacheCheckout { | |||
} | |||
throw ex; | |||
} | |||
monitor.endTask(); | |||
// commit the index builder - a new index is persisted | |||
if (!builder.commit()) | |||
throw new IndexWriteException(); |
@@ -178,6 +178,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String cantPassMeATree; | |||
/***/ public String channelMustBeInRange1_255; | |||
/***/ public String characterClassIsNotSupported; | |||
/***/ public String checkingOutFiles; | |||
/***/ public String checkoutConflictWithFile; | |||
/***/ public String checkoutConflictWithFiles; | |||
/***/ public String checkoutUnexpectedResult; |