Package org.eclipse.jgit.api

Source Code of org.eclipse.jgit.api.StashApplyCommand$StashDiffFilter

/*
* Copyright (C) 2012, 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.File;
import java.io.IOException;
import java.text.MessageFormat;

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.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheEditor;
import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
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.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.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FileUtils;

/**
* Command class to apply a stashed commit.
*
* @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}";

  /**
   * Stash diff filter that looks for differences in the first three trees
   * which must be the stash head tree, stash index tree, and stash working
   * directory tree in any order.
   */
  private static class StashDiffFilter extends TreeFilter {

    @Override
    public boolean include(final TreeWalk walker) {
      final int m = walker.getRawMode(0);
      if (walker.getRawMode(1) != m || !walker.idEqual(1, 0))
        return true;
      if (walker.getRawMode(2) != m || !walker.idEqual(2, 0))
        return true;
      return false;
    }

    @Override
    public boolean shouldBeRecursive() {
      return false;
    }

    @Override
    public TreeFilter clone() {
      return this;
    }

    @Override
    public String toString() {
      return "STASH_DIFF";
    }
  }

  private String stashRef;

  /**
   * 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;
  }

  private boolean isEqualEntry(AbstractTreeIterator iter1,
      AbstractTreeIterator iter2) {
    if (!iter1.getEntryFileMode().equals(iter2.getEntryFileMode()))
      return false;
    ObjectId id1 = iter1.getEntryObjectId();
    ObjectId id2 = iter2.getEntryObjectId();
    return id1 != null ? id1.equals(id2) : id2 == null;
  }

  /**
   * Would unstashing overwrite local changes?
   *
   * @param stashIndexIter
   * @param stashWorkingTreeIter
   * @param headIter
   * @param indexIter
   * @param workingTreeIter
   * @return true if unstash conflict, false otherwise
   */
  private boolean isConflict(AbstractTreeIterator stashIndexIter,
      AbstractTreeIterator stashWorkingTreeIter,
      AbstractTreeIterator headIter, AbstractTreeIterator indexIter,
      AbstractTreeIterator workingTreeIter) {
    // Is the current index dirty?
    boolean indexDirty = indexIter != null
        && (headIter == null || !isEqualEntry(indexIter, headIter));

    // Is the current working tree dirty?
    boolean workingTreeDirty = workingTreeIter != null
        && (headIter == null || !isEqualEntry(workingTreeIter, headIter));

    // Would unstashing overwrite existing index changes?
    if (indexDirty && stashIndexIter != null && indexIter != null
        && !isEqualEntry(stashIndexIter, indexIter))
      return true;

    // Would unstashing overwrite existing working tree changes?
    if (workingTreeDirty && stashWorkingTreeIter != null
        && workingTreeIter != null
        && !isEqualEntry(stashWorkingTreeIter, workingTreeIter))
      return true;

    return false;
  }

  private ObjectId getHeadTree() throws GitAPIException {
    final ObjectId headTree;
    try {
      headTree = repo.resolve(Constants.HEAD + "^{tree}");
    } catch (IOException e) {
      throw new JGitInternalException(JGitText.get().cannotReadTree, e);
    }
    if (headTree == null)
      throw new NoHeadException(JGitText.get().cannotReadTree);
    return headTree;
  }

  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;
  }

  private void scanForConflicts(TreeWalk treeWalk) throws IOException {
    File workingTree = repo.getWorkTree();
    while (treeWalk.next()) {
      // State of the stashed index and working directory
      AbstractTreeIterator stashIndexIter = treeWalk.getTree(1,
          AbstractTreeIterator.class);
      AbstractTreeIterator stashWorkingIter = treeWalk.getTree(2,
          AbstractTreeIterator.class);

      // State of the current HEAD, index, and working directory
      AbstractTreeIterator headIter = treeWalk.getTree(3,
          AbstractTreeIterator.class);
      AbstractTreeIterator indexIter = treeWalk.getTree(4,
          AbstractTreeIterator.class);
      AbstractTreeIterator workingIter = treeWalk.getTree(5,
          AbstractTreeIterator.class);

      if (isConflict(stashIndexIter, stashWorkingIter, headIter,
          indexIter, workingIter)) {
        String path = treeWalk.getPathString();
        File file = new File(workingTree, path);
        throw new CheckoutConflictException(file.getAbsolutePath());
      }
    }
  }

  private void applyChanges(TreeWalk treeWalk, DirCache cache,
      DirCacheEditor editor) throws IOException {
    File workingTree = repo.getWorkTree();
    while (treeWalk.next()) {
      String path = treeWalk.getPathString();
      File file = new File(workingTree, path);

      // State of the stashed HEAD, index, and working directory
      AbstractTreeIterator stashHeadIter = treeWalk.getTree(0,
          AbstractTreeIterator.class);
      AbstractTreeIterator stashIndexIter = treeWalk.getTree(1,
          AbstractTreeIterator.class);
      AbstractTreeIterator stashWorkingIter = treeWalk.getTree(2,
          AbstractTreeIterator.class);

      if (stashWorkingIter != null && stashIndexIter != null) {
        // Checkout index change
        DirCacheEntry entry = cache.getEntry(path);
        if (entry == null)
          entry = new DirCacheEntry(treeWalk.getRawPath());
        entry.setFileMode(stashIndexIter.getEntryFileMode());
        entry.setObjectId(stashIndexIter.getEntryObjectId());
        DirCacheCheckout.checkoutEntry(repo, file, entry,
            treeWalk.getObjectReader());
        final DirCacheEntry updatedEntry = entry;
        editor.add(new PathEdit(path) {

          public void apply(DirCacheEntry ent) {
            ent.copyMetaData(updatedEntry);
          }
        });

        // Checkout working directory change
        if (!stashWorkingIter.idEqual(stashIndexIter)) {
          entry = new DirCacheEntry(treeWalk.getRawPath());
          entry.setObjectId(stashWorkingIter.getEntryObjectId());
          DirCacheCheckout.checkoutEntry(repo, file, entry,
              treeWalk.getObjectReader());
        }
      } else {
        if (stashIndexIter == null
            || (stashHeadIter != null && !stashIndexIter
                .idEqual(stashHeadIter)))
          editor.add(new DeletePath(path));
        FileUtils
            .delete(file, FileUtils.RETRY | FileUtils.SKIP_MISSING);
      }
    }
  }

  /**
   * Apply the changes in a stashed commit to the working directory and index
   *
   * @return id of stashed commit that was applied
   * @throws GitAPIException
   * @throws WrongRepositoryStateException
   */
  public ObjectId call() throws GitAPIException,
      WrongRepositoryStateException {
    checkCallable();

    if (repo.getRepositoryState() != RepositoryState.SAFE)
      throw new WrongRepositoryStateException(MessageFormat.format(
          JGitText.get().stashApplyOnUnsafeRepository,
          repo.getRepositoryState()));

    final ObjectId headTree = getHeadTree();
    final ObjectId stashId = getStashId();

    ObjectReader reader = repo.newObjectReader();
    try {
      RevWalk revWalk = new RevWalk(reader);
      RevCommit stashCommit = revWalk.parseCommit(stashId);
      if (stashCommit.getParentCount() != 2)
        throw new JGitInternalException(MessageFormat.format(
            JGitText.get().stashCommitMissingTwoParents,
            stashId.name()));

      RevTree stashWorkingTree = stashCommit.getTree();
      RevTree stashIndexTree = revWalk.parseCommit(
          stashCommit.getParent(1)).getTree();
      RevTree stashHeadTree = revWalk.parseCommit(
          stashCommit.getParent(0)).getTree();

      CanonicalTreeParser stashWorkingIter = new CanonicalTreeParser();
      stashWorkingIter.reset(reader, stashWorkingTree);
      CanonicalTreeParser stashIndexIter = new CanonicalTreeParser();
      stashIndexIter.reset(reader, stashIndexTree);
      CanonicalTreeParser stashHeadIter = new CanonicalTreeParser();
      stashHeadIter.reset(reader, stashHeadTree);
      CanonicalTreeParser headIter = new CanonicalTreeParser();
      headIter.reset(reader, headTree);

      DirCache cache = repo.lockDirCache();
      DirCacheEditor editor = cache.editor();
      try {
        DirCacheIterator indexIter = new DirCacheIterator(cache);
        FileTreeIterator workingIter = new FileTreeIterator(repo);

        TreeWalk treeWalk = new TreeWalk(reader);
        treeWalk.setRecursive(true);
        treeWalk.setFilter(new StashDiffFilter());

        treeWalk.addTree(stashHeadIter);
        treeWalk.addTree(stashIndexIter);
        treeWalk.addTree(stashWorkingIter);
        treeWalk.addTree(headIter);
        treeWalk.addTree(indexIter);
        treeWalk.addTree(workingIter);

        scanForConflicts(treeWalk);

        // Reset trees and walk
        treeWalk.reset();
        stashWorkingIter.reset(reader, stashWorkingTree);
        stashIndexIter.reset(reader, stashIndexTree);
        stashHeadIter.reset(reader, stashHeadTree);
        treeWalk.addTree(stashHeadIter);
        treeWalk.addTree(stashIndexIter);
        treeWalk.addTree(stashWorkingIter);

        applyChanges(treeWalk, cache, editor);
      } finally {
        editor.commit();
        cache.unlock();
      }
    } catch (JGitInternalException e) {
      throw e;
    } catch (IOException e) {
      throw new JGitInternalException(JGitText.get().stashApplyFailed, e);
    } finally {
      reader.release();
    }
    return stashId;
  }
}
TOP

Related Classes of org.eclipse.jgit.api.StashApplyCommand$StashDiffFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.