@ -49,12 +49,16 @@ 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 ;
@ -65,6 +69,7 @@ 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 ;
@ -75,11 +80,45 @@ import org.eclipse.jgit.util.FileUtils;
*
* @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 ;
/ * *
@ -105,86 +144,230 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
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 ;
}
/ * *
* Apply the changes in a stashed commit to the working directory and index
* Would unstashing overwrite local changes ?
*
* @return id of stashed commit that was applied
* @param stashIndexIter
* @param stashWorkingTreeIter
* @param headIter
* @param indexIter
* @param workingTreeIter
* @return true if unstash conflict , false otherwise
* /
public ObjectId call ( ) throws GitAPIException , JGitInternalException {
checkCallable ( ) ;
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 ) ) ;
if ( repo . getRepositoryState ( ) ! = RepositoryState . SAFE )
throw new WrongRepositoryStateException ( MessageFormat . format (
JGitText . get ( ) . stashApplyOnUnsafeRepository ,
repo . getRepositoryState ( ) ) ) ;
// 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 JGitInternalException ,
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 JGitInternalException , GitAPIException {
final String revision = stashRef ! = null ? stashRef : DEFAULT_REF ;
final ObjectId stashId ;
try {
stashId = repo . resolve ( revision ) ;
} catch ( IOException e ) {
throw new JGitInternalException ( JGitText . get ( ) . stashApplyFailed , 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
* /
public ObjectId call ( ) throws GitAPIException , JGitInternalException {
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 wtCommit = revWalk . parseCommit ( stashId ) ;
if ( wtCommit . getParentCount ( ) ! = 2 )
RevCommit stash Commit = revWalk . parseCommit ( stashId ) ;
if ( stash Commit. getParentCount ( ) ! = 2 )
throw new JGitInternalException ( MessageFormat . format (
JGitText . get ( ) . stashCommitMissingTwoParents ,
stashId . name ( ) ) ) ;
// Apply index changes
RevTree indexTree = revWalk . parseCommit ( wtCommit . getParent ( 1 ) )
. getTree ( ) ;
DirCacheCheckout dco = new DirCacheCheckout ( repo ,
repo . lockDirCache ( ) , indexTree , new FileTreeIterator ( repo ) ) ;
dco . setFailOnConflict ( true ) ;
dco . checkout ( ) ;
// Apply working directory changes
RevTree headTree = revWalk . parseCommit ( wtCommit . getParent ( 0 ) )
. getTree ( ) ;
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 . addTree ( headTree ) ;
treeWalk . addTree ( indexTree ) ;
treeWalk . addTree ( wtCommit . getTree ( ) ) ;
treeWalk . setFilter ( TreeFilter . ANY_DIFF ) ;
File workingTree = repo . getWorkTree ( ) ;
while ( treeWalk . next ( ) ) {
String path = treeWalk . getPathString ( ) ;
File file = new File ( workingTree , path ) ;
AbstractTreeIterator headIter = treeWalk . getTree ( 0 ,
AbstractTreeIterator . class ) ;
AbstractTreeIterator indexIter = treeWalk . getTree ( 1 ,
AbstractTreeIterator . class ) ;
AbstractTreeIterator wtIter = treeWalk . getTree ( 2 ,
AbstractTreeIterator . class ) ;
if ( wtIter ! = null ) {
DirCacheEntry entry = new DirCacheEntry (
treeWalk . getRawPath ( ) ) ;
entry . setObjectId ( wtIter . getEntryObjectId ( ) ) ;
DirCacheCheckout . checkoutEntry ( repo , file , entry ) ;
} else {
if ( indexIter ! = null & & headIter ! = null
& & ! indexIter . idEqual ( headIter ) )
editor . add ( new DeletePath ( path ) ) ;
FileUtils . delete ( file , FileUtils . RETRY
| FileUtils . SKIP_MISSING ) ;
}
}
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 {