diff options
author | Thomas Wolf | 2017-07-07 09:26:02 +0000 |
---|---|---|
committer | Matthias Sohn | 2017-08-15 20:52:00 +0000 |
commit | b13a285098305149b34924bce2679a0cd98d9b2c (patch) | |
tree | 1fcbaf3d00e2cd486481f7d8f036f7857bcbbc7d /org.eclipse.jgit/src/org/eclipse/jgit/api | |
parent | 81d020aba9d48825a70d17a5fefc4b5472795e2e (diff) | |
download | jgit-b13a285098305149b34924bce2679a0cd98d9b2c.tar.gz jgit-b13a285098305149b34924bce2679a0cd98d9b2c.tar.xz jgit-b13a285098305149b34924bce2679a0cd98d9b2c.zip |
Send a detailed event on working tree modifications
Currently there is no way to determine the precise changes done
to the working tree by a JGit command. Only the CheckoutCommand
actually provides access to the lists of modified, deleted, and
to-be-deleted files, but those lists may be inaccurate (since they
are determined up-front before the working tree is modified) if
the actual checkout then fails halfway through. Moreover, other
JGit commands that modify the working tree do not offer any way to
figure out which files were changed.
This poses problems for EGit, which may need to refresh parts of the
Eclipse workspace when JGit has done java.io file operations.
Provide the foundations for better file change tracking: the working
tree is modified exclusively in DirCacheCheckout. Make it emit a new
type of RepositoryEvent that lists all files that were modified or
deleted, even if the checkout failed halfway through. We update the
'updated' and 'removed' lists determined up-front in case of file
system problems to reflect the actual state of changes made.
EGit thus can register a listener for these events and then knows
exactly which parts of the Eclipse workspace may need to be refreshed.
Two commands manage checking out individual DirCacheEntries themselves:
checkout specific paths, and applying a stash with untracked files.
Make those two also emit such a new WorkingTreeModifiedEvent.
Furthermore, merges may modify files, and clean, rm, and stash create
may delete files.
CQ: 13969
Bug: 500106
Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/api')
6 files changed, 89 insertions, 13 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index 21d62837e9..6b20da3ede 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -47,8 +47,10 @@ import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.errors.CheckoutConflictException; @@ -66,6 +68,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; @@ -175,6 +178,8 @@ public class CheckoutCommand extends GitCommand<Ref> { private boolean checkoutAllPaths; + private Set<String> actuallyModifiedPaths; + /** * @param repo */ @@ -410,7 +415,8 @@ public class CheckoutCommand extends GitCommand<Ref> { } /** - * Checkout paths into index and working directory + * Checkout paths into index and working directory, firing a + * {@link WorkingTreeModifiedEvent} if the working tree was modified. * * @return this instance * @throws IOException @@ -418,6 +424,7 @@ public class CheckoutCommand extends GitCommand<Ref> { */ protected CheckoutCommand checkoutPaths() throws IOException, RefNotFoundException { + actuallyModifiedPaths = new HashSet<>(); DirCache dc = repo.lockDirCache(); try (RevWalk revWalk = new RevWalk(repo); TreeWalk treeWalk = new TreeWalk(repo, @@ -432,7 +439,16 @@ public class CheckoutCommand extends GitCommand<Ref> { checkoutPathsFromCommit(treeWalk, dc, commit); } } finally { - dc.unlock(); + try { + dc.unlock(); + } finally { + WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent( + actuallyModifiedPaths, null); + actuallyModifiedPaths = null; + if (!event.isEmpty()) { + repo.fireEvent(event); + } + } } return this; } @@ -461,9 +477,11 @@ public class CheckoutCommand extends GitCommand<Ref> { int stage = ent.getStage(); if (stage > DirCacheEntry.STAGE_0) { if (checkoutStage != null) { - if (stage == checkoutStage.number) + if (stage == checkoutStage.number) { checkoutPath(ent, r, new CheckoutMetadata( eolStreamType, filterCommand)); + actuallyModifiedPaths.add(path); + } } else { UnmergedPathException e = new UnmergedPathException( ent); @@ -472,6 +490,7 @@ public class CheckoutCommand extends GitCommand<Ref> { } else { checkoutPath(ent, r, new CheckoutMetadata(eolStreamType, filterCommand)); + actuallyModifiedPaths.add(path); } } }); @@ -492,13 +511,15 @@ public class CheckoutCommand extends GitCommand<Ref> { final EolStreamType eolStreamType = treeWalk.getEolStreamType(); final String filterCommand = treeWalk .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE); - editor.add(new PathEdit(treeWalk.getPathString()) { + final String path = treeWalk.getPathString(); + editor.add(new PathEdit(path) { @Override public void apply(DirCacheEntry ent) { ent.setObjectId(blobId); ent.setFileMode(mode); checkoutPath(ent, r, new CheckoutMetadata(eolStreamType, filterCommand)); + actuallyModifiedPaths.add(path); } }); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java index c58efb1478..e41a03b81a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java @@ -54,6 +54,7 @@ import java.util.TreeSet; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; @@ -135,6 +136,10 @@ public class CleanCommand extends GitCommand<Set<String>> { } } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); + } finally { + if (!files.isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent(null, files)); + } } return files; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index bae54ce7b5..75460fbd14 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -64,6 +64,7 @@ import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Config.ConfigEnum; @@ -355,6 +356,10 @@ public class MergeCommand extends GitCommand<MergeResult> { .getMergeResults(); failingPaths = resolveMerger.getFailingPaths(); unmergedPaths = resolveMerger.getUnmergedPaths(); + if (!resolveMerger.getModifiedFiles().isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent( + resolveMerger.getModifiedFiles(), null)); + } } else noProblems = merger.merge(headCommit, srcCommit); refLogMessage.append(": Merge made by "); //$NON-NLS-1$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java index 9e2cf31100..48c23f59c2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java @@ -44,8 +44,10 @@ package org.eclipse.jgit.api; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; +import java.util.List; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; @@ -53,6 +55,7 @@ import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; @@ -145,6 +148,7 @@ public class RmCommand extends GitCommand<DirCache> { checkCallable(); DirCache dc = null; + List<String> actuallyDeletedFiles = new ArrayList<>(); try (final TreeWalk tw = new TreeWalk(repo)) { dc = repo.lockDirCache(); DirCacheBuilder builder = dc.builder(); @@ -157,11 +161,14 @@ public class RmCommand extends GitCommand<DirCache> { if (!cached) { final FileMode mode = tw.getFileMode(0); if (mode.getObjectType() == Constants.OBJ_BLOB) { + String relativePath = tw.getPathString(); final File path = new File(repo.getWorkTree(), - tw.getPathString()); + relativePath); // Deleting a blob is simply a matter of removing // the file or symlink named by the tree entry. - delete(path); + if (delete(path)) { + actuallyDeletedFiles.add(relativePath); + } } } } @@ -171,16 +178,28 @@ public class RmCommand extends GitCommand<DirCache> { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e); } finally { - if (dc != null) - dc.unlock(); + try { + if (dc != null) { + dc.unlock(); + } + } finally { + if (!actuallyDeletedFiles.isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent(null, + actuallyDeletedFiles)); + } + } } return dc; } - private void delete(File p) { - while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) + private boolean delete(File p) { + boolean deleted = false; + while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) { + deleted = true; p = p.getParentFile(); + } + return deleted; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java index 10ec2a6a5a..b56fb2519b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012, GitHub Inc. + * Copyright (C) 2012, 2017 GitHub Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -44,6 +44,9 @@ package org.eclipse.jgit.api; import java.io.IOException; import java.text.MessageFormat; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; @@ -58,6 +61,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CheckoutConflictException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig.EolStreamType; @@ -198,7 +202,13 @@ public class StashApplyCommand extends GitCommand<ObjectId> { "stash" }); //$NON-NLS-1$ merger.setBase(stashHeadCommit); merger.setWorkingTreeIterator(new FileTreeIterator(repo)); - if (merger.merge(headCommit, stashCommit)) { + boolean mergeSucceeded = merger.merge(headCommit, stashCommit); + List<String> modifiedByMerge = merger.getModifiedFiles(); + if (!modifiedByMerge.isEmpty()) { + repo.fireEvent( + new WorkingTreeModifiedEvent(modifiedByMerge, null)); + } + if (mergeSucceeded) { DirCache dc = repo.lockDirCache(); DirCacheCheckout dco = new DirCacheCheckout(repo, headTree, dc, merger.getResultTreeId()); @@ -329,6 +339,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { private void resetUntracked(RevTree tree) throws CheckoutConflictException, IOException { + Set<String> actuallyModifiedPaths = new HashSet<>(); // TODO maybe NameConflictTreeWalk ? try (TreeWalk walk = new TreeWalk(repo)) { walk.addTree(tree); @@ -361,6 +372,12 @@ public class StashApplyCommand extends GitCommand<ObjectId> { checkoutPath(entry, reader, new CheckoutMetadata(eolStreamType, null)); + actuallyModifiedPaths.add(entry.getPathString()); + } + } finally { + if (!actuallyModifiedPaths.isEmpty()) { + repo.fireEvent(new WorkingTreeModifiedEvent( + actuallyModifiedPaths, null)); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java index 681f8e65ae..21b06e637a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.UnmergedPathException; +import org.eclipse.jgit.events.WorkingTreeModifiedEvent; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; @@ -240,6 +241,7 @@ public class StashCreateCommand extends GitCommand<RevCommit> { public RevCommit call() throws GitAPIException { checkCallable(); + List<String> deletedFiles = new ArrayList<>(); Ref head = getHead(); try (ObjectReader reader = repo.newObjectReader()) { RevCommit headCommit = parseCommit(reader, head.getObjectId()); @@ -377,9 +379,11 @@ public class StashCreateCommand extends GitCommand<RevCommit> { // Remove untracked files if (includeUntracked) { for (DirCacheEntry entry : untracked) { + String repoRelativePath = entry.getPathString(); File file = new File(repo.getWorkTree(), - entry.getPathString()); + repoRelativePath); FileUtils.delete(file); + deletedFiles.add(repoRelativePath); } } @@ -394,6 +398,11 @@ public class StashCreateCommand extends GitCommand<RevCommit> { return parseCommit(reader, commitId); } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashFailed, e); + } finally { + if (!deletedFiles.isEmpty()) { + repo.fireEvent( + new WorkingTreeModifiedEvent(null, deletedFiles)); + } } } } |