Browse Source

Merge branch 'master' into stable-3.2

* master:
  Implement rebase.autostash
  CLI status should support --porcelain
  More helpful InvalidPathException messages (include reason)
  Fix IgnoreRule#isMatch returning wrong result due to missing reset
  Fix exception on conflicts with recursive merge
  Add pgm test for checkout of existing branch with checkout conflict
  Fix broken symbolic links on Cygwin.
  Do not allow non-ff-rebase if there are uncommitted changes
  Manage CheckoutConflictException in pgm
  Fix handling of file/folder conflicts during a checkout
  Mention null return in Javadoc of Config#getString
  Fix applying stash on other commit
  Use static factory methods instead of overloaded constructors
  Break up GCTest to run in parallel
  Modify T0004_PackReaderTest to use existing pack
  Move SampleDataRepositoryTestCase to org.eclipse.jgit.test
  Support running from JARs in JGitTestUtil
  Cache SimpleDateFormat in GitDateParser per locale
  Fix FIXUP error for blank lines in interactive rebase
  Fix parsing Rebase todo lines when commit message is missing
  Add close() method to API
  Update Jetty to 7.6.14.v20131031
  Document that path parameters should use '/' as separator
  Improve Javadoc for typeHint parameter
  Do not update the ref hot bit when checking isIndexLoaded
  Don't delete .idx file if .pack file can't be deleted

Change-Id: I02abfc09000d0fe9bdf4331c65bec7046f586179
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
tags/v3.2.0.201312181205-r
Matthias Sohn 10 years ago
parent
commit
162a5c4c89
89 changed files with 2740 additions and 1040 deletions
  1. 34
    1
      org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java
  2. 17
    17
      org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target
  3. 17
    17
      org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target
  4. 26
    0
      org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java
  5. 118
    0
      org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java
  6. 3
    0
      org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties
  7. 7
    0
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java
  8. 117
    8
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java
  9. 3
    0
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
  10. 8
    3
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java
  11. 28
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java
  12. 34
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java
  13. 340
    28
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
  14. 87
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
  15. 12
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java
  16. 0
    741
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java
  17. 162
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
  18. 119
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java
  19. 119
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
  20. 85
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java
  21. 104
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
  22. 180
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
  23. 120
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
  24. 146
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
  25. 78
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java
  26. 113
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
  27. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
  28. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java
  29. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java
  30. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java
  31. 11
    11
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java
  32. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java
  33. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java
  34. 25
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
  35. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
  36. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java
  37. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java
  38. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java
  39. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java
  40. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java
  41. 8
    5
      org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java
  42. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
  43. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
  44. 1
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
  45. 4
    2
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserBadlyFormattedTest.java
  46. 26
    13
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserTest.java
  47. 4
    0
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  48. 8
    4
      org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
  49. 2
    1
      org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
  50. 4
    3
      org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
  51. 1
    1
      org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
  52. 4
    4
      org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
  53. 20
    0
      org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
  54. 3
    3
      org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
  55. 2
    4
      org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
  56. 110
    28
      org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
  57. 76
    13
      org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java
  58. 5
    4
      org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
  59. 2
    2
      org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
  60. 2
    1
      org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
  61. 15
    1
      org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
  62. 3
    0
      org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
  63. 37
    7
      org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java
  64. 2
    1
      org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
  65. 1
    0
      org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
  66. 1
    0
      org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
  67. 1
    0
      org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
  68. 1
    0
      org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
  69. 1
    0
      org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
  70. 45
    23
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
  71. 5
    1
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java
  72. 1
    0
      org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
  73. 4
    0
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  74. 4
    0
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
  75. 1
    1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
  76. 42
    19
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
  77. 4
    4
      org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
  78. 2
    2
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
  79. 15
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
  80. 4
    3
      org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
  81. 9
    9
      org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
  82. 2
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
  83. 4
    3
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
  84. 9
    20
      org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java
  85. 5
    0
      org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
  86. 20
    1
      org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
  87. 77
    15
      org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
  88. 15
    0
      org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
  89. 1
    1
      pom.xml

+ 34
- 1
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java View File

@@ -46,8 +46,10 @@
package org.eclipse.jgit.junit;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Method;
@@ -124,13 +126,44 @@ public abstract class JGitTestUtil {
// loaded previously
return new File("tst", fileName);
}
if ("jar".equals(url.getProtocol())) {
try {
File tmp = File.createTempFile("tmp_", "_" + fileName);
copyTestResource(fileName, tmp);
return tmp;
} catch (IOException err) {
throw new RuntimeException("Cannot create temporary file", err);
}
}
try {
return new File(url.toURI());
} catch(URISyntaxException e) {
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage() + " " + url);
} catch (URISyntaxException e) {
return new File(url.getPath());
}
}

