Browse Source
Adds a new command to stash the index and working directory changes in a commit stored in refs/stash Bug: 309355 Change-Id: I2ce85b1601b74b07e286a3f99feb358dfbdfe29c Signed-off-by: Chris Aniszczyk <zx@twitter.com>stable-2.0
Kevin Sawicki
13 years ago
committed by
Chris Aniszczyk
5 changed files with 726 additions and 0 deletions
@ -0,0 +1,397 @@ |
|||||||
|
/* |
||||||
|
* 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 static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertFalse; |
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
import static org.junit.Assert.assertNull; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry; |
||||||
|
import org.eclipse.jgit.lib.Constants; |
||||||
|
import org.eclipse.jgit.lib.Ref; |
||||||
|
import org.eclipse.jgit.lib.RepositoryTestCase; |
||||||
|
import org.eclipse.jgit.revwalk.RevCommit; |
||||||
|
import org.eclipse.jgit.revwalk.RevWalk; |
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk; |
||||||
|
import org.eclipse.jgit.treewalk.filter.TreeFilter; |
||||||
|
import org.eclipse.jgit.util.FileUtils; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
/** |
||||||
|
* Unit tests of {@link StashCreateCommand} |
||||||
|
*/ |
||||||
|
public class StashCreateCommandTest extends RepositoryTestCase { |
||||||
|
|
||||||
|
private RevCommit head; |
||||||
|
|
||||||
|
private Git git; |
||||||
|
|
||||||
|
private File committedFile; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() throws Exception { |
||||||
|
super.setUp(); |
||||||
|
git = Git.wrap(db); |
||||||
|
committedFile = writeTrashFile("file.txt", "content"); |
||||||
|
git.add().addFilepattern("file.txt").call(); |
||||||
|
head = git.commit().setMessage("add file").call(); |
||||||
|
assertNotNull(head); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Core validation to be performed on all stashed commits |
||||||
|
* |
||||||
|
* @param commit |
||||||
|
* @throws IOException |
||||||
|
*/ |
||||||
|
private void validateStashedCommit(final RevCommit commit) |
||||||
|
throws IOException { |
||||||
|
assertNotNull(commit); |
||||||
|
Ref stashRef = db.getRef(Constants.R_STASH); |
||||||
|
assertNotNull(stashRef); |
||||||
|
assertEquals(commit, stashRef.getObjectId()); |
||||||
|
assertNotNull(commit.getAuthorIdent()); |
||||||
|
assertEquals(commit.getAuthorIdent(), commit.getCommitterIdent()); |
||||||
|
assertEquals(2, commit.getParentCount()); |
||||||
|
|
||||||
|
// Load parents
|
||||||
|
RevWalk walk = new RevWalk(db); |
||||||
|
try { |
||||||
|
for (RevCommit parent : commit.getParents()) |
||||||
|
walk.parseBody(parent); |
||||||
|
} finally { |
||||||
|
walk.release(); |
||||||
|
} |
||||||
|
|
||||||
|
assertEquals(1, commit.getParent(1).getParentCount()); |
||||||
|
assertEquals(head, commit.getParent(1).getParent(0)); |
||||||
|
assertFalse("Head tree matches stashed commit tree", commit.getTree() |
||||||
|
.equals(head.getTree())); |
||||||
|
assertEquals(head, commit.getParent(0)); |
||||||
|
assertFalse(commit.getFullMessage().equals( |
||||||
|
commit.getParent(1).getFullMessage())); |
||||||
|
} |
||||||
|
|
||||||
|
private TreeWalk createTreeWalk() { |
||||||
|
TreeWalk walk = new TreeWalk(db); |
||||||
|
walk.setRecursive(true); |
||||||
|
walk.setFilter(TreeFilter.ANY_DIFF); |
||||||
|
return walk; |
||||||
|
} |
||||||
|
|
||||||
|
private List<DiffEntry> diffWorkingAgainstHead(final RevCommit commit) |
||||||
|
throws IOException { |
||||||
|
TreeWalk walk = createTreeWalk(); |
||||||
|
try { |
||||||
|
walk.addTree(commit.getParent(0).getTree()); |
||||||
|
walk.addTree(commit.getTree()); |
||||||
|
return DiffEntry.scan(walk); |
||||||
|
} finally { |
||||||
|
walk.release(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private List<DiffEntry> diffIndexAgainstHead(final RevCommit commit) |
||||||
|
throws IOException { |
||||||
|
TreeWalk walk = createTreeWalk(); |
||||||
|
try { |
||||||
|
walk.addTree(commit.getParent(0).getTree()); |
||||||
|
walk.addTree(commit.getParent(1).getTree()); |
||||||
|
return DiffEntry.scan(walk); |
||||||
|
} finally { |
||||||
|
walk.release(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void noLocalChanges() throws Exception { |
||||||
|
assertNull(git.stashCreate().call()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void workingDirectoryDelete() throws Exception { |
||||||
|
deleteTrashFile("file.txt"); |
||||||
|
RevCommit stashed = git.stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertEquals("content", read(committedFile)); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertEquals(head.getTree(), stashed.getParent(1).getTree()); |
||||||
|
|
||||||
|
List<DiffEntry> diffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, diffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.DELETE, diffs.get(0).getChangeType()); |
||||||
|
assertEquals("file.txt", diffs.get(0).getOldPath()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void indexAdd() throws Exception { |
||||||
|
File addedFile = writeTrashFile("file2.txt", "content2"); |
||||||
|
git.add().addFilepattern("file2.txt").call(); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertFalse(addedFile.exists()); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertEquals(stashed.getTree(), stashed.getParent(1).getTree()); |
||||||
|
|
||||||
|
List<DiffEntry> diffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, diffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.ADD, diffs.get(0).getChangeType()); |
||||||
|
assertEquals("file2.txt", diffs.get(0).getNewPath()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void indexDelete() throws Exception { |
||||||
|
git.rm().addFilepattern("file.txt").call(); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertEquals("content", read(committedFile)); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertEquals(stashed.getTree(), stashed.getParent(1).getTree()); |
||||||
|
|
||||||
|
List<DiffEntry> diffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, diffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.DELETE, diffs.get(0).getChangeType()); |
||||||
|
assertEquals("file.txt", diffs.get(0).getOldPath()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void workingDirectoryModify() throws Exception { |
||||||
|
writeTrashFile("file.txt", "content2"); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertEquals("content", read(committedFile)); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertEquals(head.getTree(), stashed.getParent(1).getTree()); |
||||||
|
|
||||||
|
List<DiffEntry> diffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, diffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.MODIFY, diffs.get(0).getChangeType()); |
||||||
|
assertEquals("file.txt", diffs.get(0).getNewPath()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void workingDirectoryModifyInSubfolder() throws Exception { |
||||||
|
String path = "d1/d2/f.txt"; |
||||||
|
File subfolderFile = writeTrashFile(path, "content"); |
||||||
|
git.add().addFilepattern(path).call(); |
||||||
|
head = git.commit().setMessage("add file").call(); |
||||||
|
|
||||||
|
writeTrashFile(path, "content2"); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertEquals("content", read(subfolderFile)); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertEquals(head.getTree(), stashed.getParent(1).getTree()); |
||||||
|
|
||||||
|
List<DiffEntry> diffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, diffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.MODIFY, diffs.get(0).getChangeType()); |
||||||
|
assertEquals(path, diffs.get(0).getNewPath()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void workingDirectoryModifyIndexChanged() throws Exception { |
||||||
|
writeTrashFile("file.txt", "content2"); |
||||||
|
git.add().addFilepattern("file.txt").call(); |
||||||
|
writeTrashFile("file.txt", "content3"); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertEquals("content", read(committedFile)); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertFalse(stashed.getTree().equals(stashed.getParent(1).getTree())); |
||||||
|
|
||||||
|
List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, workingDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.MODIFY, workingDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals("file.txt", workingDiffs.get(0).getNewPath()); |
||||||
|
|
||||||
|
List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed); |
||||||
|
assertEquals(1, indexDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.MODIFY, indexDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals("file.txt", indexDiffs.get(0).getNewPath()); |
||||||
|
|
||||||
|
assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0) |
||||||
|
.getOldId()); |
||||||
|
assertFalse(workingDiffs.get(0).getNewId() |
||||||
|
.equals(indexDiffs.get(0).getNewId())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void workingDirectoryCleanIndexModify() throws Exception { |
||||||
|
writeTrashFile("file.txt", "content2"); |
||||||
|
git.add().addFilepattern("file.txt").call(); |
||||||
|
writeTrashFile("file.txt", "content"); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertEquals("content", read(committedFile)); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertTrue(stashed.getTree().equals(stashed.getParent(1).getTree())); |
||||||
|
|
||||||
|
List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, workingDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.MODIFY, workingDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals("file.txt", workingDiffs.get(0).getNewPath()); |
||||||
|
|
||||||
|
List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed); |
||||||
|
assertEquals(1, indexDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.MODIFY, indexDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals("file.txt", indexDiffs.get(0).getNewPath()); |
||||||
|
|
||||||
|
assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0) |
||||||
|
.getOldId()); |
||||||
|
assertTrue(workingDiffs.get(0).getNewId() |
||||||
|
.equals(indexDiffs.get(0).getNewId())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void workingDirectoryDeleteIndexAdd() throws Exception { |
||||||
|
String path = "file2.txt"; |
||||||
|
File added = writeTrashFile(path, "content2"); |
||||||
|
assertTrue(added.exists()); |
||||||
|
git.add().addFilepattern(path).call(); |
||||||
|
FileUtils.delete(added); |
||||||
|
assertFalse(added.exists()); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertFalse(added.exists()); |
||||||
|
|
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertTrue(stashed.getTree().equals(stashed.getParent(1).getTree())); |
||||||
|
|
||||||
|
List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, workingDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.ADD, workingDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals(path, workingDiffs.get(0).getNewPath()); |
||||||
|
|
||||||
|
List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed); |
||||||
|
assertEquals(1, indexDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.ADD, indexDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals(path, indexDiffs.get(0).getNewPath()); |
||||||
|
|
||||||
|
assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0) |
||||||
|
.getOldId()); |
||||||
|
assertTrue(workingDiffs.get(0).getNewId() |
||||||
|
.equals(indexDiffs.get(0).getNewId())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void workingDirectoryDeleteIndexEdit() throws Exception { |
||||||
|
File edited = writeTrashFile("file.txt", "content2"); |
||||||
|
git.add().addFilepattern("file.txt").call(); |
||||||
|
FileUtils.delete(edited); |
||||||
|
assertFalse(edited.exists()); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertEquals("content", read(committedFile)); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertFalse(stashed.getTree().equals(stashed.getParent(1).getTree())); |
||||||
|
|
||||||
|
List<DiffEntry> workingDiffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(1, workingDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.DELETE, workingDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals("file.txt", workingDiffs.get(0).getOldPath()); |
||||||
|
|
||||||
|
List<DiffEntry> indexDiffs = diffIndexAgainstHead(stashed); |
||||||
|
assertEquals(1, indexDiffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.MODIFY, indexDiffs.get(0) |
||||||
|
.getChangeType()); |
||||||
|
assertEquals("file.txt", indexDiffs.get(0).getNewPath()); |
||||||
|
|
||||||
|
assertEquals(workingDiffs.get(0).getOldId(), indexDiffs.get(0) |
||||||
|
.getOldId()); |
||||||
|
assertFalse(workingDiffs.get(0).getNewId() |
||||||
|
.equals(indexDiffs.get(0).getNewId())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void multipleEdits() throws Exception { |
||||||
|
git.rm().addFilepattern("file.txt").call(); |
||||||
|
File addedFile = writeTrashFile("file2.txt", "content2"); |
||||||
|
git.add().addFilepattern("file2.txt").call(); |
||||||
|
|
||||||
|
RevCommit stashed = Git.wrap(db).stashCreate().call(); |
||||||
|
assertNotNull(stashed); |
||||||
|
assertFalse(addedFile.exists()); |
||||||
|
validateStashedCommit(stashed); |
||||||
|
|
||||||
|
assertEquals(stashed.getTree(), stashed.getParent(1).getTree()); |
||||||
|
|
||||||
|
List<DiffEntry> diffs = diffWorkingAgainstHead(stashed); |
||||||
|
assertEquals(2, diffs.size()); |
||||||
|
assertEquals(DiffEntry.ChangeType.DELETE, diffs.get(0).getChangeType()); |
||||||
|
assertEquals("file.txt", diffs.get(0).getOldPath()); |
||||||
|
assertEquals(DiffEntry.ChangeType.ADD, diffs.get(1).getChangeType()); |
||||||
|
assertEquals("file2.txt", diffs.get(1).getNewPath()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,316 @@ |
|||||||
|
/* |
||||||
|
* 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.JGitText; |
||||||
|
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.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> |
||||||
|
*/ |
||||||
|
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 |
||||||
|
*/ |
||||||
|
public void setPerson(PersonIdent person) { |
||||||
|
this.person = person; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the reference to update with the stashed commit id |
||||||
|
* <p> |
||||||
|
* This value defaults to {@link Constants#R_STASH} |
||||||
|
* |
||||||
|
* @param ref |
||||||
|
*/ |
||||||
|
public void setRef(String ref) { |
||||||
|
this.ref = ref; |
||||||
|
} |
||||||
|
|
||||||
|
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) throws IOException { |
||||||
|
Ref currentRef = repo.getRef(ref); |
||||||
|
RefUpdate refUpdate = repo.updateRef(ref); |
||||||
|
refUpdate.setNewObjectId(commitId); |
||||||
|
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 |
||||||
|
*/ |
||||||
|
public RevCommit call() throws GitAPIException, JGitInternalException { |
||||||
|
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()); |
||||||
|
InputStream in = wtIter.openEntryStream(); |
||||||
|
try { |
||||||
|
entry.setObjectId(inserter.insert( |
||||||
|
Constants.OBJ_BLOB, |
||||||
|
wtIter.getEntryLength(), 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); |
||||||
|
} 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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue