diff options
Diffstat (limited to 'org.eclipse.jgit')
3 files changed, 215 insertions, 3 deletions
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index b2d9937da0..f96d4261cc 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -153,6 +153,7 @@ duplicateRef=Duplicate ref: {0} duplicateRemoteRefUpdateIsIllegal=Duplicate remote ref update is illegal. Affected remote name: {0} duplicateStagesNotAllowed=Duplicate stages not allowed eitherGitDirOrWorkTreeRequired=One of setGitDir or setWorkTree must be called. +emptyCommit=No changes emptyPathNotPermitted=Empty path not permitted. encryptionError=Encryption error: {0} endOfFileInEscape=End of file in escape @@ -207,6 +208,7 @@ hunkBelongsToAnotherFile=Hunk belongs to another file hunkDisconnectedFromFile=Hunk disconnected from file hunkHeaderDoesNotMatchBodyLineCountOf=Hunk header {0} does not match body line count of {1} illegalArgumentNotA=Not {0} +illegalCombinationOfArguments=The combination of arguments {0} and {1} is not allowed illegalStateExists=exists {0} improperlyPaddedBase64Input=Improperly padded Base64 input. inMemoryBufferLimitExceeded=In-memory buffer limit exceeded diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 7265db8bc6..9ef2cd93ee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -213,6 +213,7 @@ public class JGitText extends TranslationBundle { /***/ public String duplicateRemoteRefUpdateIsIllegal; /***/ public String duplicateStagesNotAllowed; /***/ public String eitherGitDirOrWorkTreeRequired; + /***/ public String emptyCommit; /***/ public String emptyPathNotPermitted; /***/ public String encryptionError; /***/ public String endOfFileInEscape; @@ -267,6 +268,7 @@ public class JGitText extends TranslationBundle { /***/ public String hunkDisconnectedFromFile; /***/ public String hunkHeaderDoesNotMatchBodyLineCountOf; /***/ public String illegalArgumentNotA; + /***/ public String illegalCombinationOfArguments; /***/ public String illegalStateExists; /***/ public String improperlyPaddedBase64Input; /***/ public String inMemoryBufferLimitExceeded; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 8ebee49673..8d9ce98187 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -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; + } } |