public static void copyTestResource(String name, File dest)
throws IOException {
URL url = cl().getResource(CLASSPATH_TO_RESOURCES + name);
if (url == null)
throw new FileNotFoundException(name);
InputStream in = url.openStream();
try {
FileOutputStream out = new FileOutputStream(dest);
try {
byte[] buf = new byte[4096];
for (int n; (n = in.read(buf)) > 0;)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}

private static ClassLoader cl() {
return JGitTestUtil.class.getClassLoader();
}

+ 17
- 17
org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target View File

@@ -11,23 +11,23 @@
<target name="jgit.target" sequenceNumber="55">
<locations>
<location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit">
<repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.11.v20130520/"/>
<unit id="org.eclipse.jetty.client" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.client.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.continuation" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.continuation.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.http" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.http.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.io" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.io.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.security" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.security.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.server" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.server.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.servlet" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.servlet.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.util" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.util.source" version="7.6.11.v20130520"/>
<repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.14.v20131031/"/>
<unit id="org.eclipse.jetty.client" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.client.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.continuation" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.continuation.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.http" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.http.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.io" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.io.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.security" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.security.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.server" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.server.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.servlet" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.servlet.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.util" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.util.source" version="7.6.14.v20131031"/>
</location>
<location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit">
<repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20130827064939/repository/"/>

+ 17
- 17
org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target View File

@@ -11,23 +11,23 @@
<target name="jgit.target" sequenceNumber="55">
<locations>
<location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit">
<repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.11.v20130520/"/>
<unit id="org.eclipse.jetty.client" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.client.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.continuation" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.continuation.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.http" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.http.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.io" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.io.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.security" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.security.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.server" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.server.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.servlet" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.servlet.source" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.util" version="7.6.11.v20130520"/>
<unit id="org.eclipse.jetty.util.source" version="7.6.11.v20130520"/>
<repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.14.v20131031/"/>
<unit id="org.eclipse.jetty.client" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.client.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.continuation" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.continuation.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.http" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.http.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.io" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.io.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.security" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.security.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.server" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.server.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.servlet" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.servlet.source" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.util" version="7.6.14.v20131031"/>
<unit id="org.eclipse.jetty.util.source" version="7.6.14.v20131031"/>
</location>
<location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit">
<repository location="http://download.eclipse.org/tools/orbit/downloads/drops/S20131024145017/repository/"/>

+ 26
- 0
org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java View File

@@ -42,8 +42,11 @@
*/
package org.eclipse.jgit.pgm;

import java.io.File;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.CLIRepositoryTestCase;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Assert;
import org.junit.Test;

@@ -107,6 +110,29 @@ public class CheckoutTest extends CLIRepositoryTestCase {
assertEquals("", execute("git checkout HEAD"));
}

@Test
public void testCheckoutExistingBranchWithConflict() throws Exception {
Git git = new Git(db);
writeTrashFile("a", "Hello world a");
git.add().addFilepattern(".").call();
git.commit().setMessage("commit file a").call();
git.branchCreate().setName("branch_1").call();
git.rm().addFilepattern("a").call();
FileUtils.mkdirs(new File(db.getWorkTree(), "a"));
writeTrashFile("a/b", "Hello world b");
git.add().addFilepattern("a/b").call();
git.commit().setMessage("commit folder a").call();
git.rm().addFilepattern("a").call();
writeTrashFile("a", "New Hello world a");
git.add().addFilepattern(".").call();

String[] execute = execute("git checkout branch_1");
Assert.assertEquals(
"error: Your local changes to the following files would be overwritten by checkout:",
execute[0]);
Assert.assertEquals("\ta", execute[1]);
}

static private void assertEquals(String expected, String[] actual) {
// if there is more than one line, ignore last one if empty
Assert.assertEquals(

+ 118
- 0
org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java View File

@@ -213,4 +213,122 @@ public class StatusTest extends CLIRepositoryTestCase {
"" //
}, execute("git status")); //
}

@Test
public void testStatusPorcelain() throws Exception {
Git git = new Git(db);
// Write all files
writeTrashFile("tracked", "tracked");
writeTrashFile("stagedNew", "stagedNew");
writeTrashFile("stagedModified", "stagedModified");
writeTrashFile("stagedDeleted", "stagedDeleted");
writeTrashFile("trackedModified", "trackedModified");
writeTrashFile("trackedDeleted", "trackedDeleted");
writeTrashFile("untracked", "untracked");
// Test untracked
assertArrayOfLinesEquals(new String[] { // git status output
"?? stagedDeleted", //
"?? stagedModified", //
"?? stagedNew", //
"?? tracked", //
"?? trackedDeleted", //
"?? trackedModified", //
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Add to index
git.add().addFilepattern("tracked").call();
git.add().addFilepattern("stagedModified").call();
git.add().addFilepattern("stagedDeleted").call();
git.add().addFilepattern("trackedModified").call();
git.add().addFilepattern("trackedDeleted").call();
// Test staged count
assertArrayOfLinesEquals(new String[] { // git status output
"A stagedDeleted", //
"A stagedModified", //
"A tracked", //
"A trackedDeleted", //
"A trackedModified", //
"?? stagedNew", //
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Commit
git.commit().setMessage("initial commit").call();
assertArrayOfLinesEquals(new String[] { // git status output
"?? stagedNew", //
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Make some changes and stage them
writeTrashFile("stagedModified", "stagedModified modified");
deleteTrashFile("stagedDeleted");
writeTrashFile("trackedModified", "trackedModified modified");
deleteTrashFile("trackedDeleted");
git.add().addFilepattern("stagedModified").call();
git.rm().addFilepattern("stagedDeleted").call();
git.add().addFilepattern("stagedNew").call();
// Test staged/not-staged status
assertArrayOfLinesEquals(new String[] { // git status output
"D stagedDeleted", //
"M stagedModified", //
"A stagedNew", //
" D trackedDeleted", //
" M trackedModified", //
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Create unmerged file
writeTrashFile("unmerged", "unmerged");
git.add().addFilepattern("unmerged").call();
// Commit pending changes
git.add().addFilepattern("trackedModified").call();
git.rm().addFilepattern("trackedDeleted").call();
git.commit().setMessage("commit before branching").call();
assertArrayOfLinesEquals(new String[] { // git status output
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Checkout new branch
git.checkout().setCreateBranch(true).setName("test").call();
// Test branch status
assertArrayOfLinesEquals(new String[] { // git status output
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Commit change and checkout master again
writeTrashFile("unmerged", "changed in test branch");
git.add().addFilepattern("unmerged").call();
RevCommit testBranch = git.commit()
.setMessage("changed unmerged in test branch").call();
assertArrayOfLinesEquals(new String[] { // git status output
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
git.checkout().setName("master").call();
// Change the same file and commit
writeTrashFile("unmerged", "changed in master branch");
git.add().addFilepattern("unmerged").call();
git.commit().setMessage("changed unmerged in master branch").call();
assertArrayOfLinesEquals(new String[] { // git status output
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Merge test branch into master
git.merge().include(testBranch.getId()).call();
// Test unmerged status
assertArrayOfLinesEquals(new String[] { // git status output
"UU unmerged", //
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
// Test detached head
String commitId = db.getRef(Constants.MASTER).getObjectId().name();
git.checkout().setName(commitId).call();
assertArrayOfLinesEquals(new String[] { // git status output
"UU unmerged", //
"?? untracked", //
"" //
}, execute("git status --porcelain")); //
}
}

+ 3
- 0
org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties View File

@@ -36,6 +36,8 @@ cantFindGitDirectory=error: can't find git directory
cantWrite=Can't write {0}
changesNotStagedForCommit=Changes not staged for commit:
changesToBeCommitted=Changes to be committed:
checkoutConflict=error: Your local changes to the following files would be overwritten by checkout:
checkoutConflictPathLine=\t{0}
commitLabel=commit
configFileNotFound=configuration file {0} not found
conflictingUsageOf_git_dir_andArguments=conflicting usage of --git-dir and arguments
@@ -280,6 +282,7 @@ usage_inputOutputFile=Input/output file
usage_listBothRemoteTrackingAndLocalBranches=list both remote-tracking and local branches
usage_listCreateOrDeleteBranches=List, create, or delete branches
usage_logAllPretty=format:%H %ct %P' output=log --all '--pretty=format:%H %ct %P' output
usage_machineReadableOutput=machine-readable output
usage_manageReflogInformation=Manage reflog information
usage_mergeFf=When the merge resolves as a fast-forward, only update the branch pointer, without creating a merge commit.
usage_mergeNoFf=Create a merge commit even when the merge resolves as a fast-forward.

+ 7
- 0
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2010, 2012 Chris Aniszczyk <caniszczyk@gmail.com>
* Copyright (C) 2013, Obeo
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -47,6 +48,7 @@ import java.text.MessageFormat;

import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.lib.Constants;
@@ -107,6 +109,11 @@ class Checkout extends TextBuiltin {
} catch (RefAlreadyExistsException e) {
throw die(MessageFormat.format(CLIText.get().branchAlreadyExists,
name));
} catch (CheckoutConflictException e) {
outw.println(CLIText.get().checkoutConflict);
for (String path : e.getConflictingPaths())
outw.println(MessageFormat.format(
CLIText.get().checkoutConflictPathLine, path));
}
}
}

+ 117
- 8
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java View File

@@ -50,6 +50,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.StatusCommand;
@@ -71,26 +72,134 @@ class Status extends TextBuiltin {

protected final String statusFileListFormatUnmerged = CLIText.get().statusFileListFormatUnmerged;

@Option(name = "--porcelain", usage = "usage_machineReadableOutput")
protected boolean porcelain;

@Option(name = "--", metaVar = "metaVar_path", multiValued = true)
protected List<String> filterPaths;

@Override
protected void run() throws Exception {
StatusCommand statusCommand = new Git(db).status();
if (filterPaths != null && filterPaths.size() > 0)
for (String path : filterPaths)
statusCommand.addPath(path);
org.eclipse.jgit.api.Status status = statusCommand.call();
printStatus(status);
}

private void printStatus(org.eclipse.jgit.api.Status status)
throws IOException {
if (porcelain)
printPorcelainStatus(status);
else
printLongStatus(status);
}

private void printPorcelainStatus(org.eclipse.jgit.api.Status status)
throws IOException {

Collection<String> added = status.getAdded();
Collection<String> changed = status.getChanged();
Collection<String> removed = status.getRemoved();
Collection<String> modified = status.getModified();
Collection<String> missing = status.getMissing();
Map<String, StageState> conflicting = status.getConflictingStageState();

// build a sorted list of all paths except untracked and ignored
TreeSet<String> sorted = new TreeSet<String>();
sorted.addAll(added);
sorted.addAll(changed);
sorted.addAll(removed);
sorted.addAll(modified);
sorted.addAll(missing);
sorted.addAll(conflicting.keySet());

// list each path
for (String path : sorted) {
char x = ' ';
char y = ' ';

if (added.contains(path))
x = 'A';
else if (changed.contains(path))
x = 'M';
else if (removed.contains(path))
x = 'D';

if (modified.contains(path))
y = 'M';
else if (missing.contains(path))
y = 'D';

if (conflicting.containsKey(path)) {
StageState stageState = conflicting.get(path);

switch (stageState) {
case BOTH_DELETED:
x = 'D';
y = 'D';
break;
case ADDED_BY_US:
x = 'A';
y = 'U';
break;
case DELETED_BY_THEM:
x = 'U';
y = 'D';
break;
case ADDED_BY_THEM:
x = 'U';
y = 'A';
break;
case DELETED_BY_US:
x = 'D';
y = 'U';
break;
case BOTH_ADDED:
x = 'A';
y = 'A';
break;
case BOTH_MODIFIED:
x = 'U';
y = 'U';
break;
default:
throw new IllegalArgumentException("Unknown StageState: " //$NON-NLS-1$
+ stageState);
}
}

printPorcelainLine(x, y, path);
}

// untracked are always at the end of the list
TreeSet<String> untracked = new TreeSet<String>(status.getUntracked());
for (String path : untracked)
printPorcelainLine('?', '?', path);
}

private void printPorcelainLine(char x, char y, String path)
throws IOException {
StringBuilder lineBuilder = new StringBuilder();
lineBuilder.append(x).append(y).append(' ').append(path);
outw.println(lineBuilder.toString());
}

private void printLongStatus(org.eclipse.jgit.api.Status status)
throws IOException {
// Print current branch name
final Ref head = db.getRef(Constants.HEAD);
boolean firstHeader = true;
if (head != null && head.isSymbolic()) {
String branch = Repository.shortenRefName(head.getLeaf().getName());
outw.println(CLIText.formatLine(
MessageFormat.format(CLIText.get().onBranch, branch)));
outw.println(CLIText.formatLine(MessageFormat.format(
CLIText.get().onBranch, branch)));
} else
outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch));

// List changes
StatusCommand statusCommand = new Git(db).status();
if (filterPaths != null && filterPaths.size() > 0)
for (String path : filterPaths)
statusCommand.addPath(path);
org.eclipse.jgit.api.Status status = statusCommand.call();
boolean firstHeader = true;

Collection<String> added = status.getAdded();
Collection<String> changed = status.getChanged();
Collection<String> removed = status.getRemoved();

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

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com>
* Copyright (C) 2013, Obeo
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -106,6 +107,8 @@ public class CLIText extends TranslationBundle {
/***/ public String cantWrite;
/***/ public String changesNotStagedForCommit;
/***/ public String changesToBeCommitted;
/***/ public String checkoutConflict;
/***/ public String checkoutConflictPathLine;
/***/ public String commitLabel;
/***/ public String conflictingUsageOf_git_dir_andArguments;
/***/ public String couldNotCreateBranch;

+ 8
- 3
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java View File

@@ -49,6 +49,7 @@ import java.util.Properties;

import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.util.GitDateParser;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Before;
import org.junit.Test;

@@ -68,7 +69,8 @@ public class GarbageCollectCommandTest extends RepositoryTestCase {

@Test
public void testGConeCommit() throws Exception {
Date expire = GitDateParser.parse("now", null);
Date expire = GitDateParser.parse("now", null, SystemReader
.getInstance().getLocale());
Properties res = git.gc().setExpire(expire).call();
assertTrue(res.size() == 7);
}
@@ -83,8 +85,11 @@ public class GarbageCollectCommandTest extends RepositoryTestCase {
writeTrashFile("b.txt", "a couple of words for gc to pack more 2");
writeTrashFile("c.txt", "a couple of words for gc to pack more 3");
git.commit().setAll(true).setMessage("commit3").call();
Properties res = git.gc().setExpire(GitDateParser.parse("now", null))
.call();
Properties res = git
.gc()
.setExpire(
GitDateParser.parse("now", null, SystemReader
.getInstance().getLocale())).call();
assertTrue(res.size() == 7);
}
}

+ 28
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java View File

@@ -45,6 +45,7 @@ package org.eclipse.jgit.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;

import org.eclipse.jgit.api.ListBranchCommand.ListMode;
@@ -53,6 +54,7 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -123,4 +125,30 @@ public class GitConstructionTest extends RepositoryTestCase {
// should not get here
}
}

@Test
/**
* Tests that a repository with packfiles can be deleted after calling
* Git.close(). On Windows the first try to delete the worktree will fail
* (because file handles on packfiles are still open) but the second
* attempt after a close will succeed.
*
* @throws IOException
* @throws JGitInternalException
* @throws GitAPIException
*/
public void testClose() throws IOException, JGitInternalException,
GitAPIException {
File workTree = db.getWorkTree();
Git git = Git.wrap(db);
git.gc().setExpire(null).call();
git.checkout().setName(git.getRepository().resolve("HEAD^").getName())
.call();
try {
FileUtils.delete(workTree, FileUtils.RECURSIVE);
} catch (IOException e) {
git.close();
FileUtils.delete(workTree, FileUtils.RECURSIVE);
}
}
}

+ 34
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java View File

@@ -56,8 +56,11 @@ import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
@@ -1532,6 +1535,37 @@ public class MergeCommandTest extends RepositoryTestCase {

assertEquals(MergeStatus.ABORTED, result.getMergeStatus());
}

@Test
public void testRecursiveMergeWithConflict() throws Exception {
TestRepository<Repository> db_t = new TestRepository<Repository>(db);
BranchBuilder master = db_t.branch("master");
RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n")
.message("m0").create();
RevCommit m1 = master.commit()
.add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1")
.create();
db_t.getRevWalk().parseCommit(m1);

BranchBuilder side = db_t.branch("side");
RevCommit s1 = side.commit().parent(m0)
.add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1")
.create();
RevCommit s2 = side.commit().parent(m1)
.add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n")
.message("s2(merge)").create();
master.commit().parent(s1)
.add("f", "1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n")
.message("m2(merge)").create();

Git git = Git.wrap(db);
git.checkout().setName("master").call();

MergeResult result = git.merge().setStrategy(MergeStrategy.RECURSIVE)
.include("side", s2).call();
assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus());
}

private static void setExecutable(Git git, String path, boolean executable) {
FS.DETECTED.setExecute(
new File(git.getRepository().getWorkTree(), path), executable);

+ 340
- 28
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java View File

@@ -68,9 +68,14 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
@@ -80,9 +85,10 @@ import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
@@ -1200,9 +1206,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
assertEquals(Status.CONFLICTS, result.getStatus());
assertEquals(1, result.getConflicts().size());
assertEquals("file2", result.getConflicts().get(0));
assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getUncommittedChanges().size());
assertEquals("file2", result.getUncommittedChanges().get(0));
}

@Test
@@ -1233,9 +1239,9 @@ public class RebaseCommandTest extends RepositoryTestCase {

RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
assertEquals(Status.CONFLICTS, result.getStatus());
assertEquals(1, result.getConflicts().size());
assertEquals("file2", result.getConflicts().get(0));
assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getUncommittedChanges().size());
assertEquals("file2", result.getUncommittedChanges().get(0));

checkFile(uncommittedFile, "uncommitted file2");
assertEquals(RepositoryState.SAFE, git.getRepository().getRepositoryState());
@@ -1268,9 +1274,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
assertEquals(Status.CONFLICTS, result.getStatus());
assertEquals(1, result.getConflicts().size());
assertEquals(FILE1, result.getConflicts().get(0));
assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getUncommittedChanges().size());
assertEquals(FILE1, result.getUncommittedChanges().get(0));
}

@Test
@@ -1302,9 +1308,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
assertEquals(Status.CONFLICTS, result.getStatus());
assertEquals(1, result.getConflicts().size());
assertEquals(FILE1, result.getConflicts().get(0));
assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getUncommittedChanges().size());
assertEquals(FILE1, result.getUncommittedChanges().get(0));
}

@Test
@@ -1333,7 +1339,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
writeTrashFile("file0", "unstaged modified file0");

// rebase
assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master")
assertEquals(Status.UNCOMMITTED_CHANGES,
git.rebase().setUpstream("refs/heads/master")
.call().getStatus());
}

@@ -1371,12 +1378,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
assertEquals(Status.FAILED, result.getStatus());
// staged file0 causes DIRTY_INDEX
assertEquals(1, result.getFailingPaths().size());
assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths()
.get("file0"));
assertEquals("unstaged modified file0", read(file0));
assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getUncommittedChanges().size());
// index shall be unchanged
assertEquals(indexState, indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
@@ -1412,7 +1415,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
writeTrashFile("file0", "unstaged modified file0");

// rebase
assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master")
assertEquals(Status.UNCOMMITTED_CHANGES,
git.rebase().setUpstream("refs/heads/master")
.call().getStatus());
}

@@ -1453,17 +1457,91 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
assertEquals(Status.FAILED, result.getStatus());
assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
// staged file0 causes DIRTY_INDEX
assertEquals(1, result.getFailingPaths().size());
assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths()
.get("file0"));
assertEquals(1, result.getUncommittedChanges().size());
assertEquals("unstaged modified file0", read(file0));
// index shall be unchanged
assertEquals(indexState, indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
}

@Test
public void testFastForwardRebaseWithModification() throws Exception {
// create file0 + file1, add and commit
writeTrashFile("file0", "file0");
writeTrashFile(FILE1, "file1");
git.add().addFilepattern("file0").addFilepattern(FILE1).call();
RevCommit commit = git.commit().setMessage("commit1").call();

// create topic branch
createBranch(commit, "refs/heads/topic");

// still on master / modify file1, add and commit
writeTrashFile(FILE1, "modified file1");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("commit2").call();

// checkout topic branch / modify file0 and add to index
checkoutBranch("refs/heads/topic");
writeTrashFile("file0", "modified file0 in index");
git.add().addFilepattern("file0").addFilepattern(FILE1).call();
// modify once more
writeTrashFile("file0", "modified file0");

// rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
assertEquals(Status.FAST_FORWARD, result.getStatus());
checkFile(new File(db.getWorkTree(), "file0"), "modified file0");
checkFile(new File(db.getWorkTree(), FILE1), "modified file1");
assertEquals("[file0, mode:100644, content:modified file0 in index]"
+ "[file1, mode:100644, content:modified file1]",
indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
}

@Test
public void testRebaseWithModificationShouldNotDeleteData()
throws Exception {
// create file0 + file1, add and commit
writeTrashFile("file0", "file0");
writeTrashFile(FILE1, "file1");
git.add().addFilepattern("file0").addFilepattern(FILE1).call();
RevCommit commit = git.commit().setMessage("commit1").call();

// create topic branch
createBranch(commit, "refs/heads/topic");

// still on master / modify file1, add and commit
writeTrashFile(FILE1, "modified file1");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("commit2").call();

// checkout topic branch / modify file1, add and commit
checkoutBranch("refs/heads/topic");
writeTrashFile(FILE1, "modified file1 on topic");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("commit3").call();

writeTrashFile("file0", "modified file0");

RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call();
// the following condition was true before commit 83b6ab233:
// jgit started the rebase and deleted the change on abort
// This test should verify that content was deleted
if (result.getStatus() == Status.STOPPED)
git.rebase().setOperation(Operation.ABORT).call();

checkFile(new File(db.getWorkTree(), "file0"), "modified file0");
checkFile(new File(db.getWorkTree(), FILE1),
"modified file1 on topic");
assertEquals("[file0, mode:100644, content:file0]"
+ "[file1, mode:100644, content:modified file1 on topic]",
indexState(CONTENT));
}

@Test
public void testRebaseWithUncommittedDelete() throws Exception {
// create file0 + file1, add and commit
@@ -1496,6 +1574,136 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
}

@Test
public void testRebaseWithAutoStash()
throws Exception {
// create file0, add and commit
db.getConfig().setBoolean(ConfigConstants.CONFIG_REBASE_SECTION, null,
ConfigConstants.CONFIG_KEY_AUTOSTASH, true);
writeTrashFile("file0", "file0");
git.add().addFilepattern("file0").call();
git.commit().setMessage("commit0").call();
// create file1, add and commit
writeTrashFile(FILE1, "file1");
git.add().addFilepattern(FILE1).call();
RevCommit commit = git.commit().setMessage("commit1").call();

// create topic branch and checkout / create file2, add and commit
createBranch(commit, "refs/heads/topic");
checkoutBranch("refs/heads/topic");
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("commit2").call();

// checkout master branch / modify file1, add and commit
checkoutBranch("refs/heads/master");
writeTrashFile(FILE1, "modified file1");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("commit3").call();

// checkout topic branch / modify file0
checkoutBranch("refs/heads/topic");
writeTrashFile("file0", "unstaged modified file0");

// rebase
assertEquals(Status.OK,
git.rebase().setUpstream("refs/heads/master").call()
.getStatus());
checkFile(new File(db.getWorkTree(), "file0"),
"unstaged modified file0");
checkFile(new File(db.getWorkTree(), FILE1), "modified file1");
checkFile(new File(db.getWorkTree(), "file2"), "file2");
assertEquals("[file0, mode:100644, content:file0]"
+ "[file1, mode:100644, content:modified file1]"
+ "[file2, mode:100644, content:file2]",
indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState());
}

@Test
public void testRebaseWithAutoStashConflictOnApply() throws Exception {
// create file0, add and commit
db.getConfig().setBoolean(ConfigConstants.CONFIG_REBASE_SECTION, null,
ConfigConstants.CONFIG_KEY_AUTOSTASH, true);
writeTrashFile("file0", "file0");
git.add().addFilepattern("file0").call();
git.commit().setMessage("commit0").call();
// create file1, add and commit
writeTrashFile(FILE1, "file1");
git.add().addFilepattern(FILE1).call();
RevCommit commit = git.commit().setMessage("commit1").call();

// create topic branch and checkout / create file2, add and commit
createBranch(commit, "refs/heads/topic");
checkoutBranch("refs/heads/topic");
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("commit2").call();

// checkout master branch / modify file1, add and commit
checkoutBranch("refs/heads/master");
writeTrashFile(FILE1, "modified file1");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("commit3").call();

// checkout topic branch / modify file0
checkoutBranch("refs/heads/topic");
writeTrashFile("file1", "unstaged modified file1");

// rebase
assertEquals(Status.STASH_APPLY_CONFLICTS,
git.rebase().setUpstream("refs/heads/master").call()
.getStatus());
checkFile(new File(db.getWorkTree(), "file0"), "file0");
checkFile(
new File(db.getWorkTree(), FILE1),
"<<<<<<< HEAD\nmodified file1\n=======\nunstaged modified file1\n>>>>>>> stash\n");
checkFile(new File(db.getWorkTree(), "file2"), "file2");
assertEquals(
"[file0, mode:100644, content:file0]"
+ "[file1, mode:100644, stage:1, content:file1]"
+ "[file1, mode:100644, stage:2, content:modified file1]"
+ "[file1, mode:100644, stage:3, content:unstaged modified file1]"
+ "[file2, mode:100644, content:file2]",
indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState());

List<DiffEntry> diffs = getStashedDiff();
assertEquals(1, diffs.size());
assertEquals(DiffEntry.ChangeType.MODIFY, diffs.get(0).getChangeType());
assertEquals("file1", diffs.get(0).getOldPath());
}

private List<DiffEntry> getStashedDiff() throws AmbiguousObjectException,
IncorrectObjectTypeException, IOException, MissingObjectException {
ObjectId stashId = db.resolve("stash@{0}");
RevWalk revWalk = new RevWalk(db);
RevCommit stashCommit = revWalk.parseCommit(stashId);
List<DiffEntry> diffs = diffWorkingAgainstHead(stashCommit, revWalk);
return diffs;
}

private TreeWalk createTreeWalk() {
TreeWalk walk = new TreeWalk(db);
walk.setRecursive(true);
walk.setFilter(TreeFilter.ANY_DIFF);
return walk;
}

private List<DiffEntry> diffWorkingAgainstHead(final RevCommit commit,
RevWalk revWalk)
throws IOException {
TreeWalk walk = createTreeWalk();
RevCommit parentCommit = revWalk.parseCommit(commit.getParent(0));
try {
walk.addTree(parentCommit.getTree());
walk.addTree(commit.getTree());
return DiffEntry.scan(walk);
} finally {
walk.release();
}
}

private int countPicks() throws IOException {
int count = 0;
File todoFile = getTodoFile();
@@ -1595,9 +1803,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// and attempt to rebase
RebaseResult rebaseResult = git.rebase()
.setUpstream("refs/heads/master").call();
assertEquals(Status.CONFLICTS, rebaseResult.getStatus());
assertEquals(1, rebaseResult.getConflicts().size());
assertEquals(FILE1, rebaseResult.getConflicts().get(0));
assertEquals(Status.UNCOMMITTED_CHANGES, rebaseResult.getStatus());
assertEquals(1, rebaseResult.getUncommittedChanges().size());
assertEquals(FILE1, rebaseResult.getUncommittedChanges().get(0));

checkFile(theFile, "dirty the file");

@@ -1641,6 +1849,19 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertEquals("2222222", steps.get(1).getCommit().name());
}

@Test
public void testRebaseShouldBeAbleToHandleLinesWithoutCommitMessageInRebaseTodoFile()
throws IOException {
String todo = "pick 1111111 \n" + "pick 2222222 Commit 2\n"
+ "# Comment line at end\n";
write(getTodoFile(), todo);

List<RebaseTodoLine> steps = db.readRebaseTodo(GIT_REBASE_TODO, false);
assertEquals(2, steps.size());
assertEquals("1111111", steps.get(0).getCommit().name());
assertEquals("2222222", steps.get(1).getCommit().name());
}

@Test
public void testRebaseShouldNotFailIfUserAddCommentLinesInPrepareSteps()
throws Exception {
@@ -2237,6 +2458,44 @@ public class RebaseCommandTest extends RepositoryTestCase {
head1Commit.getFullMessage());
}

@Test
public void testRebaseInteractiveFixupWithBlankLines() throws Exception {
// create file1 on master
writeTrashFile(FILE1, FILE1);
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1\nnew line").call();
assertTrue(new File(db.getWorkTree(), FILE1).exists());

// create file2 on master
writeTrashFile("file2", "file2");
git.add().addFilepattern("file2").call();
git.commit().setMessage("Add file2").call();
assertTrue(new File(db.getWorkTree(), "file2").exists());

// update FILE1 on master
writeTrashFile(FILE1, "blah");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("updated file1 on master\n\nsome text").call();

git.rebase().setUpstream("HEAD~2")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(1).setAction(Action.FIXUP);
}

public String modifyCommitMessage(String commit) {
fail("No callback to modify commit message expected for single fixup");
return commit;
}
}).call();

RevWalk walk = new RevWalk(db);
ObjectId headId = db.resolve(Constants.HEAD);
RevCommit headCommit = walk.parseCommit(headId);
assertEquals("Add file2",
headCommit.getFullMessage());
}

@Test(expected = InvalidRebaseStepException.class)
public void testRebaseInteractiveFixupFirstCommitShouldFail()
@@ -2534,6 +2793,59 @@ public class RebaseCommandTest extends RepositoryTestCase {

}

@Test
public void testInteractiveRebaseWithModificationShouldNotDeleteDataOnAbort()
throws Exception {
// create file0 + file1, add and commit
writeTrashFile("file0", "file0");
writeTrashFile(FILE1, "file1");
git.add().addFilepattern("file0").addFilepattern(FILE1).call();
git.commit().setMessage("commit1").call();

// modify file1, add and commit
writeTrashFile(FILE1, "modified file1");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("commit2").call();

// modify file1, add and commit
writeTrashFile(FILE1, "modified file1 a second time");
git.add().addFilepattern(FILE1).call();
git.commit().setMessage("commit3").call();

// modify file0, but do not commit
writeTrashFile("file0", "modified file0 in index");
git.add().addFilepattern("file0").addFilepattern(FILE1).call();
// do not commit
writeTrashFile("file0", "modified file0");

// start rebase
RebaseResult result = git.rebase().setUpstream("HEAD~2")
.runInteractively(new InteractiveHandler() {

public void prepareSteps(List<RebaseTodoLine> steps) {
steps.get(0).setAction(Action.EDIT);
steps.get(1).setAction(Action.PICK);
}

public String modifyCommitMessage(String commit) {
return commit;
}
}).call();
// the following condition was true before commit 83b6ab233:
// jgit started the rebase and deleted the change on abort
// This test should verify that content was deleted
if (result.getStatus() == Status.EDIT)
git.rebase().setOperation(Operation.ABORT).call();

checkFile(new File(db.getWorkTree(), "file0"), "modified file0");
checkFile(new File(db.getWorkTree(), "file1"),
"modified file1 a second time");
assertEquals("[file0, mode:100644, content:modified file0 in index]"
+ "[file1, mode:100644, content:modified file1 a second time]",
indexState(CONTENT));

}

private File getTodoFile() {
File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO);
return todoFile;

+ 87
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java View File

@@ -408,6 +408,93 @@ public class StashApplyCommandTest extends RepositoryTestCase {
read(PATH));
}

@Test
public void stashedApplyOnOtherBranch() throws Exception {
writeTrashFile(PATH, "content\nmore content\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("more content").call();
String path2 = "file2.txt";
File file2 = writeTrashFile(path2, "content\nmore content\n");
git.add().addFilepattern(PATH).call();
git.add().addFilepattern(path2).call();
git.commit().setMessage("even content").call();

String otherBranch = "otherBranch";
git.branchCreate().setName(otherBranch).call();

writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call();

git.checkout().setName(otherBranch).call();

writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call();

writeTrashFile(path2, "content\nstashed change\nmore content\n");

RevCommit stashed = git.stashCreate().call();

assertNotNull(stashed);
assertEquals("content\nmore content\n", read(file2));
assertEquals("otherBranch content",
read(committedFile));
assertTrue(git.status().call().isClean());

git.checkout().setName("master").call();
git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals("master content",
read(committedFile));
}

@Test
public void stashedApplyOnOtherBranchWithStagedChange() throws Exception {
writeTrashFile(PATH, "content\nmore content\n");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("more content").call();
String path2 = "file2.txt";
File file2 = writeTrashFile(path2, "content\nmore content\n");
git.add().addFilepattern(PATH).call();
git.add().addFilepattern(path2).call();
git.commit().setMessage("even content").call();

String otherBranch = "otherBranch";
git.branchCreate().setName(otherBranch).call();

writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call();

git.checkout().setName(otherBranch).call();

writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call();

writeTrashFile(path2,
"content\nstashed change in index\nmore content\n");
git.add().addFilepattern(path2).call();
writeTrashFile(path2, "content\nstashed change\nmore content\n");

RevCommit stashed = git.stashCreate().call();

assertNotNull(stashed);
assertEquals("content\nmore content\n", read(file2));
assertEquals("otherBranch content", read(committedFile));
assertTrue(git.status().call().isClean());

git.checkout().setName("master").call();
git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals(
"[file.txt, mode:100644, content:master content]"
+ "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]",
indexState(CONTENT));
assertEquals("master content", read(committedFile));
}

@Test
public void workingDirectoryContentMerge() throws Exception {
writeTrashFile(PATH, "content\nmore content\n");

+ 12
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java View File

@@ -340,6 +340,18 @@ public class IgnoreMatcherTest {
assertEquals(r.getPattern(), "/patter?");
}

@Test
public void testResetState() {
String pattern = "/build/*";
String target = "/build";
// Don't use the assert methods of this class, as we want to test
// whether the state in IgnoreRule is reset properly
IgnoreRule r = new IgnoreRule(pattern);
// Result should be the same for the same inputs
assertFalse(r.isMatch(target, true));
assertFalse(r.isMatch(target, true));
}

/**
* Check for a match. If target ends with "/", match will assume that the
* target is meant to be a directory.

+ 0
- 741
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java View File

@@ -1,741 +0,0 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.internal.storage.file;

import static java.lang.Integer.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.EmptyProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.Merger;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.util.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class GCTest extends LocalDiskRepositoryTestCase {
private TestRepository<FileRepository> tr;

private FileRepository repo;

private GC gc;

private RepoStatistics stats;

@Before
public void setUp() throws Exception {
super.setUp();
repo = createWorkRepository();
tr = new TestRepository<FileRepository>((repo));
gc = new GC(repo);
}

@After
public void tearDown() throws Exception {
super.tearDown();
}

// GC.packRefs tests

@Test
public void packRefs_looseRefPacked() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);

gc.packRefs();
assertSame(repo.getRef("t").getStorage(), Storage.PACKED);
}

@Test
public void concurrentPackRefs_onlyOneWritesPackedRefs() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);

final CyclicBarrier syncPoint = new CyclicBarrier(2);

Callable<Integer> packRefs = new Callable<Integer>() {

/** @return 0 for success, 1 in case of error when writing pack */
public Integer call() throws Exception {
syncPoint.await();
try {
gc.packRefs();
return valueOf(0);
} catch (IOException e) {
return valueOf(1);
}
}
};
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
Future<Integer> p1 = pool.submit(packRefs);
Future<Integer> p2 = pool.submit(packRefs);
assertEquals(1, p1.get().intValue() + p2.get().intValue());
} finally {
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
}

@Test
public void packRefsWhileRefLocked_refNotPackedNoError()
throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t1", a);
tr.lightweightTag("t2", a);
LockFile refLock = new LockFile(new File(repo.getDirectory(),
"refs/tags/t1"), repo.getFS());
try {
refLock.lock();
gc.packRefs();
} finally {
refLock.unlock();
}

assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE);
assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED);
}

