Package org.eclipse.jgit.api

Source Code of org.eclipse.jgit.api.StashCreateCommand

/*
* 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.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
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.UnmergedPathException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.IndexDiffFilter;
import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter;
import org.eclipse.jgit.util.FileUtils;

/**
* Command class to stash changes in the working directory and index in a
* 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 StashCreateCommand extends GitCommand<RevCommit> {

  private static final String MSG_INDEX = "index on {0}: {1} {2}";

  private static final String MSG_UNTRACKED = "untracked files on {0}: {1} {2}";

  private static final String MSG_WORKING_DIR = "WIP on {0}: {1} {2}";

  private String indexMessage = MSG_INDEX;

  private String workingDirectoryMessage = MSG_WORKING_DIR;

  private String ref = Constants.R_STASH;

  private PersonIdent person;

  private boolean includeUntracked;

  /**
   * Create a command to stash changes in the working directory and index
   *
   * @param repo
   */
  public StashCreateCommand(Repository repo) {
    super(repo);
    person = new PersonIdent(repo);
  }

  /**
   * Set the message used when committing index changes
   * <p>
   * The message will be formatted with the current branch, abbreviated commit
   * id, and short commit message when used.
   *
   * @param message
   * @return {@code this}
   */
  public StashCreateCommand setIndexMessage(String message) {
    indexMessage = message;
    return this;
  }

  /**
   * Set the message used when committing working directory changes
   * <p>
   * The message will be formatted with the current branch, abbreviated commit
   * id, and short commit message when used.
   *
   * @param message
   * @return {@code this}
   */
  public StashCreateCommand setWorkingDirectoryMessage(String message) {
    workingDirectoryMessage = message;
    return this;
  }

  /**
   * Set the person to use as the author and committer in the commits made
   *
   * @param person
   * @return {@code this}
   */
  public StashCreateCommand setPerson(PersonIdent person) {
    this.person = person;
    return this;
  }

  /**
   * Set the reference to update with the stashed commit id
   * If null, no reference is updated
   * <p>
   * This value defaults to {@link Constants#R_STASH}
   *
   * @param ref
   * @return {@code this}
   */
  public StashCreateCommand setRef(String ref) {
    this.ref = ref;
    return this;
  }

  /**
   * Whether to include untracked files in the stash.
   *
   * @param includeUntracked
   * @return {@code this}
   * @since 3.4
   */
  public StashCreateCommand setIncludeUntracked(boolean includeUntracked) {
    this.includeUntracked = includeUntracked;
    return this;
  }

  private RevCommit parseCommit(final ObjectReader reader,
      final ObjectId headId) throws IOException {
    final RevWalk walk = new RevWalk(reader);
    walk.setRetainBody(true);
    return walk.parseCommit(headId);
  }

  private CommitBuilder createBuilder() {
    CommitBuilder builder = new CommitBuilder();
    PersonIdent author = person;
    if (author == null)
      author = new PersonIdent(repo);
    builder.setAuthor(author);
    builder.setCommitter(author);
    return builder;
  }

  private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
      String refLogMessage) throws IOException {
    if (ref == null)
      return;
    Ref currentRef = repo.getRef(ref);
    RefUpdate refUpdate = repo.updateRef(ref);
    refUpdate.setNewObjectId(commitId);
    refUpdate.setRefLogIdent(refLogIdent);
    refUpdate.setRefLogMessage(refLogMessage, false);
    if (currentRef != null)
      refUpdate.setExpectedOldObjectId(currentRef.getObjectId());
    else
      refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
    refUpdate.forceUpdate();
  }

  private Ref getHead() throws GitAPIException {
    try {
      Ref head = repo.getRef(Constants.HEAD);
      if (head == null || head.getObjectId() == null)
        throw new NoHeadException(JGitText.get().headRequiredToStash);
      return head;
    } catch (IOException e) {
      throw new JGitInternalException(JGitText.get().stashFailed, e);
    }
  }

  /**
   * Stash the contents on the working directory and index in separate commits
   * and reset to the current HEAD commit.
   *
   * @return stashed commit or null if no changes to stash
   * @throws GitAPIException
   */
  public RevCommit call() throws GitAPIException {
    checkCallable();

    Ref head = getHead();
    ObjectReader reader = repo.newObjectReader();
    try {
      RevCommit headCommit = parseCommit(reader, head.getObjectId());
      DirCache cache = repo.lockDirCache();
      ObjectInserter inserter = repo.newObjectInserter();
      ObjectId commitId;
      try {
        TreeWalk treeWalk = new TreeWalk(reader);
        treeWalk.setRecursive(true);
        treeWalk.addTree(headCommit.getTree());
        treeWalk.addTree(new DirCacheIterator(cache));
        treeWalk.addTree(new FileTreeIterator(repo));
        treeWalk.setFilter(AndTreeFilter.create(new SkipWorkTreeFilter(
            1), new IndexDiffFilter(1, 2)));

        // Return null if no local changes to stash
        if (!treeWalk.next())
          return null;

        MutableObjectId id = new MutableObjectId();
        List<PathEdit> wtEdits = new ArrayList<PathEdit>();
        List<String> wtDeletes = new ArrayList<String>();
        List<DirCacheEntry> untracked = new ArrayList<DirCacheEntry>();
        boolean hasChanges = false;
        do {
          AbstractTreeIterator headIter = treeWalk.getTree(0,
              AbstractTreeIterator.class);
          DirCacheIterator indexIter = treeWalk.getTree(1,
              DirCacheIterator.class);
          WorkingTreeIterator wtIter = treeWalk.getTree(2,
              WorkingTreeIterator.class);
          if (indexIter != null
              && !indexIter.getDirCacheEntry().isMerged())
            throw new UnmergedPathsException(
                new UnmergedPathException(
                    indexIter.getDirCacheEntry()));
          if (wtIter != null) {
            if (indexIter == null && headIter == null
                && !includeUntracked)
              continue;
            hasChanges = true;
            if (indexIter != null && wtIter.idEqual(indexIter))
              continue;
            if (headIter != null && wtIter.idEqual(headIter))
              continue;
            treeWalk.getObjectId(id, 0);
            final DirCacheEntry entry = new DirCacheEntry(
                treeWalk.getRawPath());
            entry.setLength(wtIter.getEntryLength());
            entry.setLastModified(wtIter.getEntryLastModified());
            entry.setFileMode(wtIter.getEntryFileMode());
            long contentLength = wtIter.getEntryContentLength();
            InputStream in = wtIter.openEntryStream();
            try {
              entry.setObjectId(inserter.insert(
                  Constants.OBJ_BLOB, contentLength, in));
            } finally {
              in.close();
            }

            if (indexIter == null && headIter == null)
              untracked.add(entry);
            else
              wtEdits.add(new PathEdit(entry) {
                public void apply(DirCacheEntry ent) {
                  ent.copyMetaData(entry);
                }
              });
          }
          hasChanges = true;
          if (wtIter == null && headIter != null)
            wtDeletes.add(treeWalk.getPathString());
        } while (treeWalk.next());

        if (!hasChanges)
          return null;

        String branch = Repository.shortenRefName(head.getTarget()
            .getName());

        // Commit index changes
        CommitBuilder builder = createBuilder();
        builder.setParentId(headCommit);
        builder.setTreeId(cache.writeTree(inserter));
        builder.setMessage(MessageFormat.format(indexMessage, branch,
            headCommit.abbreviate(7).name(),
            headCommit.getShortMessage()));
        ObjectId indexCommit = inserter.insert(builder);

        // Commit untracked changes
        ObjectId untrackedCommit = null;
        if (!untracked.isEmpty()) {
          DirCache untrackedDirCache = DirCache.newInCore();
          DirCacheBuilder untrackedBuilder = untrackedDirCache
              .builder();
          for (DirCacheEntry entry : untracked)
            untrackedBuilder.add(entry);
          untrackedBuilder.finish();

          builder.setParentIds(new ObjectId[0]);
          builder.setTreeId(untrackedDirCache.writeTree(inserter));
          builder.setMessage(MessageFormat.format(MSG_UNTRACKED,
              branch, headCommit.abbreviate(7).name(),
              headCommit.getShortMessage()));
          untrackedCommit = inserter.insert(builder);
        }

        // Commit working tree changes
        if (!wtEdits.isEmpty() || !wtDeletes.isEmpty()) {
          DirCacheEditor editor = cache.editor();
          for (PathEdit edit : wtEdits)
            editor.add(edit);
          for (String path : wtDeletes)
            editor.add(new DeletePath(path));
          editor.finish();
        }
        builder.setParentId(headCommit);
        builder.addParentId(indexCommit);
        if (untrackedCommit != null)
          builder.addParentId(untrackedCommit);
        builder.setMessage(MessageFormat.format(
            workingDirectoryMessage, branch,
            headCommit.abbreviate(7).name(),
            headCommit.getShortMessage()));
        builder.setTreeId(cache.writeTree(inserter));
        commitId = inserter.insert(builder);
        inserter.flush();

        updateStashRef(commitId, builder.getAuthor(),
            builder.getMessage());

        // Remove untracked files
        if (includeUntracked) {
          for (DirCacheEntry entry : untracked) {
            File file = new File(repo.getWorkTree(),
                entry.getPathString());
            FileUtils.delete(file);
          }
        }

      } finally {
        inserter.release();
        cache.unlock();
      }

      // Hard reset to HEAD
      new ResetCommand(repo).setMode(ResetType.HARD).call();

      // Return stashed commit
      return parseCommit(reader, commitId);
    } catch (IOException e) {
      throw new JGitInternalException(JGitText.get().stashFailed, e);
    } finally {
      reader.release();
    }
  }
}
TOP

Related Classes of org.eclipse.jgit.api.StashCreateCommand

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.