@ -42,7 +42,6 @@
* /
* /
package org.eclipse.jgit.api ;
package org.eclipse.jgit.api ;
import java.io.File ;
import java.io.IOException ;
import java.io.IOException ;
import java.text.MessageFormat ;
import java.text.MessageFormat ;
@ -50,77 +49,47 @@ import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRefNameException ;
import org.eclipse.jgit.api.errors.InvalidRefNameException ;
import org.eclipse.jgit.api.errors.JGitInternalException ;
import org.eclipse.jgit.api.errors.JGitInternalException ;
import org.eclipse.jgit.api.errors.NoHeadException ;
import org.eclipse.jgit.api.errors.NoHeadException ;
import org.eclipse.jgit.api.errors.StashApplyFailureException ;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException ;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException ;
import org.eclipse.jgit.dircache.DirCache ;
import org.eclipse.jgit.dircache.DirCache ;
import org.eclipse.jgit.dircache.DirCacheBuilder ;
import org.eclipse.jgit.dircache.DirCacheCheckout ;
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.DirCacheEntry ;
import org.eclipse.jgit.dircache.DirCacheIterator ;
import org.eclipse.jgit.dircache.DirCacheIterator ;
import org.eclipse.jgit.errors.CheckoutConflictException ;
import org.eclipse.jgit.internal.JGitText ;
import org.eclipse.jgit.internal.JGitText ;
import org.eclipse.jgit.lib.Constants ;
import org.eclipse.jgit.lib.Constants ;
import org.eclipse.jgit.lib.ObjectId ;
import org.eclipse.jgit.lib.ObjectId ;
import org.eclipse.jgit.lib.ObjectReader ;
import org.eclipse.jgit.lib.ObjectReader ;
import org.eclipse.jgit.lib.Repository ;
import org.eclipse.jgit.lib.Repository ;
import org.eclipse.jgit.lib.RepositoryState ;
import org.eclipse.jgit.lib.RepositoryState ;
import org.eclipse.jgit.merge.MergeStrategy ;
import org.eclipse.jgit.merge.ResolveMerger ;
import org.eclipse.jgit.revwalk.RevCommit ;
import org.eclipse.jgit.revwalk.RevCommit ;
import org.eclipse.jgit.revwalk.RevTree ;
import org.eclipse.jgit.revwalk.RevTree ;
import org.eclipse.jgit.revwalk.RevWalk ;
import org.eclipse.jgit.revwalk.RevWalk ;
import org.eclipse.jgit.treewalk.AbstractTreeIterator ;
import org.eclipse.jgit.treewalk.AbstractTreeIterator ;
import org.eclipse.jgit.treewalk.CanonicalTreeParser ;
import org.eclipse.jgit.treewalk.FileTreeIterator ;
import org.eclipse.jgit.treewalk.FileTreeIterator ;
import org.eclipse.jgit.treewalk.TreeWalk ;
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 .
* Command class to apply a stashed commit .
*
*
* This class behaves like < em > git stash apply - - index < / em > , i . e . it tries to
* recover the stashed index state in addition to the working tree state .
*
* @see < a href = "http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
* @see < a href = "http://www.kernel.org/pub/software/scm/git/docs/git-stash.html"
* > Git documentation about Stash < / a >
* > Git documentation about Stash < / a >
*
* @since 2 . 0
* @since 2 . 0
* /
* /
public class StashApplyCommand extends GitCommand < ObjectId > {
public class StashApplyCommand extends GitCommand < ObjectId > {
private static final String DEFAULT_REF = Constants . STASH + "@{0}" ; //$NON-NLS-1$
private static final String DEFAULT_REF = Constants . STASH + "@{0}" ; //$NON-NLS-1$
/ * *
* 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" ; //$NON-NLS-1$
}
}
private String stashRef ;
private String stashRef ;
private boolean applyIndex = true ;
/ * *
/ * *
* Create command to apply the changes of a stashed commit
* Create command to apply the changes of a stashed commit
*
*
@ -144,63 +113,6 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
return this ;
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}" ) ; //$NON-NLS-1$
} 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 {
private ObjectId getStashId ( ) throws GitAPIException {
final String revision = stashRef ! = null ? stashRef : DEFAULT_REF ;
final String revision = stashRef ! = null ? stashRef : DEFAULT_REF ;
final ObjectId stashId ;
final ObjectId stashId ;
@ -216,91 +128,19 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
return stashId ;
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
* Apply the changes in a stashed commit to the working directory and index
*
*
* @return id of stashed commit that was applied
* @return id of stashed commit that was applied TODO : Does anyone depend on
* this , or could we make it more like Merge / CherryPick / Revert ?
* @throws GitAPIException
* @throws GitAPIException
* @throws WrongRepositoryStateException
* @throws WrongRepositoryStateException
* @throws NoHeadException
* @throws StashApplyFailureException
* /
* /
public ObjectId call ( ) throws GitAPIException ,
public ObjectId call ( ) throws GitAPIException ,
WrongRepositoryStateException {
WrongRepositoryStateException , NoHeadException ,
StashApplyFailureException {
checkCallable ( ) ;
checkCallable ( ) ;
if ( repo . getRepositoryState ( ) ! = RepositoryState . SAFE )
if ( repo . getRepositoryState ( ) ! = RepositoryState . SAFE )
@ -308,73 +148,114 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
JGitText . get ( ) . stashApplyOnUnsafeRepository ,
JGitText . get ( ) . stashApplyOnUnsafeRepository ,
repo . getRepositoryState ( ) ) ) ;
repo . getRepositoryState ( ) ) ) ;
final ObjectId headTree = getHeadTree ( ) ;
final ObjectId stashId = getStashId ( ) ;
ObjectReader reader = repo . newObjectReader ( ) ;
ObjectReader reader = repo . newObjectReader ( ) ;
try {
try {
RevWalk revWalk = new RevWalk ( reader ) ;
RevWalk revWalk = new RevWalk ( reader ) ;
ObjectId headCommit = repo . resolve ( Constants . HEAD ) ;
if ( headCommit = = null )
throw new NoHeadException ( JGitText . get ( ) . stashApplyWithoutHead ) ;
final ObjectId stashId = getStashId ( ) ;
RevCommit stashCommit = revWalk . parseCommit ( stashId ) ;
RevCommit stashCommit = revWalk . parseCommit ( stashId ) ;
if ( stashCommit . getParentCount ( ) ! = 2 )
if ( stashCommit . getParentCount ( ) ! = 2 )
throw new JGitInternalException ( MessageFormat . format (
throw new JGitInternalException ( MessageFormat . format (
JGitText . get ( ) . stashCommitMissingTwoParents ,
JGitText . get ( ) . stashCommitMissingTwoParents ,
stashId . name ( ) ) ) ;
stashId . name ( ) ) ) ;
RevTree stashWorkingTree = stashCommit . getTree ( ) ;
ObjectId headTree = repo . resolve ( Constants . HEAD + "^{tree}" ) ;
RevTree stashIndexTree = revWalk . parseCommit (
ObjectId stashIndexCommit = revWalk . parseCommit ( stashCommit
stashCommit . getParent ( 1 ) ) . getTree ( ) ;
. getParent ( 1 ) ) ;
RevTree stashHeadTree = revWalk . parseCommit (
ObjectId stashHeadCommit = stashCommit . getParent ( 0 ) ;
stashCommit . getParent ( 0 ) ) . getTree ( ) ;
ResolveMerger merger = ( ResolveMerger ) MergeStrategy . RESOLVE
CanonicalTreeParser stashWorkingIter = new CanonicalTreeParser ( ) ;
. newMerger ( repo ) ;
stashWorkingIter . reset ( reader , stashWorkingTree ) ;
merger . setCommitNames ( new String [ ] { "stashed HEAD" , "HEAD" ,
CanonicalTreeParser stashIndexIter = new CanonicalTreeParser ( ) ;
"stash" } ) ;
stashIndexIter . reset ( reader , stashIndexTree ) ;
merger . setBase ( stashHeadCommit ) ;
CanonicalTreeParser stashHeadIter = new CanonicalTreeParser ( ) ;
merger . setWorkingTreeIterator ( new FileTreeIterator ( repo ) ) ;
stashHeadIter . reset ( reader , stashHeadTree ) ;
if ( merger . merge ( headCommit , stashCommit ) ) {
CanonicalTreeParser headIter = new CanonicalTreeParser ( ) ;
DirCache dc = repo . lockDirCache ( ) ;
headIter . reset ( reader , headTree ) ;
DirCacheCheckout dco = new DirCacheCheckout ( repo , headTree ,
dc , merger . getResultTreeId ( ) ) ;
dco . setFailOnConflict ( true ) ;
dco . checkout ( ) ; // Ignoring failed deletes....
if ( applyIndex ) {
ResolveMerger ixMerger = ( ResolveMerger ) MergeStrategy . RESOLVE
. newMerger ( repo , true ) ;
ixMerger . setCommitNames ( new String [ ] { "stashed HEAD" ,
"HEAD" , "stashed index" } ) ;
boolean ok = ixMerger . merge ( headCommit , stashIndexCommit ) ;
if ( ok ) {
resetIndex ( revWalk
. parseTree ( ixMerger . getResultTreeId ( ) ) ) ;
} else {
throw new StashApplyFailureException (
JGitText . get ( ) . stashApplyConflict ) ;
}
}
} else {
throw new StashApplyFailureException (
JGitText . get ( ) . stashApplyConflict ) ;
}
return stashId ;
DirCache cache = repo . lockDirCache ( ) ;
} catch ( JGitInternalException e ) {
DirCacheEditor editor = cache . editor ( ) ;
throw e ;
try {
} catch ( IOException e ) {
DirCacheIterator indexIter = new DirCacheIterator ( cache ) ;
throw new JGitInternalException ( JGitText . get ( ) . stashApplyFailed , e ) ;
FileTreeIterator workingIter = new FileTreeIterator ( repo ) ;
} finally {
reader . release ( ) ;
}
}
TreeWalk treeWalk = new TreeWalk ( reader ) ;
/ * *
treeWalk . setRecursive ( true ) ;
* @param applyIndex
treeWalk . setFilter ( new StashDiffFilter ( ) ) ;
* true ( default ) if the command should restore the index state
* /
public void setApplyIndex ( boolean applyIndex ) {
this . applyIndex = applyIndex ;
}
treeWalk . addTree ( stashHeadIter ) ;
private void resetIndex ( RevTree tree ) throws IOException {
treeWalk . addTree ( stashIndexIter ) ;
DirCache dc = repo . lockDirCache ( ) ;
treeWalk . addTree ( stashWorkingIter ) ;
TreeWalk walk = null ;
treeWalk . addTree ( headIter ) ;
try {
treeWalk . addTree ( indexIter ) ;
DirCacheBuilder builder = dc . builder ( ) ;
treeWalk . addTree ( workingIter ) ;
walk = new TreeWalk ( repo ) ;
walk . addTree ( tree ) ;
walk . addTree ( new DirCacheIterator ( dc ) ) ;
walk . setRecursive ( true ) ;
while ( walk . next ( ) ) {
AbstractTreeIterator cIter = walk . getTree ( 0 ,
AbstractTreeIterator . class ) ;
if ( cIter = = null ) {
// Not in commit, don't add to new index
continue ;
}
scanForConflicts ( treeWalk ) ;
final DirCacheEntry entry = new DirCacheEntry ( walk . getRawPath ( ) ) ;
entry . setFileMode ( cIter . getEntryFileMode ( ) ) ;
entry . setObjectIdFromRaw ( cIter . idBuffer ( ) , cIter . idOffset ( ) ) ;
// Reset trees and walk
DirCacheIterator dcIter = walk . getTree ( 1 ,
treeWalk . reset ( ) ;
DirCacheIterator . class ) ;
stashWorkingIter . reset ( reader , stashWorkingTree ) ;
if ( dcIter ! = null & & dcIter . idEqual ( cIter ) ) {
stashIndexIter . reset ( reader , stashIndexTree ) ;
DirCacheEntry indexEntry = dcIter . getDirCacheEntry ( ) ;
stashHeadIter . reset ( reader , stashHeadTree ) ;
entry . setLastModified ( indexEntry . getLastModified ( ) ) ;
treeWalk . addTree ( stashHeadIter ) ;
entry . setLength ( indexEntry . getLength ( ) ) ;
treeWalk . addTree ( stashIndexIter ) ;
}
treeWalk . addTree ( stashWorkingIter ) ;
applyChanges ( treeWalk , cache , editor ) ;
builder . add ( entry ) ;
} finally {
editor . commit ( ) ;
cache . unlock ( ) ;
}
}
} catch ( JGitInternalException e ) {
throw e ;
builder . commit ( ) ;
} catch ( IOException e ) {
throw new JGitInternalException ( JGitText . get ( ) . stashApplyFailed , e ) ;
} finally {
} finally {
reader . release ( ) ;
dc . unlock ( ) ;
if ( walk ! = null )
walk . release ( ) ;
}
}
return stashId ;
}
}
}
}