diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties index 01b3fba9c..718c57ad2 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties @@ -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 format. usage_CommitMessage=Use the given 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 diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java index 1c7e93634..c460bc3b4 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java @@ -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; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java index 2737422dc..ba66a30a2 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java @@ -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 paths = new ArrayList(); + @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(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java index 5fa2c8154..3d79357bd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTests.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -63,13 +64,90 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FileUtils; import org.junit.Test; +/** + * Testing the git commit and log commands + * + * Testing the 'commit only' option: + * + * I. A single file (f1.txt) specified as part of the --only/ -o option can have + * one of the following (14) states: + * + *
+ *        |                          | expected result
+ * ---------------------------------------------------------------------
+ *        | HEAD  DirCache  Worktree | HEAD  DirCache
+ * ---------------------------------------------------------------------
+ *  f1_1  |  -       -       c       |                => e: path unknown
+ *  f1_2  |  -       c       -       |                => no changes
+ *  f1_3  |  c       -       -       |  -       -
+ *  f1_4  |  -       c       c       |  c       c
+ *  f1_5  |  c       c       -       |  -       -
+ *  f1_6  |  c       -       c       |                => no changes
+ *  f1_7  |  c       c       c       |                => no changes
+ * ---------------------------------------------------------------------
+ *  f1_8  |  -       c       c'      |  c'      c'
+ *  f1_9  |  c       -       c'      |  c'      c'
+ * f1_10  |  c       c'      -       |  -       -
+ * f1_11  |  c       c       c'      |  c'      c'
+ * f1_12  |  c       c'      c       |                => no changes
+ * f1_13  |  c       c'      c'      |  c'      c'
+ * ---------------------------------------------------------------------
+ * f1_14  |  c       c'      c''     |  c''     c''
+ * 
+ * + * II. Scenarios that do not end with a successful commit (1, 2, 6, 7, 12) have + * to be tested with a second file (f2.txt) specified that would lead to a + * successful commit, if it were executed separately (e.g. scenario 14). + * + *
+ *              |                          | expected result
+ * ---------------------------------------------------------------------------
+ *              | HEAD  DirCache  Worktree | HEAD  DirCache
+ * ---------------------------------------------------------------------------
+ *  f1_1_f2_14  |  -       -       c       |                => e: path unknown
+ *  f1_2_f2_14  |  -       c       -       |  -       -
+ *  f1_6_f2_14  |  c       -       c       |  c       c
+ *  f1_7_f2_14  |  c       c       c       |  c       c
+ * ---------------------------------------------------------------------------
+ * f1_12_f2_14  |  c       c'      c       |  c       c
+ * 
+ * + * III. All scenarios (1-14, I-II) have to be tested with different repository + * states, to check that the --only/ -o option does not change existing content + * (HEAD and DirCache). The following states for a file (f3.txt) not specified + * shall be tested: + * + *
+ *       | HEAD  DirCache
+ * --------------------
+ *  *_a  |  -       -
+ *  *_b  |  -       c
+ *  *_c  |  c       c
+ *  *_d  |  c       -
+ * --------------------
+ *  *_e  |  c       c'
+ * 
+ **/ public class CommitAndLogCommandTests extends RepositoryTestCase { + private static final String F1 = "f1.txt"; + private static final String F2 = "f2.txt"; + private static final String F3 = "f3.txt"; + private static final String MSG = "commit"; + + private static int A = 0; + private static int B = 1; + private static int C = 2; + private static int D = 3; + private static int E = 4; + @Test public void testSomeCommits() throws NoHeadException, NoMessageException, UnmergedPathException, ConcurrentRefUpdateException, @@ -255,4 +333,1171 @@ public class CommitAndLogCommandTests extends RepositoryTestCase { } assertEquals(1, c); } + + @Test + public void testOnlyOption_f1_1_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_1(git); + executeAndCheck_f1_1(git, A); + } + + @Test + public void testOnlyOption_f1_1_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_1(git); + executeAndCheck_f1_1(git, B); + } + + @Test + public void testOnlyOption_f1_1_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_1(git); + executeAndCheck_f1_1(git, C); + } + + @Test + public void testOnlyOption_f1_1_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_1(git); + executeAndCheck_f1_1(git, D); + } + + @Test + public void testOnlyOption_f1_1_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_1(git); + executeAndCheck_f1_1(git, E); + } + + @Test + public void testOnlyOption_f1_1_f2_14_a() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, A); + prepare_f1_1(git); + executeAndCheck_f1_1_f2_f14(git, A); + } + + @Test + public void testOnlyOption_f1_1_f2_14_b() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, B); + prepare_f1_1(git); + executeAndCheck_f1_1_f2_f14(git, B); + } + + @Test + public void testOnlyOption_f1_1_f2_14_c() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, C); + prepare_f1_1(git); + executeAndCheck_f1_1_f2_f14(git, C); + } + + @Test + public void testOnlyOption_f1_1_f2_14_d() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, D); + prepare_f1_1(git); + executeAndCheck_f1_1_f2_f14(git, D); + } + + @Test + public void testOnlyOption_f1_1_f2_14_e() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, E); + prepare_f1_1(git); + executeAndCheck_f1_1_f2_f14(git, E); + } + + @Test + public void testOnlyOption_f1_2_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_2(git); + executeAndCheck_f1_2(git, A); + } + + @Test + public void testOnlyOption_f1_2_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_2(git); + executeAndCheck_f1_2(git, B); + } + + @Test + public void testOnlyOption_f1_2_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_2(git); + executeAndCheck_f1_2(git, C); + } + + @Test + public void testOnlyOption_f1_2_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_2(git); + executeAndCheck_f1_2(git, D); + } + + @Test + public void testOnlyOption_f1_2_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_2(git); + executeAndCheck_f1_2(git, E); + } + + @Test + public void testOnlyOption_f1_2_f2_14_a() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, A); + prepare_f1_2(git); + executeAndCheck_f1_2_f2_f14(git, A); + } + + @Test + public void testOnlyOption_f1_2_f2_14_b() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, B); + prepare_f1_2(git); + executeAndCheck_f1_2_f2_f14(git, B); + } + + @Test + public void testOnlyOption_f1_2_f2_14_c() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, C); + prepare_f1_2(git); + executeAndCheck_f1_2_f2_f14(git, C); + } + + @Test + public void testOnlyOption_f1_2_f2_14_d() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, D); + prepare_f1_2(git); + executeAndCheck_f1_2_f2_f14(git, D); + } + + @Test + public void testOnlyOption_f1_2_f2_14_e() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, E); + prepare_f1_2(git); + executeAndCheck_f1_2_f2_f14(git, E); + } + + @Test + public void testOnlyOption_f1_3_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_3(git); + executeAndCheck_f1_3(git, A); + } + + @Test + public void testOnlyOption_f1_3_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_3(git); + executeAndCheck_f1_3(git, B); + } + + @Test + public void testOnlyOption_f1_3_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_3(git); + executeAndCheck_f1_3(git, C); + } + + @Test + public void testOnlyOption_f1_3_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_3(git); + executeAndCheck_f1_3(git, D); + } + + @Test + public void testOnlyOption_f1_3_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_3(git); + executeAndCheck_f1_3(git, E); + } + + @Test + public void testOnlyOption_f1_4_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_4(git); + executeAndCheck_f1_4(git, A); + } + + @Test + public void testOnlyOption_f1_4_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_4(git); + executeAndCheck_f1_4(git, B); + } + + @Test + public void testOnlyOption_f1_4_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_4(git); + executeAndCheck_f1_4(git, C); + } + + @Test + public void testOnlyOption_f1_4_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_4(git); + executeAndCheck_f1_4(git, D); + } + + @Test + public void testOnlyOption_f1_4_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_4(git); + executeAndCheck_f1_4(git, E); + } + + @Test + public void testOnlyOption_f1_5_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_5(git); + executeAndCheck_f1_5(git, A); + } + + @Test + public void testOnlyOption_f1_5_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_5(git); + executeAndCheck_f1_5(git, B); + } + + @Test + public void testOnlyOption_f1_5_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_5(git); + executeAndCheck_f1_5(git, C); + } + + @Test + public void testOnlyOption_f1_5_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_5(git); + executeAndCheck_f1_5(git, D); + } + + @Test + public void testOnlyOption_f1_5_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_5(git); + executeAndCheck_f1_5(git, E); + } + + @Test + public void testOnlyOption_f1_6_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_6(git); + executeAndCheck_f1_6(git, A); + } + + @Test + public void testOnlyOption_f1_6_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_6(git); + executeAndCheck_f1_6(git, B); + } + + @Test + public void testOnlyOption_f1_6_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_6(git); + executeAndCheck_f1_6(git, C); + } + + @Test + public void testOnlyOption_f1_6_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_6(git); + executeAndCheck_f1_6(git, D); + } + + @Test + public void testOnlyOption_f1_6_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_6(git); + executeAndCheck_f1_6(git, E); + } + + @Test + public void testOnlyOption_f1_6_f2_14_a() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, A); + prepare_f1_6(git); + executeAndCheck_f1_6_f2_14(git, A); + } + + @Test + public void testOnlyOption_f1_6_f2_14_b() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, B); + prepare_f1_6(git); + executeAndCheck_f1_6_f2_14(git, B); + } + + @Test + public void testOnlyOption_f1_6_f2_14_c() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, C); + prepare_f1_6(git); + executeAndCheck_f1_6_f2_14(git, C); + } + + @Test + public void testOnlyOption_f1_6_f2_14_d() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, D); + prepare_f1_6(git); + executeAndCheck_f1_6_f2_14(git, D); + } + + @Test + public void testOnlyOption_f1_6_f2_14_e() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, E); + prepare_f1_6(git); + executeAndCheck_f1_6_f2_14(git, E); + } + + @Test + public void testOnlyOption_f1_7_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_7(git); + executeAndCheck_f1_7(git, A); + } + + @Test + public void testOnlyOption_f1_7_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_7(git); + executeAndCheck_f1_7(git, B); + } + + @Test + public void testOnlyOption_f1_7_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_7(git); + executeAndCheck_f1_7(git, C); + } + + @Test + public void testOnlyOption_f1_7_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_7(git); + executeAndCheck_f1_7(git, D); + } + + @Test + public void testOnlyOption_f1_7_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_7(git); + executeAndCheck_f1_7(git, E); + } + + @Test + public void testOnlyOption_f1_7_f2_14_a() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, A); + prepare_f1_7(git); + executeAndCheck_f1_7_f2_14(git, A); + } + + @Test + public void testOnlyOption_f1_7_f2_14_b() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, B); + prepare_f1_7(git); + executeAndCheck_f1_7_f2_14(git, B); + } + + @Test + public void testOnlyOption_f1_7_f2_14_c() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, C); + prepare_f1_7(git); + executeAndCheck_f1_7_f2_14(git, C); + } + + @Test + public void testOnlyOption_f1_7_f2_14_d() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, D); + prepare_f1_7(git); + executeAndCheck_f1_7_f2_14(git, D); + } + + @Test + public void testOnlyOption_f1_7_f2_14_e() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, E); + prepare_f1_7(git); + executeAndCheck_f1_7_f2_14(git, E); + } + + @Test + public void testOnlyOption_f1_8_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_8(git); + executeAndCheck_f1_8(git, A); + } + + @Test + public void testOnlyOption_f1_8_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_8(git); + executeAndCheck_f1_8(git, B); + } + + @Test + public void testOnlyOption_f1_8_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_8(git); + executeAndCheck_f1_8(git, C); + } + + @Test + public void testOnlyOption_f1_8_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_8(git); + executeAndCheck_f1_8(git, D); + } + + @Test + public void testOnlyOption_f1_8_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_8(git); + executeAndCheck_f1_8(git, E); + } + + @Test + public void testOnlyOption_f1_9_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_9(git); + executeAndCheck_f1_9(git, A); + } + + @Test + public void testOnlyOption_f1_9_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_9(git); + executeAndCheck_f1_9(git, B); + } + + @Test + public void testOnlyOption_f1_9_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_9(git); + executeAndCheck_f1_9(git, C); + } + + @Test + public void testOnlyOption_f1_9_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_9(git); + executeAndCheck_f1_9(git, D); + } + + @Test + public void testOnlyOption_f1_9_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_9(git); + executeAndCheck_f1_9(git, E); + } + + @Test + public void testOnlyOption_f1_10_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_10(git); + executeAndCheck_f1_10(git, A); + } + + @Test + public void testOnlyOption_f1_10_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_10(git); + executeAndCheck_f1_10(git, B); + } + + @Test + public void testOnlyOption_f1_10_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_10(git); + executeAndCheck_f1_10(git, C); + } + + @Test + public void testOnlyOption_f1_10_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_10(git); + executeAndCheck_f1_10(git, D); + } + + @Test + public void testOnlyOption_f1_10_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_10(git); + executeAndCheck_f1_10(git, E); + } + + @Test + public void testOnlyOption_f1_11_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_11(git); + executeAndCheck_f1_11(git, A); + } + + @Test + public void testOnlyOption_f1_11_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_11(git); + executeAndCheck_f1_11(git, B); + } + + @Test + public void testOnlyOption_f1_11_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_11(git); + executeAndCheck_f1_11(git, C); + } + + @Test + public void testOnlyOption_f1_11_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_11(git); + executeAndCheck_f1_11(git, D); + } + + @Test + public void testOnlyOption_f1_11_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_11(git); + executeAndCheck_f1_11(git, E); + } + + @Test + public void testOnlyOption_f1_12_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_12(git); + executeAndCheck_f1_12(git, A); + } + + @Test + public void testOnlyOption_f1_12_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_12(git); + executeAndCheck_f1_12(git, B); + } + + @Test + public void testOnlyOption_f1_12_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_12(git); + executeAndCheck_f1_12(git, C); + } + + @Test + public void testOnlyOption_f1_12_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_12(git); + executeAndCheck_f1_12(git, D); + } + + @Test + public void testOnlyOption_f1_12_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_12(git); + executeAndCheck_f1_12(git, E); + } + + @Test + public void testOnlyOption_f1_12_f2_14_a() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, A); + prepare_f1_12(git); + executeAndCheck_f1_12_f2_14(git, A); + } + + @Test + public void testOnlyOption_f1_12_f2_14_b() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, B); + prepare_f1_12(git); + executeAndCheck_f1_12_f2_14(git, B); + } + + @Test + public void testOnlyOption_f1_12_f2_14_c() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, C); + prepare_f1_12(git); + executeAndCheck_f1_12_f2_14(git, C); + } + + @Test + public void testOnlyOption_f1_12_f2_14_d() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, D); + prepare_f1_12(git); + executeAndCheck_f1_12_f2_14(git, D); + } + + @Test + public void testOnlyOption_f1_12_f2_14_e() throws Exception { + final Git git = new Git(db); + prepare_f3_f2_14(git, E); + prepare_f1_12(git); + executeAndCheck_f1_12_f2_14(git, E); + } + + @Test + public void testOnlyOption_f1_13_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_13(git); + executeAndCheck_f1_13(git, A); + } + + @Test + public void testOnlyOption_f1_13_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_13(git); + executeAndCheck_f1_13(git, B); + } + + @Test + public void testOnlyOption_f1_13_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_13(git); + executeAndCheck_f1_13(git, C); + } + + @Test + public void testOnlyOption_f1_13_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_13(git); + executeAndCheck_f1_13(git, D); + } + + @Test + public void testOnlyOption_f1_13_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_13(git); + executeAndCheck_f1_13(git, E); + } + + @Test + public void testOnlyOption_f1_14_a() throws Exception { + final Git git = new Git(db); + prepare_f3(git, A); + prepare_f1_14(git); + executeAndCheck_f1_14(git, A); + } + + @Test + public void testOnlyOption_f1_14_b() throws Exception { + final Git git = new Git(db); + prepare_f3(git, B); + prepare_f1_14(git); + executeAndCheck_f1_14(git, B); + } + + @Test + public void testOnlyOption_f1_14_c() throws Exception { + final Git git = new Git(db); + prepare_f3(git, C); + prepare_f1_14(git); + executeAndCheck_f1_14(git, C); + } + + @Test + public void testOnlyOption_f1_14_d() throws Exception { + final Git git = new Git(db); + prepare_f3(git, D); + prepare_f1_14(git); + executeAndCheck_f1_14(git, D); + } + + @Test + public void testOnlyOption_f1_14_e() throws Exception { + final Git git = new Git(db); + prepare_f3(git, E); + prepare_f1_14(git); + executeAndCheck_f1_14(git, E); + } + + @Test + public void testOnlyOptionWithDirectory() throws Exception { + final Git git = new Git(db); + + // write files + final File f1 = writeTrashFile("d1/d2/f1.txt", "c1"); + writeTrashFile("d1/d2/f2.txt", "c2"); + final File f3 = writeTrashFile("d1/f3.txt", "c3"); + writeTrashFile("d1/f4.txt", "c4"); + final File f5 = writeTrashFile("d3/d4/f5.txt", "c5"); + writeTrashFile("d3/d4/f6.txt", "c6"); + final File f7 = writeTrashFile("d3/f7.txt", "c7"); + writeTrashFile("d3/f8.txt", "c8"); + final File f9 = writeTrashFile("d5/f9.txt", "c9"); + writeTrashFile("d5/f10.txt", "c10"); + final File f11 = writeTrashFile("d6/f11.txt", "c11"); + writeTrashFile("d6/f12.txt", "c12"); + + // add files + git.add().addFilepattern(".").call(); + + // modify files, but do not stage changes + write(f1, "c1'"); + write(f3, "c3'"); + write(f5, "c5'"); + write(f7, "c7'"); + write(f9, "c9'"); + write(f11, "c11'"); + + // commit selected files only + git.commit().setOnly("d1").setOnly("d3/d4/").setOnly("d5") + .setOnly("d6/f11.txt").setMessage(MSG).call(); + + assertEquals("c1'", getHead(git, "d1/d2/f1.txt")); + assertEquals("c2", getHead(git, "d1/d2/f2.txt")); + assertEquals("c3'", getHead(git, "d1/f3.txt")); + assertEquals("c4", getHead(git, "d1/f4.txt")); + assertEquals("c5'", getHead(git, "d3/d4/f5.txt")); + assertEquals("c6", getHead(git, "d3/d4/f6.txt")); + assertEquals("", getHead(git, "d3/f7.txt")); + assertEquals("", getHead(git, "d3/f8.txt")); + assertEquals("c9'", getHead(git, "d5/f9.txt")); + assertEquals("c10", getHead(git, "d5/f10.txt")); + assertEquals("c11'", getHead(git, "d6/f11.txt")); + assertEquals("", getHead(git, "d6/f12.txt")); + assertEquals("[d1/d2/f1.txt, mode:100644, content:c1']" + + "[d1/d2/f2.txt, mode:100644, content:c2]" + + "[d1/f3.txt, mode:100644, content:c3']" + + "[d1/f4.txt, mode:100644, content:c4]" + + "[d3/d4/f5.txt, mode:100644, content:c5']" + + "[d3/d4/f6.txt, mode:100644, content:c6]" + + "[d3/f7.txt, mode:100644, content:c7]" + + "[d3/f8.txt, mode:100644, content:c8]" + + "[d5/f10.txt, mode:100644, content:c10]" + + "[d5/f9.txt, mode:100644, content:c9']" + + "[d6/f11.txt, mode:100644, content:c11']" + + "[d6/f12.txt, mode:100644, content:c12]", indexState(CONTENT)); + } + + @SuppressWarnings("unused") + private File prepare_f1_1(final Git git) throws IOException { + return writeTrashFile(F1, "c1"); + } + + private File prepare_f1_2(final Git git) throws Exception { + final File f1 = prepare_f1_4(git); + f1.delete(); + return f1; + } + + private File prepare_f1_3(final Git git) throws Exception { + final File f1 = prepare_f1_7(git); + git.rm().addFilepattern(F1).call(); + return f1; + } + + private File prepare_f1_4(final Git git) throws Exception { + final File f1 = prepare_f1_1(git); + git.add().addFilepattern(F1).call(); + return f1; + } + + private File prepare_f1_5(final Git git) throws Exception { + final File f1 = prepare_f1_7(git); + f1.delete(); + return f1; + } + + private File prepare_f1_6(final Git git) throws Exception { + final File f1 = prepare_f1_3(git); + write(f1, "c1"); + return f1; + } + + private File prepare_f1_7(final Git git) throws Exception { + final File f1 = prepare_f1_4(git); + git.commit().setOnly(F1).setMessage(MSG).call(); + return f1; + } + + private File prepare_f1_8(final Git git) throws Exception { + final File f1 = prepare_f1_4(git); + write(f1, "c1'"); + return f1; + } + + private File prepare_f1_9(final Git git) throws Exception { + final File f1 = prepare_f1_3(git); + write(f1, "c1'"); + return f1; + } + + private File prepare_f1_10(final Git git) throws Exception { + final File f1 = prepare_f1_9(git); + git.add().addFilepattern(F1).call(); + f1.delete(); + return f1; + } + + private File prepare_f1_11(final Git git) throws Exception { + final File f1 = prepare_f1_7(git); + write(f1, "c1'"); + return f1; + } + + private File prepare_f1_12(final Git git) throws Exception { + final File f1 = prepare_f1_13(git); + write(f1, "c1"); + return f1; + } + + private File prepare_f1_13(final Git git) throws Exception { + final File f1 = prepare_f1_11(git); + git.add().addFilepattern(F1).call(); + return f1; + } + + private File prepare_f1_14(final Git git) throws Exception { + final File f1 = prepare_f1_13(git); + write(f1, "c1''"); + return f1; + } + + @SuppressWarnings("null") + private void executeAndCheck_f1_1(final Git git, final int state) + throws Exception { + JGitInternalException exception = null; + try { + git.commit().setOnly(F1).setMessage(MSG).call(); + } catch (JGitInternalException e) { + exception = e; + } + assertNotNull(exception); + assertTrue(exception.getMessage().contains(F1)); + + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals(expected_f3_idx(state), indexState(CONTENT)); + } + + @SuppressWarnings("null") + private void executeAndCheck_f1_1_f2_f14(final Git git, final int state) + throws Exception { + JGitInternalException exception = null; + try { + git.commit().setOnly(F1).setOnly(F2).setMessage(MSG).call(); + } catch (JGitInternalException e) { + exception = e; + } + assertNotNull(exception); + assertTrue(exception.getMessage().contains(F1)); + + assertEquals("c2", getHead(git, F2)); + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f2.txt, mode:100644, content:c2']" + + expected_f3_idx(state), indexState(CONTENT)); + } + + @SuppressWarnings("null") + private void executeAndCheck_f1_2(final Git git, final int state) + throws Exception { + JGitInternalException exception = null; + try { + git.commit().setOnly(F1).setMessage(MSG).call(); + } catch (JGitInternalException e) { + exception = e; + } + assertNotNull(exception); + assertTrue(exception.getMessage().contains("No changes")); + + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f1.txt, mode:100644, content:c1]" + + expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_2_f2_f14(final Git git, final int state) + throws Exception { + git.commit().setOnly(F1).setOnly(F2).setMessage(MSG).call(); + + assertEquals("", getHead(git, F1)); + assertEquals("c2''", getHead(git, F2)); + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f2.txt, mode:100644, content:c2'']" + + expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_3(final Git git, final int state) + throws Exception { + git.commit().setOnly(F1).setMessage(MSG).call(); + + assertEquals("", getHead(git, F1)); + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals(expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_4(final Git git, final int state) + throws Exception { + git.commit().setOnly(F1).setMessage(MSG).call(); + + assertEquals("c1", getHead(git, F1)); + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f1.txt, mode:100644, content:c1]" + + expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_5(final Git git, final int state) + throws Exception { + executeAndCheck_f1_3(git, state); + } + + @SuppressWarnings("null") + private void executeAndCheck_f1_6(final Git git, final int state) + throws Exception { + JGitInternalException exception = null; + try { + git.commit().setOnly(F1).setMessage(MSG).call(); + } catch (JGitInternalException e) { + exception = e; + } + assertNotNull(exception); + assertTrue(exception.getMessage().contains("No changes")); + + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals(expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_6_f2_14(final Git git, final int state) + throws Exception { + git.commit().setOnly(F1).setOnly(F2).setMessage(MSG).call(); + + assertEquals("c1", getHead(git, F1)); + assertEquals("c2''", getHead(git, F2)); + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f1.txt, mode:100644, content:c1]" + + "[f2.txt, mode:100644, content:c2'']" + + expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_7(final Git git, final int state) + throws Exception { + executeAndCheck_f1_2(git, state); + } + + private void executeAndCheck_f1_7_f2_14(final Git git, final int state) + throws Exception { + executeAndCheck_f1_6_f2_14(git, state); + } + + private void executeAndCheck_f1_8(final Git git, final int state) + throws Exception { + git.commit().setOnly(F1).setMessage(MSG).call(); + + assertEquals("c1'", getHead(git, F1)); + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f1.txt, mode:100644, content:c1']" + + expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_9(final Git git, final int state) + throws Exception { + executeAndCheck_f1_8(git, state); + } + + private void executeAndCheck_f1_10(final Git git, final int state) + throws Exception { + executeAndCheck_f1_3(git, state); + } + + private void executeAndCheck_f1_11(final Git git, final int state) + throws Exception { + executeAndCheck_f1_8(git, state); + } + + @SuppressWarnings("null") + private void executeAndCheck_f1_12(final Git git, final int state) + throws Exception { + JGitInternalException exception = null; + try { + git.commit().setOnly(F1).setMessage(MSG).call(); + } catch (JGitInternalException e) { + exception = e; + } + assertNotNull(exception); + assertTrue(exception.getMessage().contains("No changes")); + + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f1.txt, mode:100644, content:c1']" + + expected_f3_idx(state), indexState(CONTENT)); + } + + private void executeAndCheck_f1_12_f2_14(final Git git, final int state) + throws Exception { + executeAndCheck_f1_6_f2_14(git, state); + } + + private void executeAndCheck_f1_13(final Git git, final int state) + throws Exception { + executeAndCheck_f1_8(git, state); + } + + private void executeAndCheck_f1_14(final Git git, final int state) + throws Exception { + git.commit().setOnly(F1).setMessage(MSG).call(); + + assertEquals("c1''", getHead(git, F1)); + assertEquals(expected_f3_head(state), getHead(git, F3)); + assertEquals("[f1.txt, mode:100644, content:c1'']" + + expected_f3_idx(state), indexState(CONTENT)); + } + + private void prepare_f3(final Git git, final int state) throws Exception { + prepare_f3_f2_14(git, state, false); + } + + private void prepare_f3_f2_14(final Git git, final int state) + throws Exception { + prepare_f3_f2_14(git, state, true); + } + + private void prepare_f3_f2_14(final Git git, final int state, + final boolean include_f2) throws Exception { + File f2 = null; + if (include_f2) { + f2 = writeTrashFile(F2, "c2"); + git.add().addFilepattern(F2).call(); + git.commit().setMessage(MSG).call(); + } + + if (state >= 1) { + writeTrashFile(F3, "c3"); + git.add().addFilepattern(F3).call(); + } + if (state >= 2) + git.commit().setMessage(MSG).call(); + if (state >= 3) + git.rm().addFilepattern(F3).call(); + if (state == 4) { + writeTrashFile(F3, "c3'"); + git.add().addFilepattern(F3).call(); + } + + if (include_f2) { + write(f2, "c2'"); + git.add().addFilepattern(F2).call(); + write(f2, "c2''"); + } + } + + private String expected_f3_head(final int state) { + switch (state) { + case 0: + case 1: + return ""; + case 2: + case 3: + case 4: + return "c3"; + } + return null; + } + + private String expected_f3_idx(final int state) { + switch (state) { + case 0: + case 3: + return ""; + case 1: + case 2: + return "[f3.txt, mode:100644, content:c3]"; + case 4: + return "[f3.txt, mode:100644, content:c3']"; + } + return null; + } + + private String getHead(final Git git, final String path) throws Exception { + try { + final Repository repo = git.getRepository(); + final ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); + final TreeWalk tw = TreeWalk.forPath(repo, path, + new RevWalk(repo).parseTree(headId)); + return new String(tw.getObjectReader().open(tw.getObjectId(0)) + .getBytes()); + } catch (Exception e) { + return ""; + } + } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index b2d9937da..f96d4261c 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 7265db8bc..9ef2cd93e 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 8ebee4967..8d9ce9818 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 { private boolean all; + private List only = new ArrayList(); + + private boolean[] onlyProcessed; + private boolean amend; /** @@ -170,6 +185,9 @@ public class CommitCommand extends GitCommand { // 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 { } } + 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 + * only, 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 only; -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 { /** * 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 { * @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; + } }