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> 7 anni fa 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> 7 anni fa 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> 7 anni fa 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> 7 anni fa 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> 7 anni fa 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> 7 anni fa |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- /*
- * 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
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- 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;
- import org.eclipse.jgit.api.errors.JGitInternalException;
- import org.eclipse.jgit.api.errors.NoHeadException;
- import org.eclipse.jgit.api.errors.StashApplyFailureException;
- import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
- import org.eclipse.jgit.dircache.DirCache;
- import org.eclipse.jgit.dircache.DirCacheBuilder;
- import org.eclipse.jgit.dircache.DirCacheCheckout;
- 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;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectReader;
- import org.eclipse.jgit.lib.Repository;
- import org.eclipse.jgit.lib.RepositoryState;
- import org.eclipse.jgit.merge.MergeStrategy;
- import org.eclipse.jgit.merge.ResolveMerger;
- import org.eclipse.jgit.revwalk.RevCommit;
- import org.eclipse.jgit.revwalk.RevTree;
- import org.eclipse.jgit.revwalk.RevWalk;
- import org.eclipse.jgit.treewalk.AbstractTreeIterator;
- import org.eclipse.jgit.treewalk.FileTreeIterator;
- import org.eclipse.jgit.treewalk.TreeWalk;
-
- /**
- * Command class to apply a stashed commit.
- *
- * This class behaves like <em>git stash apply --index</em>, i.e. it tries to
- * recover the stashed index state in addition to the working tree state.
- *
- * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
- * >Git documentation about Stash</a>
- *
- * @since 2.0
- */
- public class StashApplyCommand extends GitCommand<ObjectId> {
-
- private static final String DEFAULT_REF = Constants.STASH + "@{0}"; //$NON-NLS-1$
-
- private String stashRef;
-
- private boolean applyIndex = true;
-
- private boolean applyUntracked = true;
-
- private boolean ignoreRepositoryState;
-
- private MergeStrategy strategy = MergeStrategy.RECURSIVE;
-
- /**
- * Create command to apply the changes of a stashed commit
- *
- * @param repo
- */
- public StashApplyCommand(final Repository repo) {
- super(repo);
- }
-
- /**
- * Set the stash reference to apply
- * <p>
- * This will default to apply the latest stashed commit (stash@{0}) if
- * unspecified
- *
- * @param stashRef
- * @return {@code this}
- */
- public StashApplyCommand setStashRef(final String stashRef) {
- this.stashRef = stashRef;
- return this;
- }
-
- /**
- * @param willIgnoreRepositoryState
- * @return {@code this}
- * @since 3.2
- */
- public StashApplyCommand ignoreRepositoryState(boolean willIgnoreRepositoryState) {
- this.ignoreRepositoryState = willIgnoreRepositoryState;
- return this;
- }
-
- private ObjectId getStashId() throws GitAPIException {
- final String revision = stashRef != null ? stashRef : DEFAULT_REF;
- final ObjectId stashId;
- try {
- stashId = repo.resolve(revision);
- } catch (IOException e) {
- throw new InvalidRefNameException(MessageFormat.format(
- JGitText.get().stashResolveFailed, revision), e);
- }
- if (stashId == null)
- throw new InvalidRefNameException(MessageFormat.format(
- JGitText.get().stashResolveFailed, revision));
- return stashId;
- }
-
- /**
- * Apply the changes in a stashed commit to the working directory and index
- *
- * @return id of stashed commit that was applied TODO: Does anyone depend on
- * this, or could we make it more like Merge/CherryPick/Revert?
- * @throws GitAPIException
- * @throws WrongRepositoryStateException
- * @throws NoHeadException
- * @throws StashApplyFailureException
- */
- @Override
- public ObjectId call() throws GitAPIException,
- WrongRepositoryStateException, NoHeadException,
- StashApplyFailureException {
- checkCallable();
-
- if (!ignoreRepositoryState
- && repo.getRepositoryState() != RepositoryState.SAFE)
- throw new WrongRepositoryStateException(MessageFormat.format(
- JGitText.get().stashApplyOnUnsafeRepository,
- repo.getRepositoryState()));
-
- try (ObjectReader reader = repo.newObjectReader();
- RevWalk revWalk = new RevWalk(reader)) {
-
- ObjectId headCommit = repo.resolve(Constants.HEAD);
- if (headCommit == null)
- throw new NoHeadException(JGitText.get().stashApplyWithoutHead);
-
- final ObjectId stashId = getStashId();
- RevCommit stashCommit = revWalk.parseCommit(stashId);
- if (stashCommit.getParentCount() < 2
- || stashCommit.getParentCount() > 3)
- throw new JGitInternalException(MessageFormat.format(
- JGitText.get().stashCommitIncorrectNumberOfParents,
- stashId.name(),
- Integer.valueOf(stashCommit.getParentCount())));
-
- ObjectId headTree = repo.resolve(Constants.HEAD + "^{tree}"); //$NON-NLS-1$
- ObjectId stashIndexCommit = revWalk.parseCommit(stashCommit
- .getParent(1));
- ObjectId stashHeadCommit = stashCommit.getParent(0);
- ObjectId untrackedCommit = null;
- if (applyUntracked && stashCommit.getParentCount() == 3)
- untrackedCommit = revWalk.parseCommit(stashCommit.getParent(2));
-
- ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
- merger.setCommitNames(new String[] { "stashed HEAD", "HEAD", //$NON-NLS-1$ //$NON-NLS-2$
- "stash" }); //$NON-NLS-1$
- merger.setBase(stashHeadCommit);
- merger.setWorkingTreeIterator(new FileTreeIterator(repo));
- 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());
- dco.setFailOnConflict(true);
- dco.checkout(); // Ignoring failed deletes....
- if (applyIndex) {
- ResolveMerger ixMerger = (ResolveMerger) strategy
- .newMerger(repo, true);
- ixMerger.setCommitNames(new String[] { "stashed HEAD", //$NON-NLS-1$
- "HEAD", "stashed index" }); //$NON-NLS-1$//$NON-NLS-2$
- ixMerger.setBase(stashHeadCommit);
- boolean ok = ixMerger.merge(headCommit, stashIndexCommit);
- if (ok) {
- resetIndex(revWalk
- .parseTree(ixMerger.getResultTreeId()));
- } else {
- throw new StashApplyFailureException(
- JGitText.get().stashApplyConflict);
- }
- }
-
- if (untrackedCommit != null) {
- ResolveMerger untrackedMerger = (ResolveMerger) strategy
- .newMerger(repo, true);
- untrackedMerger.setCommitNames(new String[] {
- "null", "HEAD", "untracked files" }); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
- // There is no common base for HEAD & untracked files
- // because the commit for untracked files has no parent. If
- // we use stashHeadCommit as common base (as in the other
- // merges) we potentially report conflicts for files
- // which are not even member of untracked files commit
- untrackedMerger.setBase(null);
- boolean ok = untrackedMerger.merge(headCommit,
- untrackedCommit);
- if (ok) {
- try {
- RevTree untrackedTree = revWalk
- .parseTree(untrackedCommit);
- resetUntracked(untrackedTree);
- } catch (CheckoutConflictException e) {
- throw new StashApplyFailureException(
- JGitText.get().stashApplyConflict, e);
- }
- } else {
- throw new StashApplyFailureException(
- JGitText.get().stashApplyConflict);
- }
- }
- } else {
- throw new StashApplyFailureException(
- JGitText.get().stashApplyConflict);
- }
- return stashId;
-
- } catch (JGitInternalException e) {
- throw e;
- } catch (IOException e) {
- throw new JGitInternalException(JGitText.get().stashApplyFailed, e);
- }
- }
-
- /**
- * @param applyIndex
- * true (default) if the command should restore the index state
- */
- public void setApplyIndex(boolean applyIndex) {
- this.applyIndex = applyIndex;
- }
-
- /**
- * @param strategy
- * The merge strategy to use in order to merge during this
- * command execution.
- * @return {@code this}
- * @since 3.4
- */
- public StashApplyCommand setStrategy(MergeStrategy strategy) {
- this.strategy = strategy;
- return this;
- }
-
- /**
- * @param applyUntracked
- * true (default) if the command should restore untracked files
- * @since 3.4
- */
- public void setApplyUntracked(boolean applyUntracked) {
- this.applyUntracked = applyUntracked;
- }
-
- private void resetIndex(RevTree tree) throws IOException {
- DirCache dc = repo.lockDirCache();
- try (TreeWalk walk = new TreeWalk(repo)) {
- DirCacheBuilder builder = dc.builder();
-
- walk.addTree(tree);
- walk.addTree(new DirCacheIterator(dc));
- walk.setRecursive(true);
-
- while (walk.next()) {
- AbstractTreeIterator cIter = walk.getTree(0,
- AbstractTreeIterator.class);
- if (cIter == null) {
- // Not in commit, don't add to new index
- continue;
- }
-
- final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
- entry.setFileMode(cIter.getEntryFileMode());
- entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
-
- DirCacheIterator dcIter = walk.getTree(1,
- DirCacheIterator.class);
- if (dcIter != null && dcIter.idEqual(cIter)) {
- DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
- entry.setLastModified(indexEntry.getLastModified());
- entry.setLength(indexEntry.getLength());
- }
-
- builder.add(entry);
- }
-
- builder.commit();
- } finally {
- dc.unlock();
- }
- }
-
- 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);
- walk.addTree(new FileTreeIterator(repo));
- walk.setRecursive(true);
-
- final ObjectReader reader = walk.getObjectReader();
-
- while (walk.next()) {
- final AbstractTreeIterator cIter = walk.getTree(0,
- AbstractTreeIterator.class);
- if (cIter == null)
- // Not in commit, don't create untracked
- continue;
-
- final EolStreamType eolStreamType = walk.getEolStreamType();
- final DirCacheEntry entry = new DirCacheEntry(walk.getRawPath());
- entry.setFileMode(cIter.getEntryFileMode());
- entry.setObjectIdFromRaw(cIter.idBuffer(), cIter.idOffset());
-
- FileTreeIterator fIter = walk
- .getTree(1, FileTreeIterator.class);
- if (fIter != null) {
- if (fIter.isModified(entry, true, reader)) {
- // file exists and is dirty
- throw new CheckoutConflictException(
- entry.getPathString());
- }
- }
-
- checkoutPath(entry, reader,
- new CheckoutMetadata(eolStreamType, null));
- actuallyModifiedPaths.add(entry.getPathString());
- }
- } finally {
- if (!actuallyModifiedPaths.isEmpty()) {
- repo.fireEvent(new WorkingTreeModifiedEvent(
- actuallyModifiedPaths, null));
- }
- }
- }
-
- private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
- CheckoutMetadata checkoutMetadata) {
- try {
- DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
- checkoutMetadata);
- } catch (IOException e) {
- throw new JGitInternalException(MessageFormat.format(
- JGitText.get().checkoutConflictWithFile,
- entry.getPathString()), e);
- }
- }
- }
|