@Test
public void packRefsWhileRefUpdated_refUpdateSucceeds()
throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
final RevBlob b = tr.blob("b");

final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2);
final CyclicBarrier packRefsDone = new CyclicBarrier(2);
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
Future<Result> result = pool.submit(new Callable<Result>() {

public Result call() throws Exception {
RefUpdate update = new RefDirectoryUpdate(
(RefDirectory) repo.getRefDatabase(),
repo.getRef("refs/tags/t")) {
@Override
public boolean isForceUpdate() {
try {
refUpdateLockedRef.await();
packRefsDone.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
return super.isForceUpdate();
}
};
update.setForceUpdate(true);
update.setNewObjectId(b);
return update.update();
}
});

pool.submit(new Callable<Void>() {
public Void call() throws Exception {
refUpdateLockedRef.await();
gc.packRefs();
packRefsDone.await();
return null;
}
});

assertSame(result.get(), Result.FORCED);

} finally {
pool.shutdownNow();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}

assertEquals(repo.getRef("refs/tags/t").getObjectId(), b);
}

// GC.repack tests

@Test
public void repackEmptyRepo_noPackCreated() throws IOException {
gc.repack();
assertEquals(0, repo.getObjectDatabase().getPacks().size());
}

@Test
public void concurrentRepack() throws Exception {
final CyclicBarrier syncPoint = new CyclicBarrier(2);

class DoRepack extends EmptyProgressMonitor implements
Callable<Integer> {

public void beginTask(String title, int totalWork) {
if (title.equals(JGitText.get().writingObjects)) {
try {
syncPoint.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException ignored) {
//
}
}
}

/** @return 0 for success, 1 in case of error when writing pack */
public Integer call() throws Exception {
try {
gc.setProgressMonitor(this);
gc.repack();
return valueOf(0);
} catch (IOException e) {
// leave the syncPoint in broken state so any awaiting
// threads and any threads that call await in the future get
// the BrokenBarrierException
Thread.currentThread().interrupt();
try {
syncPoint.await();
} catch (InterruptedException ignored) {
//
}
return valueOf(1);
}
}
}

RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);

ExecutorService pool = Executors.newFixedThreadPool(2);
try {
DoRepack repack1 = new DoRepack();
DoRepack repack2 = new DoRepack();
Future<Integer> result1 = pool.submit(repack1);
Future<Integer> result2 = pool.submit(repack2);
assertEquals(0, result1.get().intValue() + result2.get().intValue());
} finally {
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
}

// GC.prune tests

@Test
public void nonReferencedNonExpiredObject_notPruned() throws Exception {
RevBlob a = tr.blob("a");
gc.setExpire(new Date(lastModified(a)));
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(a));
}

@Test
public void nonReferencedExpiredObject_pruned() throws Exception {
RevBlob a = tr.blob("a");
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertFalse(repo.hasObject(a));
}

@Test
public void nonReferencedExpiredObjectTree_pruned() throws Exception {
RevBlob a = tr.blob("a");
RevTree t = tr.tree(tr.file("a", a));
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertFalse(repo.hasObject(t));
assertFalse(repo.hasObject(a));
}

@Test
public void nonReferencedObjects_onlyExpiredPruned() throws Exception {
RevBlob a = tr.blob("a");
gc.setExpire(new Date(lastModified(a) + 1));

fsTick();
RevBlob b = tr.blob("b");

gc.prune(Collections.<ObjectId> emptySet());
assertFalse(repo.hasObject(a));
assertTrue(repo.hasObject(b));
}

@Test
public void lightweightTag_objectNotPruned() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(a));
}

@Test
public void annotatedTag_objectNotPruned() throws Exception {
RevBlob a = tr.blob("a");
RevTag t = tr.tag("t", a); // this doesn't create the refs/tags/t ref
tr.lightweightTag("t", t);

gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(t));
assertTrue(repo.hasObject(a));
}

@Test
public void branch_historyNotPruned() throws Exception {
RevCommit tip = commitChain(10);
tr.branch("b").update(tip);
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
do {
assertTrue(repo.hasObject(tip));
tr.parseBody(tip);
RevTree t = tip.getTree();
assertTrue(repo.hasObject(t));
assertTrue(repo.hasObject(tr.get(t, "a")));
tip = tip.getParentCount() > 0 ? tip.getParent(0) : null;
} while (tip != null);
}

@Test
public void deleteBranch_historyPruned() throws Exception {
RevCommit tip = commitChain(10);
tr.branch("b").update(tip);
RefUpdate update = repo.updateRef("refs/heads/b");
update.setForceUpdate(true);
update.delete();
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(gc.getStatistics().numberOfLooseObjects == 0);
}

@Test
public void deleteMergedBranch_historyNotPruned() throws Exception {
RevCommit parent = tr.commit().create();
RevCommit b1Tip = tr.branch("b1").commit().parent(parent).add("x", "x")
.create();
RevCommit b2Tip = tr.branch("b2").commit().parent(parent).add("y", "y")
.create();

// merge b1Tip and b2Tip and update refs/heads/b1 to the merge commit
Merger merger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
merger.merge(b1Tip, b2Tip);
CommitBuilder cb = tr.commit();
cb.parent(b1Tip).parent(b2Tip);
cb.setTopLevelTree(merger.getResultTreeId());
RevCommit mergeCommit = cb.create();
RefUpdate u = repo.updateRef("refs/heads/b1");
u.setNewObjectId(mergeCommit);
u.update();

RefUpdate update = repo.updateRef("refs/heads/b2");
update.setForceUpdate(true);
update.delete();

gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(b2Tip));
}

@Test
public void testPackAllObjectsInOnePack() throws Exception {
tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
.create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);

