@ -43,7 +43,9 @@
package org.eclipse.jgit.api ;
package org.eclipse.jgit.api ;
import java.io.IOException ;
import java.io.IOException ;
import java.io.InputStream ;
import java.text.MessageFormat ;
import java.text.MessageFormat ;
import java.util.ArrayList ;
import java.util.LinkedList ;
import java.util.LinkedList ;
import java.util.List ;
import java.util.List ;
@ -55,6 +57,12 @@ import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException ;
import org.eclipse.jgit.api.errors.NoMessageException ;
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.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.errors.UnmergedPathException ;
import org.eclipse.jgit.lib.CommitBuilder ;
import org.eclipse.jgit.lib.CommitBuilder ;
import org.eclipse.jgit.lib.Constants ;
import org.eclipse.jgit.lib.Constants ;
@ -68,6 +76,9 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState ;
import org.eclipse.jgit.lib.RepositoryState ;
import org.eclipse.jgit.revwalk.RevCommit ;
import org.eclipse.jgit.revwalk.RevCommit ;
import org.eclipse.jgit.revwalk.RevWalk ;
import org.eclipse.jgit.revwalk.RevWalk ;
import org.eclipse.jgit.treewalk.CanonicalTreeParser ;
import org.eclipse.jgit.treewalk.FileTreeIterator ;
import org.eclipse.jgit.treewalk.TreeWalk ;
/ * *
/ * *
* A class used to execute a { @code Commit } command . It has setters for all
* A class used to execute a { @code Commit } command . It has setters for all
@ -87,6 +98,10 @@ public class CommitCommand extends GitCommand<RevCommit> {
private boolean all ;
private boolean all ;
private List < String > only = new ArrayList < String > ( ) ;
private boolean [ ] onlyProcessed ;
private boolean amend ;
private boolean amend ;
/ * *
/ * *
@ -170,6 +185,9 @@ public class CommitCommand extends GitCommand<RevCommit> {
// lock the index
// lock the index
DirCache index = repo . lockDirCache ( ) ;
DirCache index = repo . lockDirCache ( ) ;
try {
try {
if ( ! only . isEmpty ( ) )
index = createTemporaryIndex ( headId , index ) ;
ObjectInserter odi = repo . newObjectInserter ( ) ;
ObjectInserter odi = repo . newObjectInserter ( ) ;
try {
try {
// Write the index as tree to the object database. This may
// Write the index as tree to the object database. This may
@ -241,6 +259,165 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
}
}
}
private DirCache createTemporaryIndex ( ObjectId headId , DirCache index )
throws IOException {
ObjectInserter inserter = null ;
// get DirCacheEditor to modify the index if required
DirCacheEditor dcEditor = index . editor ( ) ;
// get DirCacheBuilder for newly created in-core index to build a
// temporary index for this commit
DirCache inCoreIndex = DirCache . newInCore ( ) ;
DirCacheBuilder dcBuilder = inCoreIndex . builder ( ) ;
onlyProcessed = new boolean [ only . size ( ) ] ;
boolean emptyCommit = true ;
TreeWalk treeWalk = new TreeWalk ( repo ) ;
int dcIdx = treeWalk . addTree ( new DirCacheIterator ( index ) ) ;
int fIdx = treeWalk . addTree ( new FileTreeIterator ( repo ) ) ;
int hIdx = - 1 ;
if ( headId ! = null )
hIdx = treeWalk . addTree ( new RevWalk ( repo ) . parseTree ( headId ) ) ;
treeWalk . setRecursive ( true ) ;
while ( treeWalk . next ( ) ) {
String path = treeWalk . getPathString ( ) ;
// check if current entry's path matches a specified path
int pos = lookupOnly ( path ) ;
CanonicalTreeParser hTree = null ;
if ( hIdx ! = - 1 )
hTree = treeWalk . getTree ( hIdx , CanonicalTreeParser . class ) ;
if ( pos > = 0 ) {
// include entry in commit
DirCacheIterator dcTree = treeWalk . getTree ( dcIdx ,
DirCacheIterator . class ) ;
FileTreeIterator fTree = treeWalk . getTree ( fIdx ,
FileTreeIterator . class ) ;
// check if entry refers to a tracked file
boolean tracked = dcTree ! = null | | hTree ! = null ;
if ( ! tracked )
break ;
if ( fTree ! = null ) {
// create a new DirCacheEntry with data retrieved from disk
final DirCacheEntry dcEntry = new DirCacheEntry ( path ) ;
long entryLength = fTree . getEntryLength ( ) ;
dcEntry . setLength ( entryLength ) ;
dcEntry . setLastModified ( fTree . getEntryLastModified ( ) ) ;
dcEntry . setFileMode ( fTree . getEntryFileMode ( ) ) ;
boolean objectExists = ( dcTree ! = null & & fTree
. idEqual ( dcTree ) )
| | ( hTree ! = null & & fTree . idEqual ( hTree ) ) ;
if ( objectExists ) {
dcEntry . setObjectId ( fTree . getEntryObjectId ( ) ) ;
} else {
// insert object
if ( inserter = = null )
inserter = repo . newObjectInserter ( ) ;
InputStream inputStream = fTree . openEntryStream ( ) ;
try {
dcEntry . setObjectId ( inserter . insert (
Constants . OBJ_BLOB , entryLength ,
inputStream ) ) ;
} finally {
inputStream . close ( ) ;
}
}
// update index
dcEditor . add ( new PathEdit ( path ) {
@Override
public void apply ( DirCacheEntry ent ) {
ent . copyMetaData ( dcEntry ) ;
}
} ) ;
// add to temporary in-core index
dcBuilder . add ( dcEntry ) ;
if ( emptyCommit & & ( hTree = = null | | ! hTree . idEqual ( fTree ) ) )
// this is a change
emptyCommit = false ;
} else {
// if no file exists on disk, remove entry from index and
// don't add it to temporary in-core index
dcEditor . add ( new DeletePath ( path ) ) ;
if ( emptyCommit & & hTree ! = null )
// this is a change
emptyCommit = false ;
}
// keep track of processed path
onlyProcessed [ pos ] = true ;
} else {
// add entries from HEAD for all other paths
if ( hTree ! = null ) {
// create a new DirCacheEntry with data retrieved from HEAD
final DirCacheEntry dcEntry = new DirCacheEntry ( path ) ;
dcEntry . setObjectId ( hTree . getEntryObjectId ( ) ) ;
dcEntry . setFileMode ( hTree . getEntryFileMode ( ) ) ;
// add to temporary in-core index
dcBuilder . add ( dcEntry ) ;
}
}
}
// there must be no unprocessed paths left at this point; otherwise an
// untracked or unknown path has been specified
for ( int i = 0 ; i < onlyProcessed . length ; i + + )
if ( ! onlyProcessed [ i ] )
throw new JGitInternalException ( MessageFormat . format (
JGitText . get ( ) . entryNotFoundByPath , only . get ( i ) ) ) ;
// there must be at least one change
if ( emptyCommit )
throw new JGitInternalException ( JGitText . get ( ) . emptyCommit ) ;
// update index
dcEditor . commit ( ) ;
// finish temporary in-core index used for this commit
dcBuilder . finish ( ) ;
return inCoreIndex ;
}
/ * *
* Look an entry ' s path up in the list of paths specified by the - - only / - o
* option
*
* In case the complete ( file ) path ( e . g . "d1/d2/f1" ) cannot be found in
* < code > only < / code > , lookup is also tried with ( parent ) directory paths
* ( e . g . "d1/d2" and "d1" ) .
*
* @param pathString
* entry ' s path
* @return the item ' s index in < code > only < / code > ; - 1 if no item matches
* /
private int lookupOnly ( String pathString ) {
int i = 0 ;
for ( String o : only ) {
String p = pathString ;
while ( true ) {
if ( p . equals ( o ) )
return i ;
int l = p . lastIndexOf ( "/" ) ;
if ( l < 1 )
break ;
p = p . substring ( 0 , l ) ;
}
i + + ;
}
return - 1 ;
}
/ * *
/ * *
* Sets default values for not explicitly specified options . Then validates
* Sets default values for not explicitly specified options . Then validates
* that all required data has been provided .
* that all required data has been provided .
@ -386,14 +563,20 @@ public class CommitCommand extends GitCommand<RevCommit> {
/ * *
/ * *
* If set to true the Commit command automatically stages files that have
* If set to true the Commit command automatically stages files that have
* been modified and deleted , but new files you not known by the repository
* been modified and deleted , but new files not known by the repository are
* are not affected . This corresponds to the parameter - a on the command
* not affected . This corresponds to the parameter - a on the command line .
* line .
*
*
* @param all
* @param all
* @return { @code this }
* @return { @code this }
* @throws JGitInternalException
* in case of an illegal combination of arguments / options
* /
* /
public CommitCommand setAll ( boolean all ) {
public CommitCommand setAll ( boolean all ) {
checkCallable ( ) ;
if ( ! only . isEmpty ( ) )
throw new JGitInternalException ( MessageFormat . format (
JGitText . get ( ) . illegalCombinationOfArguments , "--all" ,
"--only" ) ) ;
this . all = all ;
this . all = all ;
return this ;
return this ;
}
}
@ -407,8 +590,33 @@ public class CommitCommand extends GitCommand<RevCommit> {
* @return { @code this }
* @return { @code this }
* /
* /
public CommitCommand setAmend ( boolean amend ) {
public CommitCommand setAmend ( boolean amend ) {
checkCallable ( ) ;
this . amend = amend ;
this . amend = amend ;
return this ;
return this ;
}
}
/ * *
* Commit dedicated path only
*
* This method can be called several times to add multiple paths . Full file
* paths are supported as well as directory paths ; in the latter case this
* commits all files / directories below the specified path .
*
* @param only
* path to commit
* @return { @code this }
* /
public CommitCommand setOnly ( String only ) {
checkCallable ( ) ;
if ( all )
throw new JGitInternalException ( MessageFormat . format (
JGitText . get ( ) . illegalCombinationOfArguments , "--only" ,
"--all" ) ) ;
String o = only . endsWith ( "/" ) ? only . substring ( 0 , only . length ( ) - 1 )
: only ;
// ignore duplicates
if ( ! this . only . contains ( o ) )
this . only . add ( o ) ;
return this ;
}
}
}