Browse Source

Add -o option to commit command

This change adds the --only/ -o option to the commit command.

Change-Id: I44352d56877f8204d985cb7a35a2e0faffb7d341
Signed-off-by: Philipp Thun <philipp.thun@sap.com>
tags/v0.12.1
Philipp Thun 13 years ago
parent
commit
a490afedba

+ 5
- 1
org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties View File

@@ -62,8 +62,9 @@ metaVar_base=base
metaVar_bucket=BUCKET
metaVar_command=command
metaVar_commandDetail=DETAIL
metaVar_commitOrTag=COMMIT|TAG
metaVar_commitish=commit-ish
metaVar_commitOrTag=COMMIT|TAG
metaVar_commitPaths=paths
metaVar_configFile=FILE
metaVar_connProp=conn.prop
metaVar_diffAlg=ALGORITHM
@@ -113,6 +114,7 @@ notFound=!! NOT FOUND !!
noteObjectTooLargeToPrint=Note object {0} too large to print
onlyOneMetaVarExpectedIn=Only one {0} expected in {1}.
pushTo=To {0}
pathsRequired=at least one path has to be specified when using --only
remoteMessage=remote: {0}
remoteRefObjectChangedIsNotExpectedOne=remote ref object changed - is not expected one {0}
remoteSideDoesNotSupportDeletingRefs=remote side does not support deleting refs
@@ -125,6 +127,8 @@ unsupportedOperation=Unsupported operation: {0}
usage_CommandLineClientForamazonsS3Service=Command line client for Amazon's S3 service
usage_CommitAuthor=Override the author name used in the commit. You can use the standard A U Thor <author@example.com> format.
usage_CommitMessage=Use the given <msg> as the commit message
usage_CommitOnly=commit specified paths only
usage_CommitPaths=see --only
usage_CreateABareRepository=Create a bare repository
usage_CreateATag=Create a tag
usage_CreateAnEmptyGitRepository=Create an empty git repository

+ 1
- 0
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java View File

@@ -133,6 +133,7 @@ public class CLIText extends TranslationBundle {
/***/ public String noteObjectTooLargeToPrint;
/***/ public String onlyOneMetaVarExpectedIn;
/***/ public String pushTo;
/***/ public String pathsRequired;
/***/ public String remoteMessage;
/***/ public String remoteRefObjectChangedIsNotExpectedOne;
/***/ public String remoteSideDoesNotSupportDeletingRefs;

+ 17
- 2
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java View File

@@ -37,6 +37,9 @@
*/
package org.eclipse.jgit.pgm;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
@@ -47,6 +50,7 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.RawParseUtils;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;

@Command(common = true, usage = "usage_recordChangesToRepository")
@@ -54,12 +58,18 @@ class Commit extends TextBuiltin {
// I don't support setting the committer, because also the native git
// command doesn't allow this.

@Option(name = "--author", metaVar="metaVar_author", usage = "usage_CommitAuthor")
@Option(name = "--author", metaVar = "metaVar_author", usage = "usage_CommitAuthor")
private String author;

@Option(name = "--message", aliases = { "-m" }, metaVar="metaVar_message", usage="usage_CommitMessage", required=true)
@Option(name = "--message", aliases = { "-m" }, metaVar = "metaVar_message", usage = "usage_CommitMessage", required = true)
private String message;

@Option(name = "--only", aliases = { "-o" }, usage = "usage_CommitOnly")
private boolean only;

@Argument(metaVar = "metaVar_commitPaths", usage = "usage_CommitPaths")
private List<String> paths = new ArrayList<String>();

@Override
protected void run() throws NoHeadException, NoMessageException,
ConcurrentRefUpdateException, JGitInternalException, Exception {
@@ -68,6 +78,11 @@ class Commit extends TextBuiltin {
commitCmd.setAuthor(RawParseUtils.parsePersonIdent(author));
if (message != null)
commitCmd.setMessage(message);
if (only && paths.isEmpty())
throw die(CLIText.get().pathsRequired);
if (!paths.isEmpty())
for (String p : paths)
commitCmd.setOnly(p);
Ref head = db.getRef(Constants.HEAD);
RevCommit commit = commitCmd.call();


+ 1245
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java
File diff suppressed because it is too large
View File


+ 2
- 0
org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties View File

@@ -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

+ 2
- 0
org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java View File

@@ -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;

+ 211
- 3
org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java View File

@@ -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;
}
}

Loading…
Cancel
Save