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
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$ |
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} |
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)}. |
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; |
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); |
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; |
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) { |
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) { |
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; | |||||
} | |||||
} | } |
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); |
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(); |
/***/ 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; |