// Do the gc again and check that it hasn't changed anything
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testPackRepoWithCorruptReflog() throws Exception {
// create a reflog entry "0000... 0000... foobar" by doing an initial
// refupdate for HEAD which points to a non-existing ref. The
// All-Projects repo of gerrit instances had such entries
RefUpdate ru = repo.updateRef(Constants.HEAD);
ru.link("refs/to/garbage");
tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
.create();
// make sure HEAD exists
Git.wrap(repo).checkout().setName("refs/heads/master").call();
gc.gc();
}

@Test
public void testKeepFiles() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
assertEquals(0, stats.numberOfPackFiles);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);

Iterator<PackFile> packIt = repo.getObjectDatabase().getPacks()
.iterator();
PackFile singlePack = packIt.next();
assertFalse(packIt.hasNext());
File keepFile = new File(singlePack.getPackFile().getPath() + ".keep");
assertFalse(keepFile.exists());
assertTrue(keepFile.createNewFile());
bb.commit().add("A", "A2").add("B", "B2").create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(2, stats.numberOfPackFiles);

// check that no object is packed twice
Iterator<PackFile> packs = repo.getObjectDatabase().getPacks()
.iterator();
PackIndex ind1 = packs.next().getIndex();
assertEquals(4, ind1.getObjectCount());
PackIndex ind2 = packs.next().getIndex();
assertEquals(4, ind2.getObjectCount());
for (MutableEntry e: ind1)
if (ind2.hasObject(e.toObjectId()))
assertFalse(
"the following object is in both packfiles: "
+ e.toObjectId(),
ind2.hasObject(e.toObjectId()));
}

@Test
public void testPackRepoWithNoRefs() throws Exception {
tr.commit().add("A", "A").add("B", "B").create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
assertEquals(0, stats.numberOfPackFiles);
}

@Test
public void testPack2Commits() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testPackCommitsAndLooseOne() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(2, stats.numberOfPackFiles);
}

@Test
public void testNotPackTwice() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().message("M").add("M", "M").create();
bb.commit().message("B").add("B", "Q").create();
bb.commit().message("A").add("A", "A").create();
RevCommit second = tr.commit().parent(first).message("R").add("R", "Q")
.create();
tr.update("refs/tags/t1", second);

Collection<PackFile> oldPacks = tr.getRepository().getObjectDatabase()
.getPacks();
assertEquals(0, oldPacks.size());
stats = gc.getStatistics();
assertEquals(11, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);

gc.setExpireAgeMillis(0);
fsTick();
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);

Iterator<PackFile> pIt = repo.getObjectDatabase().getPacks().iterator();
long c = pIt.next().getObjectCount();
if (c == 9)
assertEquals(2, pIt.next().getObjectCount());
else {
assertEquals(2, c);
assertEquals(9, pIt.next().getObjectCount());
}
}

@Test
public void testPackCommitsAndLooseOneNoReflog() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);

FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
FileUtils.delete(
new File(repo.getDirectory(), "logs/refs/heads/master"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
gc.gc();

stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testPackCommitsAndLooseOneWithPruneNow() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.setExpireAgeMillis(0);
fsTick();
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(2, stats.numberOfPackFiles);
}

@Test
public void testPackCommitsAndLooseOneWithPruneNowNoReflog()
throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);

FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
FileUtils.delete(
new File(repo.getDirectory(), "logs/refs/heads/master"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
gc.setExpireAgeMillis(0);
gc.gc();

stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testIndexSavesObjects() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
bb.commit().add("A", "A3"); // this new content in index should survive
stats = gc.getStatistics();
assertEquals(9, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(1, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testIndexSavesObjectsWithPruneNow() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
bb.commit().add("A", "A3"); // this new content in index should survive
stats = gc.getStatistics();
assertEquals(9, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.setExpireAgeMillis(0);
fsTick();
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testPruneNone() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master")
.delete();
stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
tr.blob("x");
stats = gc.getStatistics();
assertEquals(9, stats.numberOfLooseObjects);
gc.prune(Collections.<ObjectId> emptySet());
stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
}

/**
* Create a chain of commits of given depth.
* <p>
* Each commit contains one file named "a" containing the index of the
* commit in the chain as its content. The created commit chain is
* referenced from any ref.
* <p>
* A chain of depth = N will create 3*N objects in Gits object database. For
* each depth level three objects are created: the commit object, the
* top-level tree object and a blob for the content of the file "a".
*
* @param depth
* the depth of the commit chain.
* @return the commit that is the tip of the commit chain
* @throws Exception
*/
private RevCommit commitChain(int depth) throws Exception {
if (depth <= 0)
throw new IllegalArgumentException("Chain depth must be > 0");
CommitBuilder cb = tr.commit();
RevCommit tip;
do {
--depth;
tip = cb.add("a", "" + depth).message("" + depth).create();
cb = cb.child();
} while (depth > 0);
return tip;
}

private long lastModified(AnyObjectId objectId) {
return repo.getObjectDatabase().fileFor(objectId).lastModified();
}

private static void fsTick() throws InterruptedException, IOException {
RepositoryTestCase.fsTick(null);
}
}

+ 162
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java View File

@@ -0,0 +1,162 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;

public class GcBasicPackingTest extends GcTestCase {
@Test
public void repackEmptyRepo_noPackCreated() throws IOException {
gc.repack();
assertEquals(0, repo.getObjectDatabase().getPacks().size());
}

@Test
public void testPackRepoWithNoRefs() throws Exception {
tr.commit().add("A", "A").add("B", "B").create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
assertEquals(0, stats.numberOfPackFiles);
}

@Test
public void testPack2Commits() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testPackAllObjectsInOnePack() throws Exception {
tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
.create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);

// Do the gc again and check that it hasn't changed anything
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testPackCommitsAndLooseOne() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(2, stats.numberOfPackFiles);
}

@Test
public void testNotPackTwice() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().message("M").add("M", "M").create();
bb.commit().message("B").add("B", "Q").create();
bb.commit().message("A").add("A", "A").create();
RevCommit second = tr.commit().parent(first).message("R").add("R", "Q")
.create();
tr.update("refs/tags/t1", second);

Collection<PackFile> oldPacks = tr.getRepository().getObjectDatabase()
.getPacks();
assertEquals(0, oldPacks.size());
stats = gc.getStatistics();
assertEquals(11, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);

gc.setExpireAgeMillis(0);
fsTick();
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);

Iterator<PackFile> pIt = repo.getObjectDatabase().getPacks().iterator();
long c = pIt.next().getObjectCount();
if (c == 9)
assertEquals(2, pIt.next().getObjectCount());
else {
assertEquals(2, c);
assertEquals(9, pIt.next().getObjectCount());
}
}
}

+ 119
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static org.junit.Assert.assertTrue;

import java.util.Collections;

import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.Merger;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.junit.Test;

public class GcBranchPrunedTest extends GcTestCase {

@Test
public void branch_historyNotPruned() throws Exception {
RevCommit tip = commitChain(10);
tr.branch("b").update(tip);
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
do {
assertTrue(repo.hasObject(tip));
tr.parseBody(tip);
RevTree t = tip.getTree();
assertTrue(repo.hasObject(t));
assertTrue(repo.hasObject(tr.get(t, "a")));
tip = tip.getParentCount() > 0 ? tip.getParent(0) : null;
} while (tip != null);
}

@Test
public void deleteBranch_historyPruned() throws Exception {
RevCommit tip = commitChain(10);
tr.branch("b").update(tip);
RefUpdate update = repo.updateRef("refs/heads/b");
update.setForceUpdate(true);
update.delete();
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(gc.getStatistics().numberOfLooseObjects == 0);
}

@Test
public void deleteMergedBranch_historyNotPruned() throws Exception {
RevCommit parent = tr.commit().create();
RevCommit b1Tip = tr.branch("b1").commit().parent(parent).add("x", "x")
.create();
RevCommit b2Tip = tr.branch("b2").commit().parent(parent).add("y", "y")
.create();

// merge b1Tip and b2Tip and update refs/heads/b1 to the merge commit
Merger merger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
merger.merge(b1Tip, b2Tip);
CommitBuilder cb = tr.commit();
cb.parent(b1Tip).parent(b2Tip);
cb.setTopLevelTree(merger.getResultTreeId());
RevCommit mergeCommit = cb.create();
RefUpdate u = repo.updateRef("refs/heads/b1");
u.setNewObjectId(mergeCommit);
u.update();

RefUpdate update = repo.updateRef("refs/heads/b2");
update.setForceUpdate(true);
update.delete();

gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(b2Tip));
}
}

+ 119
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static java.lang.Integer.valueOf;
import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.EmptyProgressMonitor;
import org.eclipse.jgit.revwalk.RevBlob;
import org.junit.Test;

public class GcConcurrentTest extends GcTestCase {
@Test
public void concurrentRepack() throws Exception {
final CyclicBarrier syncPoint = new CyclicBarrier(2);

class DoRepack extends EmptyProgressMonitor implements
Callable<Integer> {

public void beginTask(String title, int totalWork) {
if (title.equals(JGitText.get().writingObjects)) {
try {
syncPoint.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException ignored) {
//
}
}
}

/** @return 0 for success, 1 in case of error when writing pack */
public Integer call() throws Exception {
try {
gc.setProgressMonitor(this);
gc.repack();
return valueOf(0);
} catch (IOException e) {
// leave the syncPoint in broken state so any awaiting
// threads and any threads that call await in the future get
// the BrokenBarrierException
Thread.currentThread().interrupt();
try {
syncPoint.await();
} catch (InterruptedException ignored) {
//
}
return valueOf(1);
}
}
}

RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);

ExecutorService pool = Executors.newFixedThreadPool(2);
try {
DoRepack repack1 = new DoRepack();
DoRepack repack2 = new DoRepack();
Future<Integer> result1 = pool.submit(repack1);
Future<Integer> result2 = pool.submit(repack2);
assertEquals(0, result1.get().intValue() + result2.get().intValue());
} finally {
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
}
}

+ 85
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static org.junit.Assert.assertEquals;

import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.junit.Test;

public class GcDirCacheSavesObjectsTest extends GcTestCase {
@Test
public void testDirCacheSavesObjects() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
bb.commit().add("A", "A3"); // this new content in index should survive
stats = gc.getStatistics();
assertEquals(9, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.gc();
stats = gc.getStatistics();
assertEquals(1, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testDirCacheSavesObjectsWithPruneNow() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
bb.commit().add("A", "A3"); // this new content in index should survive
stats = gc.getStatistics();
assertEquals(9, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.setExpireAgeMillis(0);
fsTick();
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}
}

+ 104
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.util.Iterator;

import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.junit.Test;

public class GcKeepFilesTest extends GcTestCase {
@Test
public void testKeepFiles() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
assertEquals(0, stats.numberOfPackFiles);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);

Iterator<PackFile> packIt = repo.getObjectDatabase().getPacks()
.iterator();
PackFile singlePack = packIt.next();
assertFalse(packIt.hasNext());
File keepFile = new File(singlePack.getPackFile().getPath() + ".keep");
assertFalse(keepFile.exists());
assertTrue(keepFile.createNewFile());
bb.commit().add("A", "A2").add("B", "B2").create();
stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(2, stats.numberOfPackFiles);

// check that no object is packed twice
Iterator<PackFile> packs = repo.getObjectDatabase().getPacks()
.iterator();
PackIndex ind1 = packs.next().getIndex();
assertEquals(4, ind1.getObjectCount());
PackIndex ind2 = packs.next().getIndex();
assertEquals(4, ind2.getObjectCount());
for (MutableEntry e: ind1)
if (ind2.hasObject(e.toObjectId()))
assertFalse(
"the following object is in both packfiles: "
+ e.toObjectId(),
ind2.hasObject(e.toObjectId()));
}
}

+ 180
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java View File

@@ -0,0 +1,180 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static java.lang.Integer.valueOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.revwalk.RevBlob;
import org.junit.Test;

public class GcPackRefsTest extends GcTestCase {
@Test
public void looseRefPacked() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);

gc.packRefs();
assertSame(repo.getRef("t").getStorage(), Storage.PACKED);
}

@Test
public void concurrentOnlyOneWritesPackedRefs() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);

final CyclicBarrier syncPoint = new CyclicBarrier(2);

Callable<Integer> packRefs = new Callable<Integer>() {

/** @return 0 for success, 1 in case of error when writing pack */
public Integer call() throws Exception {
syncPoint.await();
try {
gc.packRefs();
return valueOf(0);
} catch (IOException e) {
return valueOf(1);
}
}
};
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
Future<Integer> p1 = pool.submit(packRefs);
Future<Integer> p2 = pool.submit(packRefs);
assertEquals(1, p1.get().intValue() + p2.get().intValue());
} finally {
pool.shutdown();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
}

@Test
public void whileRefLockedRefNotPackedNoError()
throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t1", a);
tr.lightweightTag("t2", a);
LockFile refLock = new LockFile(new File(repo.getDirectory(),
"refs/tags/t1"), repo.getFS());
try {
refLock.lock();
gc.packRefs();
} finally {
refLock.unlock();
}

assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE);
assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED);
}

@Test
public void whileRefUpdatedRefUpdateSucceeds()
throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
final RevBlob b = tr.blob("b");

final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2);
final CyclicBarrier packRefsDone = new CyclicBarrier(2);
ExecutorService pool = Executors.newFixedThreadPool(2);
try {
Future<Result> result = pool.submit(new Callable<Result>() {

public Result call() throws Exception {
RefUpdate update = new RefDirectoryUpdate(
(RefDirectory) repo.getRefDatabase(),
repo.getRef("refs/tags/t")) {
@Override
public boolean isForceUpdate() {
try {
refUpdateLockedRef.await();
packRefsDone.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
return super.isForceUpdate();
}
};
update.setForceUpdate(true);
update.setNewObjectId(b);
return update.update();
}
});

pool.submit(new Callable<Void>() {
public Void call() throws Exception {
refUpdateLockedRef.await();
gc.packRefs();
packRefsDone.await();
return null;
}
});

assertSame(result.get(), Result.FORCED);

} finally {
pool.shutdownNow();
pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}

assertEquals(repo.getRef("refs/tags/t").getObjectId(), b);
}
}

+ 120
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.Collections;
import java.util.Date;

import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.junit.Test;

public class GcPruneNonReferencedTest extends GcTestCase {
@Test
public void nonReferencedNonExpiredObject_notPruned() throws Exception {
RevBlob a = tr.blob("a");
gc.setExpire(new Date(lastModified(a)));
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(a));
}

@Test
public void nonReferencedExpiredObject_pruned() throws Exception {
RevBlob a = tr.blob("a");
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertFalse(repo.hasObject(a));
}

@Test
public void nonReferencedExpiredObjectTree_pruned() throws Exception {
RevBlob a = tr.blob("a");
RevTree t = tr.tree(tr.file("a", a));
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertFalse(repo.hasObject(t));
assertFalse(repo.hasObject(a));
}

@Test
public void nonReferencedObjects_onlyExpiredPruned() throws Exception {
RevBlob a = tr.blob("a");
gc.setExpire(new Date(lastModified(a) + 1));

fsTick();
RevBlob b = tr.blob("b");

gc.prune(Collections.<ObjectId> emptySet());
assertFalse(repo.hasObject(a));
assertTrue(repo.hasObject(b));
}

@Test
public void testPackCommitsAndLooseOneWithPruneNow() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);
gc.setExpireAgeMillis(0);
fsTick();
gc.gc();
stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(8, stats.numberOfPackedObjects);
assertEquals(2, stats.numberOfPackFiles);
}
}

+ 146
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static org.junit.Assert.assertEquals;

import java.io.File;
import java.util.Collections;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Test;

