|
|
@@ -43,7 +43,9 @@ |
|
|
|
package org.eclipse.jgit.api; |
|
|
|
|
|
|
|
import java.io.IOException; |
|
|
|
import java.io.InputStream; |
|
|
|
import java.text.MessageFormat; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.LinkedList; |
|
|
|
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.WrongRepositoryStateException; |
|
|
|
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.lib.CommitBuilder; |
|
|
|
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.revwalk.RevCommit; |
|
|
|
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 |
|
|
@@ -87,6 +98,10 @@ public class CommitCommand extends GitCommand<RevCommit> { |
|
|
|
|
|
|
|
private boolean all; |
|
|
|
|
|
|
|
private List<String> only = new ArrayList<String>(); |
|
|
|
|
|
|
|
private boolean[] onlyProcessed; |
|
|
|
|
|
|
|
private boolean amend; |
|
|
|
|
|
|
|
/** |
|
|
@@ -170,6 +185,9 @@ public class CommitCommand extends GitCommand<RevCommit> { |
|
|
|
// lock the index |
|
|
|
DirCache index = repo.lockDirCache(); |
|
|
|
try { |
|
|
|
if (!only.isEmpty()) |
|
|
|
index = createTemporaryIndex(headId, index); |
|
|
|
|
|
|
|
ObjectInserter odi = repo.newObjectInserter(); |
|
|
|
try { |
|
|
|
// 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 |
|
|
|
* 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 |
|
|
|
* been modified and deleted, but new files you not known by the repository |
|
|
|
* are not affected. This corresponds to the parameter -a on the command |
|
|
|
* line. |
|
|
|
* been modified and deleted, but new files not known by the repository are |
|
|
|
* not affected. This corresponds to the parameter -a on the command line. |
|
|
|
* |
|
|
|
* @param all |
|
|
|
* @return {@code this} |
|
|
|
* @throws JGitInternalException |
|
|
|
* in case of an illegal combination of arguments/ options |
|
|
|
*/ |
|
|
|
public CommitCommand setAll(boolean all) { |
|
|
|
checkCallable(); |
|
|
|
if (!only.isEmpty()) |
|
|
|
throw new JGitInternalException(MessageFormat.format( |
|
|
|
JGitText.get().illegalCombinationOfArguments, "--all", |
|
|
|
"--only")); |
|
|
|
this.all = all; |
|
|
|
return this; |
|
|
|
} |
|
|
@@ -407,8 +590,33 @@ public class CommitCommand extends GitCommand<RevCommit> { |
|
|
|
* @return {@code this} |
|
|
|
*/ |
|
|
|
public CommitCommand setAmend(boolean amend) { |
|
|
|
checkCallable(); |
|
|
|
this.amend = amend; |
|
|
|
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; |
|
|
|
} |
|
|
|
} |