123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- /*
- * 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.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.dircache.DirCache;
- 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.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;
-
- /**
- * 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_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;
-
- /**
- * 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
- * <p>
- * This value defaults to {@link Constants#R_STASH}
- *
- * @param ref
- * @return {@code this}
- */
- public StashCreateCommand setRef(String ref) {
- this.ref = ref;
- 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(ObjectId headId) {
- CommitBuilder builder = new CommitBuilder();
- PersonIdent author = person;
- if (author == null)
- author = new PersonIdent(repo);
- builder.setAuthor(author);
- builder.setCommitter(author);
- builder.setParentId(headId);
- return builder;
- }
-
- private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
- String refLogMessage) throws IOException {
- 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>();
- do {
- AbstractTreeIterator headIter = treeWalk.getTree(0,
- AbstractTreeIterator.class);
- DirCacheIterator indexIter = treeWalk.getTree(1,
- DirCacheIterator.class);
- WorkingTreeIterator wtIter = treeWalk.getTree(2,
- WorkingTreeIterator.class);
- if (headIter != null && indexIter != null && wtIter != null) {
- if (wtIter.idEqual(indexIter)
- || 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();
- }
- wtEdits.add(new PathEdit(entry) {
-
- public void apply(DirCacheEntry ent) {
- ent.copyMetaData(entry);
- }
- });
- } else if (indexIter == null)
- wtDeletes.add(treeWalk.getPathString());
- else if (wtIter == null && headIter != null)
- wtDeletes.add(treeWalk.getPathString());
- } while (treeWalk.next());
-
- String branch = Repository.shortenRefName(head.getTarget()
- .getName());
-
- // Commit index changes
- CommitBuilder builder = createBuilder(headCommit);
- builder.setTreeId(cache.writeTree(inserter));
- builder.setMessage(MessageFormat.format(indexMessage, branch,
- headCommit.abbreviate(7).name(),
- headCommit.getShortMessage()));
- ObjectId indexCommit = 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.addParentId(indexCommit);
- 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());
- } 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();
- }
- }
- }
|