public class GcReflogTest extends GcTestCase {
@Test
public void testPruneNone() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master")
.delete();
stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
tr.blob("x");
stats = gc.getStatistics();
assertEquals(9, stats.numberOfLooseObjects);
gc.prune(Collections.<ObjectId> emptySet());
stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
}

@Test
public void testPackRepoWithCorruptReflog() throws Exception {
// create a reflog entry "0000... 0000... foobar" by doing an initial
// refupdate for HEAD which points to a non-existing ref. The
// All-Projects repo of gerrit instances had such entries
RefUpdate ru = repo.updateRef(Constants.HEAD);
ru.link("refs/to/garbage");
tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
.create();
// make sure HEAD exists
Git.wrap(repo).checkout().setName("refs/heads/master").call();
gc.gc();
}

@Test
public void testPackCommitsAndLooseOneNoReflog() throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);

FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
FileUtils.delete(
new File(repo.getDirectory(), "logs/refs/heads/master"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
gc.gc();

stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}

@Test
public void testPackCommitsAndLooseOneWithPruneNowNoReflog()
throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create();
tr.update("refs/heads/master", first);

stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects);

FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
FileUtils.delete(
new File(repo.getDirectory(), "logs/refs/heads/master"),
FileUtils.RETRY | FileUtils.SKIP_MISSING);
gc.setExpireAgeMillis(0);
gc.gc();

stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects);
assertEquals(4, stats.numberOfPackedObjects);
assertEquals(1, stats.numberOfPackFiles);
}
}

+ 78
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import static org.junit.Assert.assertTrue;

import java.util.Collections;

import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevTag;
import org.junit.Test;

public class GcTagTest extends GcTestCase {
@Test
public void lightweightTag_objectNotPruned() throws Exception {
RevBlob a = tr.blob("a");
tr.lightweightTag("t", a);
gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(a));
}

@Test
public void annotatedTag_objectNotPruned() throws Exception {
RevBlob a = tr.blob("a");
RevTag t = tr.tag("t", a); // this doesn't create the refs/tags/t ref
tr.lightweightTag("t", t);

gc.setExpireAgeMillis(0);
fsTick();
gc.prune(Collections.<ObjectId> emptySet());
assertTrue(repo.hasObject(t));
assertTrue(repo.hasObject(a));
}
}

+ 113
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.internal.storage.file;

import java.io.IOException;

import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.After;
import org.junit.Before;

public abstract class GcTestCase extends LocalDiskRepositoryTestCase {
protected TestRepository<FileRepository> tr;
protected FileRepository repo;
protected GC gc;
protected RepoStatistics stats;

@Before
public void setUp() throws Exception {
super.setUp();
repo = createWorkRepository();
tr = new TestRepository<FileRepository>((repo));
gc = new GC(repo);
}

@After
public void tearDown() throws Exception {
super.tearDown();
}

/**
* Create a chain of commits of given depth.
* <p>
* Each commit contains one file named "a" containing the index of the
* commit in the chain as its content. The created commit chain is
* referenced from any ref.
* <p>
* A chain of depth = N will create 3*N objects in Gits object database. For
* each depth level three objects are created: the commit object, the
* top-level tree object and a blob for the content of the file "a".
*
* @param depth
* the depth of the commit chain.
* @return the commit that is the tip of the commit chain
* @throws Exception
*/
protected RevCommit commitChain(int depth) throws Exception {
if (depth <= 0)
throw new IllegalArgumentException("Chain depth must be > 0");
CommitBuilder cb = tr.commit();
RevCommit tip;
do {
--depth;
tip = cb.add("a", "" + depth).message("" + depth).create();
cb = cb.child();
} while (depth > 0);
return tip;
}

protected long lastModified(AnyObjectId objectId) {
return repo.getObjectDatabase().fileFor(objectId).lastModified();
}

protected static void fsTick() throws InterruptedException, IOException {
RepositoryTestCase.fsTick(null);
}
}

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java View File

@@ -67,7 +67,6 @@ import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.AnyObjectId;
@@ -79,6 +78,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.transport.PackParser;
import org.junit.After;
import org.junit.Before;

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java View File

@@ -61,7 +61,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -75,6 +74,7 @@ import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

public class RefUpdateTest extends SampleDataRepositoryTestCase {

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java View File

@@ -55,13 +55,13 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;

import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.CheckoutEntry;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

public class ReflogReaderTest extends SampleDataRepositoryTestCase {

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java View File

@@ -63,7 +63,6 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
@@ -83,6 +82,7 @@ import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Test;


+ 11
- 11
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java View File

@@ -46,35 +46,35 @@

package org.eclipse.jgit.internal.storage.file;

import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.File;
import java.io.IOException;

import org.eclipse.jgit.internal.storage.file.PackFile;
import org.eclipse.jgit.internal.storage.file.WindowCursor;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

public class T0004_PackReaderTest extends SampleDataRepositoryTestCase {
private static final String PACK_NAME = "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f";
private static final File TEST_PACK = JGitTestUtil.getTestResourceFile(PACK_NAME + ".pack");
private static final String PACK_NAME = "34be9032ac282b11fa9babdc2b2a93ca996c9c2f";

@Test
public void test003_lookupCompressedObject() throws IOException {
final PackFile pr;
final ObjectId id;
final ObjectLoader or;

PackFile pr = null;
for (PackFile p : db.getObjectDatabase().getPacks()) {
if (PACK_NAME.equals(p.getPackName())) {
pr = p;
break;
}
}
assertNotNull("have pack-" + PACK_NAME, pr);

id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327");
pr = new PackFile(TEST_PACK, PACK.getBit() | INDEX.getBit());
or = pr.get(new WindowCursor(null), id);
assertNotNull(or);
assertEquals(Constants.OBJ_TREE, or.getType());

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java View File

@@ -56,11 +56,11 @@ import java.util.List;

import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.util.MutableInteger;
import org.junit.Before;
import org.junit.Test;

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java View File

@@ -405,7 +405,7 @@ public class DirCacheCheckoutMaliciousPathTest extends RepositoryTestCase {
} catch (InvalidPathException e) {
if (good)
throw e;
assertTrue(e.getMessage().startsWith("Invalid path: "));
assertTrue(e.getMessage().startsWith("Invalid path"));
}
}


+ 25
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java View File

@@ -467,7 +467,9 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
*3 D F D Y N N Keep
*4 D F D N N N Conflict
*5 D F F Y N N Y Keep
*5b D F F Y N N N Conflict
*6 D F F N N N Y Keep
*6b D F F N N N N Conflict
*7 F D F Y Y N N Update
*8 F D F N Y N N Conflict
*9 F D F Y N N N Update
@@ -540,7 +542,17 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// 5
doit(mk("DF/DF"), mk("DF"), mk("DF"));
assertRemoved("DF/DF");
assertEquals(0, dco.getConflicts().size());
assertEquals(0, dco.getUpdated().size());
}

@Test
public void testDirectoryFileConflicts_5b() throws Exception {
// 5
doit(mk("DF/DF"), mkmap("DF", "different"), mk("DF"));
assertRemoved("DF/DF");
assertConflict("DF");
assertEquals(0, dco.getUpdated().size());
}

@Test
@@ -550,6 +562,19 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
writeTrashFile("DF", "different");
go();
assertRemoved("DF/DF");
assertEquals(0, dco.getConflicts().size());
assertEquals(0, dco.getUpdated().size());
}

@Test
public void testDirectoryFileConflicts_6b() throws Exception {
// 6
setupCase(mk("DF/DF"), mk("DF"), mkmap("DF", "different"));
writeTrashFile("DF", "again different");
go();
assertRemoved("DF/DF");
assertConflict("DF");
assertEquals(0, dco.getUpdated().size());
}

@Test

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java View File

@@ -59,9 +59,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;

import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

/**

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java View File

@@ -59,8 +59,8 @@ import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RevisionSyntaxException;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

public class RepositoryResolveTest extends SampleDataRepositoryTestCase {

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java View File

@@ -55,7 +55,7 @@ import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

@SuppressWarnings("deprecation")

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java View File

@@ -47,13 +47,13 @@ import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays;

import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Before;
import org.junit.Test;


+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java View File

@@ -52,12 +52,12 @@ import java.io.IOException;

import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Test;


+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java View File

@@ -47,9 +47,9 @@ import static org.junit.Assert.assertEquals;
import java.util.Arrays;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.util.GitDateFormatter;
import org.eclipse.jgit.util.GitDateFormatter.Format;
import org.junit.Before;

org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SampleDataRepositoryTestCase.java → org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java View File

@@ -44,10 +44,13 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.eclipse.jgit.junit;
package org.eclipse.jgit.test.resources;

import java.io.File;

import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;


/** Test case which includes C Git generated pack files for testing. */
public abstract class SampleDataRepositoryTestCase extends RepositoryTestCase {
@@ -66,11 +69,11 @@ public abstract class SampleDataRepositoryTestCase extends RepositoryTestCase {
};
final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack");
for (String n : packs) {
copyFile(JGitTestUtil.getTestResourceFile(n + ".pack"), new File(packDir, n + ".pack"));
copyFile(JGitTestUtil.getTestResourceFile(n + ".idx"), new File(packDir, n + ".idx"));
JGitTestUtil.copyTestResource(n + ".pack", new File(packDir, n + ".pack"));
JGitTestUtil.copyTestResource(n + ".idx", new File(packDir, n + ".idx"));
}

copyFile(JGitTestUtil.getTestResourceFile("packed-refs"), new File(db
.getDirectory(), "packed-refs"));
JGitTestUtil.copyTestResource("packed-refs",
new File(db.getDirectory(), "packed-refs"));
}
}

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java View File

@@ -61,13 +61,13 @@ import java.util.Set;
import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test;

public class BundleWriterTest extends SampleDataRepositoryTestCase {

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java View File

@@ -55,7 +55,6 @@ import java.util.Map;

import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
@@ -63,6 +62,7 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.junit.Before;
import org.junit.Test;

+ 1
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java View File

@@ -55,12 +55,12 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

+ 4
- 2
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserBadlyFormattedTest.java View File

@@ -92,7 +92,8 @@ public class GitDateParserBadlyFormattedTest {
Calendar ref = new GregorianCalendar(SystemReader.getInstance()
.getTimeZone(), SystemReader.getInstance().getLocale());
try {
GitDateParser.parse(dateStr, ref);
GitDateParser.parse(dateStr, ref, SystemReader.getInstance()
.getLocale());
fail("The expected ParseException while parsing '" + dateStr
+ "' did not occur.");
} catch (ParseException e) {
@@ -103,7 +104,8 @@ public class GitDateParserBadlyFormattedTest {
@Theory
public void badlyFormattedWithoutRef() {
try {
GitDateParser.parse(dateStr, null);
GitDateParser.parse(dateStr, null, SystemReader.getInstance()
.getLocale());
fail("The expected ParseException while parsing '" + dateStr
+ "' did not occur.");
} catch (ParseException e) {

+ 26
- 13
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserTest.java View File

@@ -72,7 +72,8 @@ public class GitDateParserTest {
GregorianCalendar cal = new GregorianCalendar(SystemReader
.getInstance().getTimeZone(), SystemReader.getInstance()
.getLocale());
Date parse = GitDateParser.parse("yesterday", cal);
Date parse = GitDateParser.parse("yesterday", cal, SystemReader
.getInstance().getLocale());
cal.add(Calendar.DATE, -1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
@@ -87,7 +88,8 @@ public class GitDateParserTest {
GregorianCalendar cal = new GregorianCalendar(SystemReader
.getInstance().getTimeZone(), SystemReader.getInstance()
.getLocale());
Date parse = GitDateParser.parse("never", cal);
Date parse = GitDateParser.parse("never", cal, SystemReader
.getInstance().getLocale());
Assert.assertEquals(GitDateParser.NEVER, parse);
parse = GitDateParser.parse("never", null);
Assert.assertEquals(GitDateParser.NEVER, parse);
@@ -104,7 +106,8 @@ public class GitDateParserTest {
.getLocale());
cal.setTime(refDate);

Date parse = GitDateParser.parse("now", cal);
Date parse = GitDateParser.parse("now", cal, SystemReader.getInstance()
.getLocale());
Assert.assertEquals(refDate, parse);
long t1 = SystemReader.getInstance().getCurrentTime();
parse = GitDateParser.parse("now", null);
@@ -123,7 +126,8 @@ public class GitDateParserTest {
.getLocale());
cal.setTime(refDate);

Date parse = GitDateParser.parse("2 weeks ago", cal);
Date parse = GitDateParser.parse("2 weeks ago", cal, SystemReader
.getInstance().getLocale());
Assert.assertEquals(df.parse("2007-02-07 15:35:00 +0100"), parse);
}

@@ -138,7 +142,8 @@ public class GitDateParserTest {
.getLocale());
cal.setTime(refDate);

Date parse = GitDateParser.parse("2 weeks ago", cal);
Date parse = GitDateParser.parse("2 weeks ago", cal, SystemReader.getInstance()
.getLocale());
Assert.assertEquals(df.parse("2007-02-07 15:35:00 +0100"), parse);
parse = GitDateParser.parse("3 days 2 weeks ago", cal);
Assert.assertEquals(df.parse("2007-02-04 15:35:00 +0100"), parse);
@@ -151,7 +156,8 @@ public class GitDateParserTest {
String dateStr = "2007-02-21 15:35:00 +0100";
Date exp = SystemReader.getInstance()
.getSimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}

@@ -161,7 +167,8 @@ public class GitDateParserTest {
Date exp = SystemReader.getInstance()
.getSimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z")
.parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}

@@ -170,7 +177,8 @@ public class GitDateParserTest {
String dateStr = "2007-02-21";
Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy-MM-dd")
.parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}

@@ -179,7 +187,8 @@ public class GitDateParserTest {
String dateStr = "2007.02.21";
Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy.MM.dd")
.parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}

@@ -188,7 +197,8 @@ public class GitDateParserTest {
String dateStr = "02/21/2007";
Date exp = SystemReader.getInstance().getSimpleDateFormat("MM/dd/yyyy")
.parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}

@@ -197,7 +207,8 @@ public class GitDateParserTest {
String dateStr = "21.02.2007";
Date exp = SystemReader.getInstance().getSimpleDateFormat("dd.MM.yyyy")
.parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}

@@ -207,7 +218,8 @@ public class GitDateParserTest {
Date exp = SystemReader.getInstance()
.getSimpleDateFormat("EEE MMM dd HH:mm:ss yyyy Z")
.parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}

@@ -216,7 +228,8 @@ public class GitDateParserTest {
String dateStr = "Wed Feb 21 15:35:00 2007";
Date exp = SystemReader.getInstance()
.getSimpleDateFormat("EEE MMM dd HH:mm:ss yyyy").parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null);
Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse);
}
}

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

@@ -270,6 +270,10 @@ invalidObject=Invalid {0} {1}:{2}
invalidOldIdSent=invalid old id sent
invalidPacketLineHeader=Invalid packet line header: {0}
invalidPath=Invalid path: {0}
invalidPathContainsSeparator=Invalid path (contains separator ''{0}''): {1}
invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0}
invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0}
invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1}
invalidReflogRevision=Invalid reflog revision: {0}
invalidRefName=Invalid ref name: {0}
invalidRemote=Invalid remote: {0}

+ 8
- 4
org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java View File

@@ -93,11 +93,15 @@ public class AddCommand extends GitCommand<DirCache> {
}

/**
* Add a path to a file/directory whose content should be added.
* <p>
* A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and
* <code>dir/file2</code>) can also be given to add all files in the
* directory, recursively. Fileglobs (e.g. *.c) are not yet supported.
*
* @param filepattern
* File to add content from. Also a leading directory name (e.g.
* dir to add dir/file1 and dir/file2) can be given to add all
* files in the directory, recursively. Fileglobs (e.g. *.c) are
* not yet supported.
* repository-relative path of file/directory to add (with
* <code>/</code> as separator)
* @return {@code this}
*/
public AddCommand addFilepattern(String filepattern) {

+ 2
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java View File

@@ -86,9 +86,10 @@ public class BlameCommand extends GitCommand<BlameResult> {
}

/**
* Set file path
* Set file path.
*
* @param filePath
* file path (with <code>/</code> as separator)
* @return this command
*/
public BlameCommand setFilePath(String filePath) {

+ 4
- 3
org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java View File

@@ -305,15 +305,16 @@ public class CheckoutCommand extends GitCommand<Ref> {
}

/**
* Add a single path to the list of paths to check out. To check out all
* paths, use {@link #setAllPaths(boolean)}.
* Add a single slash-separated path to the list of paths to check out. To
* check out all paths, use {@link #setAllPaths(boolean)}.
* <p>
* If this option is set, neither the {@link #setCreateBranch(boolean)} nor
* {@link #setName(String)} option is considered. In other words, these
* options are exclusive.
*
* @param path
* path to update in the working tree and index
* path to update in the working tree and index (with
* <code>/</code> as separator)
* @return {@code this}
*/
public CheckoutCommand addPath(String path) {

+ 1
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java View File

@@ -174,7 +174,7 @@ public class CleanCommand extends GitCommand<Set<String>> {
* If paths are set, only these paths are affected by the cleaning.
*
* @param paths
* the paths to set
* the paths to set (with <code>/</code> as separator)
* @return {@code this}
*/
public CleanCommand setPaths(Set<String> paths) {

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

@@ -667,14 +667,14 @@ public class CommitCommand extends GitCommand<RevCommit> {
}

/**
* Commit dedicated path only
*
* Commit dedicated path only.
* <p>
* 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.
* commits all files/directories below the specified path.
*
* @param only
* path to commit
* path to commit (with <code>/</code> as separator)
* @return {@code this}
*/
public CommitCommand setOnly(String only) {

+ 20
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java View File

@@ -124,6 +124,26 @@ public class Git {
return new Git(repo);
}

/**
* Frees resources held by the underlying {@link Repository} instance. It is
* recommended to call this method as soon as you don't need a reference to
* this {@link Git} instance and the underlying {@link Repository} instance
* anymore. This method closes the underlying object and ref databases. This
* will free memory and file handles. E.g. on Windows the repository will
* keep file handles on pack files unless you call this method. Such open
* file handles may for example prevent that the repository folder in the
* filesystem can be deleted.
* <p>
* After calling close() you should not use this {@link Git} instance and
* the underlying {@link Repository} instance anymore.
*
* @since 3.2
*/
public void close() {
if (repo != null)
repo.close();
}

/**
* Returns a command object to execute a {@code clone} command
*

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

@@ -282,11 +282,11 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> {

/**
* Show only commits that affect any of the specified paths. The path must
* either name a file or a directory exactly. Note that regex expressions or
* wildcards are not supported.
* either name a file or a directory exactly and use <code>/</code> (slash)
* as separator. Note that regex expressions or wildcards are not supported.
*
* @param path
* a path is relative to the top level of the repository
* a repository-relative path (with <code>/</code> as separator)
* @return {@code this}
*/
public LogCommand addPath(String path) {

+ 2
- 4
org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java View File

@@ -379,8 +379,7 @@ public class MergeCommand extends GitCommand<MergeResult> {
if (failingPaths != null) {
repo.writeMergeCommitMsg(null);
repo.writeMergeHeads(null);
return new MergeResult(null,
merger.getBaseCommit(0, 1),
return new MergeResult(null, merger.getBaseCommitId(),
new ObjectId[] {
headCommit.getId(), srcCommit.getId() },
MergeStatus.FAILED, mergeStrategy,
@@ -390,8 +389,7 @@ public class MergeCommand extends GitCommand<MergeResult> {
.formatWithConflicts(mergeMessage,
unmergedPaths);
repo.writeMergeCommitMsg(mergeMessageWithConflicts);
return new MergeResult(null,
merger.getBaseCommit(0, 1),
return new MergeResult(null, merger.getBaseCommitId(),
new ObjectId[] { headCommit.getId(),
srcCommit.getId() },
MergeStatus.CONFLICTING, mergeStrategy,

+ 110
- 28
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java View File

@@ -70,6 +70,7 @@ import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.api.errors.StashApplyFailureException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffFormatter;
@@ -79,6 +80,7 @@ import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
@@ -158,6 +160,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> {

private static final String MESSAGE_SQUASH = "message-squash"; //$NON-NLS-1$

private static final String AUTOSTASH = "autostash"; //$NON-NLS-1$

private static final String AUTOSTASH_MSG = "On {0}: autostash";

/**
* The available operations
*/
@@ -257,11 +263,26 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
.resolve(upstreamCommitId));
break;
case BEGIN:
autoStash();
if (stopAfterInitialization
|| !walk.isMergedInto(
walk.parseCommit(repo.resolve(Constants.HEAD)),
upstreamCommit)) {
org.eclipse.jgit.api.Status status = Git.wrap(repo)
.status().call();
if (status.hasUncommittedChanges()) {
List<String> list = new ArrayList<String>();
list.addAll(status.getUncommittedChanges());
return RebaseResult.uncommittedChanges(list);
}
}
RebaseResult res = initFilesAndRewind();
if (stopAfterInitialization)
return RebaseResult.INTERACTIVE_PREPARED_RESULT;
if (res != null)
if (res != null) {
autoStashApply();
return res;
}
}

if (monitor.isCancelled())
@@ -321,12 +342,63 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
return finishRebase(newHead, lastStepWasForward);
} catch (CheckoutConflictException cce) {
return new RebaseResult(cce.getConflictingPaths());
return RebaseResult.conflicts(cce.getConflictingPaths());
} catch (IOException ioe) {
throw new JGitInternalException(ioe.getMessage(), ioe);
}
}

private void autoStash() throws GitAPIException, IOException {
if (repo.getConfig().getBoolean(ConfigConstants.CONFIG_REBASE_SECTION,
ConfigConstants.CONFIG_KEY_AUTOSTASH, false)) {
String message = MessageFormat.format(
AUTOSTASH_MSG,
Repository
.shortenRefName(getHeadName(getHead())));
RevCommit stashCommit = Git.wrap(repo).stashCreate().setRef(null)
.setWorkingDirectoryMessage(
message)
.call();
if (stashCommit != null) {
FileUtils.mkdir(rebaseState.getDir());
rebaseState.createFile(AUTOSTASH, stashCommit.getName());
}
}
}

private boolean autoStashApply() throws IOException, GitAPIException {
boolean conflicts = false;
if (rebaseState.getFile(AUTOSTASH).exists()) {
String stash = rebaseState.readFile(AUTOSTASH);
try {
Git.wrap(repo).stashApply().setStashRef(stash)
.ignoreRepositoryState(true).call();
} catch (StashApplyFailureException e) {
conflicts = true;
RevWalk rw = new RevWalk(repo);
ObjectId stashId = repo.resolve(stash);
RevCommit commit = rw.parseCommit(stashId);
updateStashRef(commit, commit.getAuthorIdent(),
commit.getShortMessage());
}
}
return conflicts;
}

private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
String refLogMessage) throws IOException {
Ref currentRef = repo.getRef(Constants.R_STASH);
RefUpdate refUpdate = repo.updateRef(Constants.R_STASH);
refUpdate.setNewObjectId(commitId);
refUpdate.setRefLogIdent(refLogIdent);
refUpdate.setRefLogMessage(refLogMessage, false);
if (currentRef != null)
refUpdate.setExpectedOldObjectId(currentRef.getObjectId());
else
refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
refUpdate.forceUpdate();
}

private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick)
throws IOException, GitAPIException {
if (Action.COMMENT.equals(step.getAction()))
@@ -340,7 +412,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
RevCommit commitToPick = walk.parseCommit(ids.iterator().next());
if (shouldPick) {
if (monitor.isCancelled())
return new RebaseResult(commitToPick, Status.STOPPED);
return RebaseResult.result(Status.STOPPED, commitToPick);
RebaseResult result = cherryPickCommit(commitToPick);
if (result != null)
return result;
@@ -403,8 +475,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
switch (cherryPickResult.getStatus()) {
case FAILED:
if (operation == Operation.BEGIN)
return abort(new RebaseResult(
cherryPickResult.getFailingPaths()));
return abort(RebaseResult.failed(cherryPickResult
.getFailingPaths()));
else
return stop(commitToPick, Status.STOPPED);
case CONFLICTING:
@@ -420,10 +492,13 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}

private RebaseResult finishRebase(RevCommit newHead,
boolean lastStepWasForward) throws IOException {
boolean lastStepWasForward) throws IOException, GitAPIException {
String headName = rebaseState.readFile(HEAD_NAME);
updateHead(headName, newHead, upstreamCommit);
boolean stashConflicts = autoStashApply();
FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
if (stashConflicts)
return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
if (lastStepWasForward || newHead == null)
return RebaseResult.FAST_FORWARD_RESULT;
return RebaseResult.OK_RESULT;
@@ -550,7 +625,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} else {
sb.append("# The ").append(count).append(ordinal)
.append(" commit message will be skipped:\n# ");
sb.append(commitToPick.getFullMessage().replaceAll("([\n\r]+)",
sb.append(commitToPick.getFullMessage().replaceAll("([\n\r])",
"$1# "));
}
// Add the previous message without header (i.e first line)
@@ -735,7 +810,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
// Remove cherry pick state file created by CherryPickCommand, it's not
// needed for rebase
repo.writeCherryPickHead(null);
return new RebaseResult(commitToPick, status);
return RebaseResult.result(status, commitToPick);
}

String toAuthorScript(PersonIdent author) {
@@ -797,16 +872,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
// we need to store everything into files so that we can implement
// --skip, --continue, and --abort

Ref head = repo.getRef(Constants.HEAD);
if (head == null || head.getObjectId() == null)
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, Constants.HEAD));
Ref head = getHead();

String headName;
if (head.isSymbolic())
headName = head.getTarget().getName();
else
headName = head.getObjectId().getName();
String headName = getHeadName(head);
ObjectId headId = head.getObjectId();
if (headId == null)
throw new RefNotFoundException(MessageFormat.format(
@@ -845,7 +913,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {

Collections.reverse(cherryPickList);
// create the folder for the meta information
FileUtils.mkdir(rebaseState.getDir());
FileUtils.mkdir(rebaseState.getDir(), true);

repo.writeOrigHead(headId);
rebaseState.createFile(REBASE_HEAD, headId.name());
@@ -881,6 +949,23 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return null;
}

private static String getHeadName(Ref head) {
String headName;
if (head.isSymbolic())
headName = head.getTarget().getName();
else
headName = head.getObjectId().getName();
return headName;
}

private Ref getHead() throws IOException, RefNotFoundException {
Ref head = repo.getRef(Constants.HEAD);
if (head == null || head.getObjectId() == null)
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, Constants.HEAD));
return head;
}

private boolean isInteractive() {
return interactiveHandler != null;
}
@@ -895,10 +980,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
*/
public RevCommit tryFastForward(RevCommit newCommit) throws IOException,
GitAPIException {
Ref head = repo.getRef(Constants.HEAD);
if (head == null || head.getObjectId() == null)
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, Constants.HEAD));
Ref head = getHead();

ObjectId headId = head.getObjectId();
if (headId == null)
@@ -908,11 +990,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
if (walk.isMergedInto(newCommit, headCommit))
return newCommit;

String headName;
if (head.isSymbolic())
headName = head.getTarget().getName();
else
headName = head.getObjectId().getName();
String headName = getHeadName(head);
return tryFastForward(headName, headCommit, newCommit);
}

@@ -992,7 +1070,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
}

private RebaseResult abort(RebaseResult result) throws IOException {
private RebaseResult abort(RebaseResult result) throws IOException,
GitAPIException {
try {
ObjectId origHead = repo.readOrigHead();
String commitId = origHead != null ? origHead.name() : null;
@@ -1041,9 +1120,12 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
JGitText.get().abortingRebaseFailed);
}
}
boolean stashConflicts = autoStashApply();
// cleanup the files
FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
repo.writeCherryPickHead(null);
if (stashConflicts)
return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
return result;

} finally {

+ 76
- 13
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java View File

@@ -104,6 +104,18 @@ public class RebaseResult {
return false;
}
},
/**
* The repository contains uncommitted changes and the rebase is not a
* fast-forward
*
* @since 3.2
*/
UNCOMMITTED_CHANGES {
@Override
public boolean isSuccessful() {
return false;
}
},
/**
* Conflicts: checkout of target HEAD failed
*/
@@ -153,6 +165,18 @@ public class RebaseResult {
public boolean isSuccessful() {
return false;
}
},

/**
* Applying stash resulted in conflicts
*
* @since 3.2
*/
STASH_APPLY_CONFLICTS {
@Override
public boolean isSuccessful() {
return true;
}
};

/**
@@ -177,6 +201,9 @@ public class RebaseResult {
static final RebaseResult INTERACTIVE_PREPARED_RESULT = new RebaseResult(
Status.INTERACTIVE_PREPARED);

static final RebaseResult STASH_APPLY_CONFLICTS_RESULT = new RebaseResult(
Status.STASH_APPLY_CONFLICTS);

private final Status status;

private final RevCommit currentCommit;
@@ -185,21 +212,29 @@ public class RebaseResult {

private List<String> conflicts;

private List<String> uncommittedChanges;

private RebaseResult(Status status) {
this.status = status;
currentCommit = null;
}

private RebaseResult(Status status, RevCommit commit) {
this.status = status;
currentCommit = commit;
}

/**
* Create <code>RebaseResult</code> with status {@link Status#STOPPED}
* Create <code>RebaseResult</code>
*
* @param status
* @param commit
* current commit
* @param status
* @return the RebaseResult
*/
RebaseResult(RevCommit commit, RebaseResult.Status status) {
this.status = status;
currentCommit = commit;
static RebaseResult result(RebaseResult.Status status,
RevCommit commit) {
return new RebaseResult(status, commit);
}

/**
@@ -207,11 +242,13 @@ public class RebaseResult {
*
* @param failingPaths
* list of paths causing this rebase to fail
* @return the RebaseResult
*/
RebaseResult(Map<String, MergeFailureReason> failingPaths) {
status = Status.FAILED;
currentCommit = null;
this.failingPaths = failingPaths;
static RebaseResult failed(
Map<String, MergeFailureReason> failingPaths) {
RebaseResult result = new RebaseResult(Status.FAILED);
result.failingPaths = failingPaths;
return result;
}

/**
@@ -219,11 +256,26 @@ public class RebaseResult {
*
* @param conflicts
* the list of conflicting paths
* @return the RebaseResult
*/
RebaseResult(List<String> conflicts) {
status = Status.CONFLICTS;
currentCommit = null;
this.conflicts = conflicts;
static RebaseResult conflicts(List<String> conflicts) {
RebaseResult result = new RebaseResult(Status.CONFLICTS);
result.conflicts = conflicts;
return result;
}

/**
* Create <code>RebaseResult</code> with status
* {@link Status#UNCOMMITTED_CHANGES}
*
* @param uncommittedChanges
* the list of paths
* @return the RebaseResult
*/
static RebaseResult uncommittedChanges(List<String> uncommittedChanges) {
RebaseResult result = new RebaseResult(Status.UNCOMMITTED_CHANGES);
result.uncommittedChanges = uncommittedChanges;
return result;
}

/**
@@ -256,4 +308,15 @@ public class RebaseResult {
public List<String> getConflicts() {
return conflicts;
}

/**
* @return the list of uncommitted changes if status is
* {@link Status#UNCOMMITTED_CHANGES}
*
* @since 3.2
*/
public List<String> getUncommittedChanges() {
return uncommittedChanges;
}

}

+ 5
- 4
org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java View File

@@ -279,16 +279,17 @@ public class ResetCommand extends GitCommand<Ref> {
}

/**
* @param file
* the file to add
* @param path
* repository-relative path of file/directory to reset (with
* <code>/</code> as separator)
* @return this instance
*/
public ResetCommand addPath(String file) {
public ResetCommand addPath(String path) {
if (mode != null)
throw new JGitInternalException(MessageFormat.format(
JGitText.get().illegalCombinationOfArguments, "<paths>...",
"[--mixed | --soft | --hard]")); //$NON-NLS-1$
filepaths.add(file);
filepaths.add(path);
return this;
}


+ 2
- 2
org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java View File

@@ -191,14 +191,14 @@ public class RevertCommand extends GitCommand<RevCommit> {
.getFailingPaths();
if (failingPaths != null)
failingResult = new MergeResult(null,
merger.getBaseCommit(0, 1),
merger.getBaseCommitId(),
new ObjectId[] { headCommit.getId(),
srcParent.getId() },
MergeStatus.FAILED, MergeStrategy.RECURSIVE,
merger.getMergeResults(), failingPaths, null);
else
failingResult = new MergeResult(null,
merger.getBaseCommit(0, 1),
merger.getBaseCommitId(),
new ObjectId[] { headCommit.getId(),
srcParent.getId() },
MergeStatus.CONFLICTING,

+ 2
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java View File

@@ -104,7 +104,8 @@ public class RmCommand extends GitCommand<DirCache> {

/**
* @param filepattern
* File to remove.
* repository-relative path of file to remove (with
* <code>/</code> as separator)
* @return {@code this}
*/
public RmCommand addFilepattern(String filepattern) {

+ 15
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java View File

@@ -90,6 +90,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> {

private boolean applyIndex = true;

private boolean ignoreRepositoryState;

/**
* Create command to apply the changes of a stashed commit
*
@@ -113,6 +115,16 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
return this;
}

/**
* @param ignoreRepositoryState
* @return {@code this}
* @since 3.2
*/
public StashApplyCommand ignoreRepositoryState(boolean ignoreRepositoryState) {
this.ignoreRepositoryState = ignoreRepositoryState;
return this;
}

private ObjectId getStashId() throws GitAPIException {
final String revision = stashRef != null ? stashRef : DEFAULT_REF;
final ObjectId stashId;
@@ -143,7 +155,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
StashApplyFailureException {
checkCallable();

if (repo.getRepositoryState() != RepositoryState.SAFE)
if (!ignoreRepositoryState
&& repo.getRepositoryState() != RepositoryState.SAFE)
throw new WrongRepositoryStateException(MessageFormat.format(
JGitText.get().stashApplyOnUnsafeRepository,
repo.getRepositoryState()));
@@ -185,6 +198,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
.newMerger(repo, true);
ixMerger.setCommitNames(new String[] { "stashed HEAD",
"HEAD", "stashed index" });
ixMerger.setBase(stashHeadCommit);
boolean ok = ixMerger.merge(headCommit, stashIndexCommit);
if (ok) {
resetIndex(revWalk

+ 3
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java View File

@@ -154,6 +154,7 @@ public class StashCreateCommand extends GitCommand<RevCommit> {

/**
* Set the reference to update with the stashed commit id
* If null, no reference is updated
* <p>
* This value defaults to {@link Constants#R_STASH}
*
@@ -185,6 +186,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> {

private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
String refLogMessage) throws IOException {
if (ref == null)
return;
Ref currentRef = repo.getRef(ref);
RefUpdate refUpdate = repo.updateRef(ref);
refUpdate.setNewObjectId(commitId);

+ 37
- 7
org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java View File

@@ -43,6 +43,7 @@
package org.eclipse.jgit.api;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@@ -66,19 +67,22 @@ public class Status {

private final boolean clean;

private final boolean hasUncommittedChanges;;

/**
* @param diff
*/
public Status(IndexDiff diff) {
super();
this.diff = diff;
clean = diff.getAdded().isEmpty() //
&& diff.getChanged().isEmpty() //
&& diff.getRemoved().isEmpty() //
&& diff.getMissing().isEmpty() //
&& diff.getModified().isEmpty() //
&& diff.getUntracked().isEmpty() //
&& diff.getConflicting().isEmpty();
hasUncommittedChanges = !diff.getAdded().isEmpty() //
|| !diff.getChanged().isEmpty() //
|| !diff.getRemoved().isEmpty() //
|| !diff.getMissing().isEmpty() //
|| !diff.getModified().isEmpty() //
|| !diff.getConflicting().isEmpty();
clean = !hasUncommittedChanges //
&& diff.getUntracked().isEmpty();
}

/**
@@ -89,6 +93,15 @@ public class Status {
return clean;
}

/**
* @return true if any tracked file is changed
*
* @since 3.2
*/
public boolean hasUncommittedChanges() {
return hasUncommittedChanges;
}

/**
* @return list of files added to the index, not in HEAD (e.g. what you get
* if you call 'git add ...' on a newly created file)
@@ -168,4 +181,21 @@ public class Status {
public Set<String> getIgnoredNotInIndex() {
return Collections.unmodifiableSet(diff.getIgnoredNotInIndex());
}

/**
* @return set of files and folders that are known to the repo and changed
* either in the index or in the working tree.
*
* @since 3.2
*/
public Set<String> getUncommittedChanges() {
Set<String> uncommittedChanges = new HashSet<String>();
uncommittedChanges.addAll(diff.getAdded());
uncommittedChanges.addAll(diff.getChanged());
uncommittedChanges.addAll(diff.getRemoved());
uncommittedChanges.addAll(diff.getMissing());
uncommittedChanges.addAll(diff.getModified());
uncommittedChanges.addAll(diff.getConflicting());
return uncommittedChanges;
}
}

+ 2
- 1
org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java View File

@@ -89,7 +89,8 @@ public class StatusCommand extends GitCommand<Status> {
* supported.
*
* @param path
* a path is relative to the top level of the repository
* repository-relative path of file/directory to show status for
* (with <code>/</code> as separator)
* @return {@code this}
* @since 3.1
*/

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java View File

@@ -93,6 +93,7 @@ public class SubmoduleAddCommand extends
* Set repository-relative path of submodule
*
* @param path
* (with <code>/</code> as separator)
* @return this command
*/
public SubmoduleAddCommand setPath(final String path) {

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java View File

@@ -83,6 +83,7 @@ public class SubmoduleInitCommand extends GitCommand<Collection<String>> {
* Add repository-relative submodule path to initialize
*
* @param path
* (with <code>/</code> as separator)
* @return this command
*/
public SubmoduleInitCommand addPath(final String path) {

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java View File

@@ -83,6 +83,7 @@ public class SubmoduleStatusCommand extends
* Add repository-relative submodule path to limit status reporting to
*
* @param path
* (with <code>/</code> as separator)
* @return this command
*/
public SubmoduleStatusCommand addPath(final String path) {

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java View File

@@ -85,6 +85,7 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> {
* Add repository-relative submodule path to synchronize
*
* @param path
* (with <code>/</code> as separator)
* @return this command
*/
public SubmoduleSyncCommand addPath(final String path) {

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java View File

@@ -110,6 +110,7 @@ public class SubmoduleUpdateCommand extends
* Add repository-relative submodule path to initialize
*
* @param path
* (with <code>/</code> as separator)
* @return this command
*/
public SubmoduleUpdateCommand addPath(final String path) {

+ 45
- 23
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java View File

@@ -76,6 +76,7 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.AutoCRLFOutputStream;

@@ -306,8 +307,7 @@ public class DirCacheCheckout {
void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
WorkingTreeIterator f) throws IOException {
if (m != null) {
if (!isValidPath(m))
throw new InvalidPathException(m.getEntryPathString());
checkValidPath(m);
// There is an entry in the merge commit. Means: we want to update
// what's currently in the index and working-tree to that one
if (i == null) {
@@ -522,8 +522,8 @@ public class DirCacheCheckout {

String name = walk.getPathString();

if (m != null && !isValidPath(m))
throw new InvalidPathException(m.getEntryPathString());
if (m != null)
checkValidPath(m);

if (i == null && m == null && h == null) {
// File/Directory conflict case #20
@@ -555,7 +555,9 @@ public class DirCacheCheckout {
* 3 D F D Y N N Keep
* 4 D F D N N N Conflict
* 5 D F F Y N N Y Keep
* 5b D F F Y N N N Conflict
* 6 D F F N N N Y Keep
* 6b D F F N N N N Conflict
* 7 F D F Y Y N N Update
* 8 F D F N Y N N Conflict
* 9 F D F Y N N N Update
@@ -620,7 +622,11 @@ public class DirCacheCheckout {
case 0xF0D: // 18
remove(name);
break;
case 0xDFF: // 5 6
case 0xDFF: // 5 5b 6 6b
if (equalIdAndMode(iId, iMode, mId, mMode))
keep(dce); // 5 6
else
conflict(name, dce, h, m); // 5b 6b
case 0xFDD: // 10 11
// TODO: make use of tree extension as soon as available in jgit
// we would like to do something like
@@ -1151,14 +1157,14 @@ public class DirCacheCheckout {
forbidden[i] = Constants.encodeASCII(list[i]);
}

private static boolean isValidPath(CanonicalTreeParser t) {
private static void checkValidPath(CanonicalTreeParser t)
throws InvalidPathException {
for (CanonicalTreeParser i = t; i != null; i = i.getParent())
if (!isValidPathSegment(i))
return false;
return true;
checkValidPathSegment(i);
}

private static boolean isValidPathSegment(CanonicalTreeParser t) {
private static void checkValidPathSegment(CanonicalTreeParser t)
throws InvalidPathException {
boolean isWindows = SystemReader.getInstance().isWindows();
boolean isOSX = SystemReader.getInstance().isMacOS();
boolean ignCase = isOSX || isWindows;
@@ -1171,23 +1177,29 @@ public class DirCacheCheckout {
int start = ptr;
while (ptr < end) {
if (raw[ptr] == '/')
return false;
throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
"/", t.getEntryPathString()); //$NON-NLS-1$
if (isWindows) {
if (raw[ptr] == '\\')
return false;
throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
"\\", t.getEntryPathString()); //$NON-NLS-1$
if (raw[ptr] == ':')
return false;
throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
":", t.getEntryPathString()); //$NON-NLS-1$
}
ptr++;
}
// '.' and '.'' are invalid here
// '.' and '..' are invalid here
if (ptr - start == 1) {
if (raw[start] == '.')
return false;
throw new InvalidPathException(t.getEntryPathString());
} else if (ptr - start == 2) {
if (raw[start] == '.')
if (raw[start + 1] == '.')
return false;
throw new InvalidPathException(t.getEntryPathString());
} else if (ptr - start == 4) {
// .git (possibly case insensitive) is disallowed
if (raw[start] == '.')
@@ -1196,15 +1208,24 @@ public class DirCacheCheckout {
|| (ignCase && raw[start + 2] == 'I'))
if (raw[start + 3] == 't'
|| (ignCase && raw[start + 3] == 'T'))
return false;
throw new InvalidPathException(
t.getEntryPathString());
}
if (isWindows) {
// Space or period at end of file name is ignored by Windows.
// Treat this as a bad path for now. We may want to handle
// this as case insensitivity in the future.
if (ptr > 0)
if (raw[ptr - 1] == '.' || raw[ptr - 1] == ' ')
return false;
if (ptr > 0) {
if (raw[ptr - 1] == '.')
throw new InvalidPathException(
JGitText.get().invalidPathPeriodAtEndWindows,
t.getEntryPathString());
if (raw[ptr - 1] == ' ')
throw new InvalidPathException(
JGitText.get().invalidPathSpaceAtEndWindows,
t.getEntryPathString());
}

int i;
// Bad names, eliminate suffix first
for (i = start; i < ptr; ++i)
@@ -1222,13 +1243,14 @@ public class DirCacheCheckout {
break;
}
if (k == len)
return false;
throw new InvalidPathException(
JGitText.get().invalidPathReservedOnWindows,
RawParseUtils.decode(forbidden[j]), t
.getEntryPathString());
}
}
}
}

return true;
}

private static byte toUpper(byte b) {

+ 5
- 1
org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java View File

@@ -60,6 +60,10 @@ public class InvalidPathException extends IllegalArgumentException {
* @param path
*/
public InvalidPathException(String path) {
super(MessageFormat.format(JGitText.get().invalidPath, path));
this(JGitText.get().invalidPath, path);
}

InvalidPathException(String messagePattern, Object... arguments) {
super(MessageFormat.format(messagePattern, arguments));
}
}

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java View File

@@ -198,6 +198,7 @@ public class IgnoreRule {
}

} else {
matcher.reset();
matcher.append(target);
if (matcher.isMatch())
return true;

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

@@ -332,6 +332,10 @@ public class JGitText extends TranslationBundle {
/***/ public String invalidOldIdSent;
/***/ public String invalidPacketLineHeader;
/***/ public String invalidPath;
/***/ public String invalidPathContainsSeparator;
/***/ public String invalidPathPeriodAtEndWindows;
/***/ public String invalidPathSpaceAtEndWindows;
/***/ public String invalidPathReservedOnWindows;
/***/ public String invalidReflogRevision;
/***/ public String invalidRefName;
/***/ public String invalidRemote;

+ 4
- 0
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java View File

@@ -556,5 +556,9 @@ public final class DfsBlockCache {
hot = true;
return v;
}

boolean has() {
return value != null;
}
}
}

+ 1
- 1
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java View File

@@ -191,7 +191,7 @@ public final class DfsPackFile {
*/
public boolean isIndexLoaded() {
DfsBlockCache.Ref<PackIndex> idxref = index;
return idxref != null && idxref.get() != null;
return idxref != null && idxref.has();
}

/** @return bytes cached in memory for this pack, excluding the index. */

+ 42
- 19
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java View File

@@ -97,6 +97,7 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.GitDateParser;
import org.eclipse.jgit.util.SystemReader;

/**
* A garbage collector for git {@link FileRepository}. Instances of this class
@@ -175,21 +176,9 @@ public class GC {
*
* @param oldPacks
* @param newPacks
* @param ignoreErrors
* <code>true</code> if we should ignore the fact that a certain
* pack files or index files couldn't be deleted.
* <code>false</code> if an exception should be thrown in such
* cases
* @throws IOException
* if a pack file couldn't be deleted and
* <code>ignoreErrors</code> is set to <code>false</code>
*/
private void deleteOldPacks(Collection<PackFile> oldPacks,
Collection<PackFile> newPacks, boolean ignoreErrors)
throws IOException {
int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING;
if (ignoreErrors)
deleteOptions |= FileUtils.IGNORE_ERRORS;
Collection<PackFile> newPacks) {
oldPackLoop: for (PackFile oldPack : oldPacks) {
String oldName = oldPack.getPackName();
// check whether an old pack file is also among the list of new
@@ -200,10 +189,7 @@ public class GC {

if (!oldPack.shouldBeKept()) {
oldPack.close();
for (PackExt ext : PackExt.values()) {
File f = nameFor(oldName, "." + ext.getExtension()); //$NON-NLS-1$
FileUtils.delete(f, deleteOptions);
}
prunePack(oldName);
}
}
// close the complete object database. Thats my only chance to force
@@ -211,6 +197,42 @@ public class GC {
repo.getObjectDatabase().close();
}

/**
* Delete files associated with a single pack file. First try to delete the
* ".pack" file because on some platforms the ".pack" file may be locked and
* can't be deleted. In such a case it is better to detect this early and
* give up on deleting files for this packfile. Otherwise we may delete the
* ".index" file and when failing to delete the ".pack" file we are left
* with a ".pack" file without a ".index" file.
*
* @param packName
*/
private void prunePack(String packName) {
PackExt[] extensions = PackExt.values();
try {
// Delete the .pack file first and if this fails give up on deleting
// the other files
int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING;
for (PackExt ext : extensions)
if (PackExt.PACK.equals(ext)) {
File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$
FileUtils.delete(f, deleteOptions);
break;
}
// The .pack file has been deleted. Delete as many as the other
// files as you can.
deleteOptions |= FileUtils.IGNORE_ERRORS;
for (PackExt ext : extensions) {
if (!PackExt.PACK.equals(ext)) {
File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$
FileUtils.delete(f, deleteOptions);
}
}
} catch (IOException e) {
// Deletion of the .pack file failed. Silently return.
}
}

/**
* Like "git prune-packed" this method tries to prune all loose objects
* which can be found in packs. If certain objects can't be pruned (e.g.
@@ -286,7 +308,8 @@ public class GC {
ConfigConstants.CONFIG_KEY_PRUNEEXPIRE);
if (pruneExpireStr == null)
pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
expire = GitDateParser.parse(pruneExpireStr, null);
expire = GitDateParser.parse(pruneExpireStr, null, SystemReader
.getInstance().getLocale());
expireAgeMillis = -1;
}
if (expire != null)
@@ -533,7 +556,7 @@ public class GC {
if (rest != null)
ret.add(rest);
}
deleteOldPacks(toBeDeleted, ret, true);
deleteOldPacks(toBeDeleted, ret);
prunePacked();

lastPackedRefs = refsBefore;

+ 4
- 4
org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java View File

@@ -100,7 +100,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
&& ref[7] == ' ';
}

private static File getSymRef(File workTree, File dotGit)
private static File getSymRef(File workTree, File dotGit, FS fs)
throws IOException {
byte[] content = IO.readFully(dotGit);
if (!isSymRef(content))
@@ -116,7 +116,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath()));

String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd);
File gitdirFile = new File(gitdirPath);
File gitdirFile = fs.resolve(workTree, gitdirPath);
if (gitdirFile.isAbsolute())
return gitdirFile;
else
@@ -516,7 +516,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
break;
} else if (dir.isFile())
try {
setGitDir(getSymRef(current, dir));
setGitDir(getSymRef(current, dir, tryFS));
break;
} catch (IOException ignored) {
// Continue searching if gitdir ref isn't found
@@ -597,7 +597,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
if (!dotGit.isFile())
setGitDir(dotGit);
else
setGitDir(getSymRef(getWorkTree(), dotGit));
setGitDir(getSymRef(getWorkTree(), dotGit, safeFS()));
}
}


+ 2
- 2
org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java View File

@@ -435,7 +435,7 @@ public class Config {
}

/**
* Get string value
* Get string value or null if not found.
*
* @param section
* the section
@@ -443,7 +443,7 @@ public class Config {
* the subsection for the value
* @param name
* the key name
* @return a String value from git config.
* @return a String value from the config, <code>null</code> if not found
*/
public String getString(final String section, String subsection,
final String name) {

+ 15
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java View File

@@ -77,6 +77,13 @@ public class ConfigConstants {
/** The "submodule" section */
public static final String CONFIG_SUBMODULE_SECTION = "submodule";

/**
* The "rebase" section
*
* @since 3.2
*/
public static final String CONFIG_REBASE_SECTION = "rebase";

/** The "gc" section */
public static final String CONFIG_GC_SECTION = "gc";

@@ -136,6 +143,14 @@ public class ConfigConstants {

/** The "autosetuprebase" key */
public static final String CONFIG_KEY_AUTOSETUPREBASE = "autosetuprebase";

/**
* The "autostash" key
*
* @since 3.2
*/
public static final String CONFIG_KEY_AUTOSTASH = "autostash";

/** The "name" key */
public static final String CONFIG_KEY_NAME = "name";


+ 4
- 3
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java View File

@@ -156,9 +156,10 @@ public abstract class ObjectDatabase {
* @param objectId
* identity of the object to open.
* @param typeHint
* hint about the type of object being requested;
* {@link ObjectReader#OBJ_ANY} if the object type is not known,
* or does not matter to the caller.
* hint about the type of object being requested, e.g.
* {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if
* the object type is not known, or does not matter to the
* caller.
* @return a {@link ObjectLoader} for accessing the object.
* @throws MissingObjectException
* the object does not exist.

+ 9
- 9
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java View File

@@ -194,9 +194,9 @@ public abstract class ObjectReader {
* @param objectId
* identity of the object to test for existence of.
* @param typeHint
* hint about the type of object being requested;
* {@link #OBJ_ANY} if the object type is not known, or does not
* matter to the caller.
* hint about the type of object being requested, e.g.
* {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object
* type is not known, or does not matter to the caller.
* @return true if the specified object is stored in this database.
* @throws IncorrectObjectTypeException
* typeHint was not OBJ_ANY, and the object's actual type does
@@ -235,9 +235,9 @@ public abstract class ObjectReader {
* @param objectId
* identity of the object to open.
* @param typeHint
* hint about the type of object being requested;
* {@link #OBJ_ANY} if the object type is not known, or does not
* matter to the caller.
* hint about the type of object being requested, e.g.
* {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object
* type is not known, or does not matter to the caller.
* @return a {@link ObjectLoader} for accessing the object.
* @throws MissingObjectException
* the object does not exist.
@@ -323,9 +323,9 @@ public abstract class ObjectReader {
* @param objectId
* identity of the object to open.
* @param typeHint
* hint about the type of object being requested;
* {@link #OBJ_ANY} if the object type is not known, or does not
* matter to the caller.
* hint about the type of object being requested, e.g.
* {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object
* type is not known, or does not matter to the caller.
* @return size of object in bytes.
* @throws MissingObjectException
* the object does not exist.

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

@@ -197,6 +197,8 @@ public class RebaseTodoFile {
}
tokenCount++;
}
if (tokenCount == 2)
return new RebaseTodoLine(action, commit, ""); //$NON-NLS-1$
return null;
}


+ 4
- 3
org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java View File

@@ -257,9 +257,10 @@ public abstract class Repository {
* @param objectId
* identity of the object to open.
* @param typeHint
* hint about the type of object being requested;
* {@link ObjectReader#OBJ_ANY} if the object type is not known,
* or does not matter to the caller.
* hint about the type of object being requested, e.g.
* {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if
* the object type is not known, or does not matter to the
* caller.
* @return a {@link ObjectLoader} for accessing the object.
* @throws MissingObjectException
* the object does not exist.

+ 9
- 20
org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2009, Google Inc.
* Copyright (C) 2008-2013, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -54,8 +54,8 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
@@ -63,7 +63,6 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;

/**
* Instance of a specific {@link MergeStrategy} for a single {@link Repository}.
@@ -186,24 +185,11 @@ public abstract class Merger {
}

/**
* Create an iterator to walk the merge base of two commits.
*
* @param a
* the first commit in {@link #sourceObjects}.
* @param b
* the second commit in {@link #sourceObjects}.
* @return the new iterator
* @throws IncorrectObjectTypeException
* one of the input objects is not a commit.
* @throws IOException
* objects are missing or multiple merge bases were found.
* @since 3.0
* @return the ID of the commit that was used as merge base for merging, or
* null if no merge base was used or it was set manually
* @since 3.2
*/
protected AbstractTreeIterator mergeBase(RevCommit a, RevCommit b)
throws IOException {
RevCommit base = getBaseCommit(a, b);
return (base == null) ? new EmptyTreeIterator() : openTree(base.getTree());
}
public abstract ObjectId getBaseCommitId();

/**
* Return the merge base of two commits.
@@ -217,7 +203,10 @@ public abstract class Merger {
* one of the input objects is not a commit.
* @throws IOException
* objects are missing or multiple merge bases were found.
* @deprecated use {@link #getBaseCommitId()} instead, as that does not
* require walking the commits again
*/
@Deprecated
public RevCommit getBaseCommit(final int aIdx, final int bIdx)
throws IncorrectObjectTypeException,
IOException {

+ 5
- 0
org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java View File

@@ -106,5 +106,10 @@ public class StrategyOneSided extends MergeStrategy {
public ObjectId getResultTreeId() {
return sourceTrees[treeIndex];
}

@Override
public ObjectId getBaseCommitId() {
return null;
}
}
}

+ 20
- 1
org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java View File

@@ -49,14 +49,19 @@ import java.io.IOException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;

/** A merge of 2 trees, using a common base ancestor tree. */
public abstract class ThreeWayMerger extends Merger {
private RevTree baseTree;

private ObjectId baseCommitId;

/**
* Create a new merge instance for a repository.
*
@@ -109,6 +114,11 @@ public abstract class ThreeWayMerger extends Merger {
return super.merge(tips);
}

@Override
public ObjectId getBaseCommitId() {
return baseCommitId;
}

/**
* Create an iterator to walk the merge base.
*
@@ -119,6 +129,15 @@ public abstract class ThreeWayMerger extends Merger {
protected AbstractTreeIterator mergeBase() throws IOException {
if (baseTree != null)
return openTree(baseTree);
return mergeBase(sourceCommits[0], sourceCommits[1]);
RevCommit baseCommit = (baseCommitId != null) ? walk
.parseCommit(baseCommitId) : getBaseCommit(sourceCommits[0],
sourceCommits[1]);
if (baseCommit == null) {
baseCommitId = null;
return new EmptyTreeIterator();
} else {
baseCommitId = baseCommit.toObjectId();
return openTree(baseCommit.getTree());
}
}
}

+ 77
- 15
org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java View File

@@ -49,6 +49,7 @@ import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.eclipse.jgit.internal.JGitText;
@@ -71,23 +72,40 @@ public class GitDateParser {
// Since SimpleDateFormat instances are expensive to instantiate they should
// be cached. Since they are also not threadsafe they are cached using
// ThreadLocal.
private static ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>> formatCache = new ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>>() {
protected Map<ParseableSimpleDateFormat, SimpleDateFormat> initialValue() {
return new HashMap<ParseableSimpleDateFormat, SimpleDateFormat>();
private static ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>> formatCache =
new ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>>() {

protected Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> initialValue() {
return new HashMap<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>();
}
};

// Gets an instance of a SimpleDateFormat. If there is not already an
// appropriate instance in the (ThreadLocal) cache the create one and put in
// into the cache
private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f) {
Map<ParseableSimpleDateFormat, SimpleDateFormat> map = formatCache
// Gets an instance of a SimpleDateFormat for the specified locale. If there
// is not already an appropriate instance in the (ThreadLocal) cache then
// create one and put it into the cache.
private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f,
Locale locale) {
Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> cache = formatCache
.get();
Map<ParseableSimpleDateFormat, SimpleDateFormat> map = cache
.get(locale);
if (map == null) {
map = new HashMap<ParseableSimpleDateFormat, SimpleDateFormat>();
cache.put(locale, map);
return getNewSimpleDateFormat(f, locale, map);
}
SimpleDateFormat dateFormat = map.get(f);
if (dateFormat != null)
return dateFormat;
SimpleDateFormat df = getNewSimpleDateFormat(f, locale, map);
return df;
}

private static SimpleDateFormat getNewSimpleDateFormat(
ParseableSimpleDateFormat f, Locale locale,
Map<ParseableSimpleDateFormat, SimpleDateFormat> map) {
SimpleDateFormat df = SystemReader.getInstance().getSimpleDateFormat(
f.formatStr);
f.formatStr, locale);
map.put(f, df);
return df;
}
@@ -115,9 +133,9 @@ public class GitDateParser {
}

/**
* Parses a string into a {@link Date}. Since this parser also supports
* relative formats (e.g. "yesterday") the caller can specify the reference
* date. These types of strings can be parsed:
* Parses a string into a {@link Date} using the default locale. Since this
* parser also supports relative formats (e.g. "yesterday") the caller can
* specify the reference date. These types of strings can be parsed:
* <ul>
* <li>"never"</li>
* <li>"now"</li>
@@ -151,6 +169,49 @@ public class GitDateParser {
*/
public static Date parse(String dateStr, Calendar now)
throws ParseException {
return parse(dateStr, now, Locale.getDefault());
}

/**
* Parses a string into a {@link Date} using the given locale. Since this
* parser also supports relative formats (e.g. "yesterday") the caller can
* specify the reference date. These types of strings can be parsed:
* <ul>
* <li>"never"</li>
* <li>"now"</li>
* <li>"yesterday"</li>
* <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br>
* Multiple specs can be combined like in "2 weeks 3 days ago". Instead of
* ' ' one can use '.' to seperate the words</li>
* <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li>
* <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li>
* <li>"yyyy-MM-dd"</li>
* <li>"yyyy.MM.dd"</li>
* <li>"MM/dd/yyyy",</li>
* <li>"dd.MM.yyyy"</li>
* <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li>
* <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li>
* </ul>
*
* @param dateStr
* the string to be parsed
* @param now
* the base date which is used for the calculation of relative
* formats. E.g. if baseDate is "25.8.2012" then parsing of the
* string "1 week ago" would result in a date corresponding to
* "18.8.2012". This is used when a JGit command calls this
* parser often but wants a consistent starting point for calls.<br>
* If set to <code>null</code> then the current time will be used
* instead.
* @param locale
* locale to be used to parse the date string
* @return the parsed {@link Date}
* @throws ParseException
* if the given dateStr was not recognized
* @since 3.2
*/
public static Date parse(String dateStr, Calendar now, Locale locale)
throws ParseException {
dateStr = dateStr.trim();
Date ret;

@@ -161,7 +222,7 @@ public class GitDateParser {
return ret;
for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) {
try {
return parse_simple(dateStr, f);
return parse_simple(dateStr, f, locale);
} catch (ParseException e) {
// simply proceed with the next parser
}
@@ -177,9 +238,10 @@ public class GitDateParser {
}

// tries to parse a string with the formats supported by SimpleDateFormat
private static Date parse_simple(String dateStr, ParseableSimpleDateFormat f)
private static Date parse_simple(String dateStr,
ParseableSimpleDateFormat f, Locale locale)
throws ParseException {
SimpleDateFormat dateFormat = getDateFormat(f);
SimpleDateFormat dateFormat = getDateFormat(f, locale);
dateFormat.setLenient(false);
return dateFormat.parse(dateStr);
}

+ 15
- 0
org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java View File

@@ -229,6 +229,21 @@ public abstract class SystemReader {
return new SimpleDateFormat(pattern);
}

/**
* Returns a simple date format instance as specified by the given pattern.
*
* @param pattern
* the pattern as defined in
* {@link SimpleDateFormat#SimpleDateFormat(String)}
* @param locale
* locale to be used for the {@code SimpleDateFormat}
* @return the simple date format
* @since 3.2
*/
public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) {
return new SimpleDateFormat(pattern, locale);
}

/**
* Returns a date/time format instance for the given styles.
*

+ 1
- 1
pom.xml View File

@@ -184,7 +184,7 @@
<commons-compress-version>1.4.1</commons-compress-version>
<osgi-core-version>4.3.1</osgi-core-version>
<servlet-api-version>2.5</servlet-api-version>
<jetty-version>7.6.11.v20130520</jetty-version>
<jetty-version>7.6.14.v20131031</jetty-version>
<clirr-version>2.4</clirr-version>
</properties>


Loading…
Cancel
Save