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

35
org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java

@ -46,8 +46,10 @@
package org.eclipse.jgit.junit; package org.eclipse.jgit.junit;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -124,13 +126,44 @@ public abstract class JGitTestUtil {
// loaded previously // loaded previously
return new File("tst", fileName); 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 { try {
return new File(url.toURI()); 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()); 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() { private static ClassLoader cl() {
return JGitTestUtil.class.getClassLoader(); return JGitTestUtil.class.getClassLoader();
} }

34
org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target

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

34
org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target

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

26
org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java

@ -42,8 +42,11 @@
*/ */
package org.eclipse.jgit.pgm; package org.eclipse.jgit.pgm;
import java.io.File;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.lib.CLIRepositoryTestCase;
import org.eclipse.jgit.util.FileUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -107,6 +110,29 @@ public class CheckoutTest extends CLIRepositoryTestCase {
assertEquals("", execute("git checkout HEAD")); 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) { static private void assertEquals(String expected, String[] actual) {
// if there is more than one line, ignore last one if empty // if there is more than one line, ignore last one if empty
Assert.assertEquals( Assert.assertEquals(

118
org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java

@ -213,4 +213,122 @@ public class StatusTest extends CLIRepositoryTestCase {
"" // "" //
}, execute("git status")); // }, 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
org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties

@ -36,6 +36,8 @@ cantFindGitDirectory=error: can't find git directory
cantWrite=Can't write {0} cantWrite=Can't write {0}
changesNotStagedForCommit=Changes not staged for commit: changesNotStagedForCommit=Changes not staged for commit:
changesToBeCommitted=Changes to be committed: changesToBeCommitted=Changes to be committed:
checkoutConflict=error: Your local changes to the following files would be overwritten by checkout:
checkoutConflictPathLine=\t{0}
commitLabel=commit commitLabel=commit
configFileNotFound=configuration file {0} not found configFileNotFound=configuration file {0} not found
conflictingUsageOf_git_dir_andArguments=conflicting usage of --git-dir and arguments 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_listBothRemoteTrackingAndLocalBranches=list both remote-tracking and local branches
usage_listCreateOrDeleteBranches=List, create, or delete branches usage_listCreateOrDeleteBranches=List, create, or delete branches
usage_logAllPretty=format:%H %ct %P' output=log --all '--pretty=format:%H %ct %P' output 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_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_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. usage_mergeNoFf=Create a merge commit even when the merge resolves as a fast-forward.

7
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2010, 2012 Chris Aniszczyk <caniszczyk@gmail.com> * 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. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * 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.CheckoutCommand;
import org.eclipse.jgit.api.Git; 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.RefAlreadyExistsException;
import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -107,6 +109,11 @@ class Checkout extends TextBuiltin {
} catch (RefAlreadyExistsException e) { } catch (RefAlreadyExistsException e) {
throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, throw die(MessageFormat.format(CLIText.get().branchAlreadyExists,
name)); name));
} catch (CheckoutConflictException e) {
outw.println(CLIText.get().checkoutConflict);
for (String path : e.getConflictingPaths())
outw.println(MessageFormat.format(
CLIText.get().checkoutConflictPathLine, path));
} }
} }
} }

125
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java

@ -50,6 +50,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.StatusCommand; import org.eclipse.jgit.api.StatusCommand;
@ -71,26 +72,134 @@ class Status extends TextBuiltin {
protected final String statusFileListFormatUnmerged = CLIText.get().statusFileListFormatUnmerged; protected final String statusFileListFormatUnmerged = CLIText.get().statusFileListFormatUnmerged;
@Option(name = "--porcelain", usage = "usage_machineReadableOutput")
protected boolean porcelain;
@Option(name = "--", metaVar = "metaVar_path", multiValued = true) @Option(name = "--", metaVar = "metaVar_path", multiValued = true)
protected List<String> filterPaths; protected List<String> filterPaths;
@Override @Override
protected void run() throws Exception { 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 // Print current branch name
final Ref head = db.getRef(Constants.HEAD); final Ref head = db.getRef(Constants.HEAD);
boolean firstHeader = true;
if (head != null && head.isSymbolic()) { if (head != null && head.isSymbolic()) {
String branch = Repository.shortenRefName(head.getLeaf().getName()); String branch = Repository.shortenRefName(head.getLeaf().getName());
outw.println(CLIText.formatLine( outw.println(CLIText.formatLine(MessageFormat.format(
MessageFormat.format(CLIText.get().onBranch, branch))); CLIText.get().onBranch, branch)));
} else } else
outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch)); outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch));
// List changes // List changes
StatusCommand statusCommand = new Git(db).status(); boolean firstHeader = true;
if (filterPaths != null && filterPaths.size() > 0)
for (String path : filterPaths)
statusCommand.addPath(path);
org.eclipse.jgit.api.Status status = statusCommand.call();
Collection<String> added = status.getAdded(); Collection<String> added = status.getAdded();
Collection<String> changed = status.getChanged(); Collection<String> changed = status.getChanged();
Collection<String> removed = status.getRemoved(); Collection<String> removed = status.getRemoved();

3
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java

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

11
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java

@ -49,6 +49,7 @@ import java.util.Properties;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.util.GitDateParser; import org.eclipse.jgit.util.GitDateParser;
import org.eclipse.jgit.util.SystemReader;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -68,7 +69,8 @@ public class GarbageCollectCommandTest extends RepositoryTestCase {
@Test @Test
public void testGConeCommit() throws Exception { 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(); Properties res = git.gc().setExpire(expire).call();
assertTrue(res.size() == 7); 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("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"); writeTrashFile("c.txt", "a couple of words for gc to pack more 3");
git.commit().setAll(true).setMessage("commit3").call(); git.commit().setAll(true).setMessage("commit3").call();
Properties res = git.gc().setExpire(GitDateParser.parse("now", null)) Properties res = git
.call(); .gc()
.setExpire(
GitDateParser.parse("now", null, SystemReader
.getInstance().getLocale())).call();
assertTrue(res.size() == 7); assertTrue(res.size() == 7);
} }
} }

28
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java

@ -45,6 +45,7 @@ package org.eclipse.jgit.api;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import org.eclipse.jgit.api.ListBranchCommand.ListMode; 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.errors.RepositoryNotFoundException;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FileUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -123,4 +125,30 @@ public class GitConstructionTest extends RepositoryTestCase {
// should not get here // 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
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java

@ -56,8 +56,11 @@ import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.errors.InvalidMergeHeadsException; import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
import org.eclipse.jgit.junit.RepositoryTestCase; 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.Constants;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
@ -1532,6 +1535,37 @@ public class MergeCommandTest extends RepositoryTestCase {
assertEquals(MergeStatus.ABORTED, result.getMergeStatus()); 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) { private static void setExecutable(Git git, String path, boolean executable) {
FS.DETECTED.setExecute( FS.DETECTED.setExecute(
new File(git.getRepository().getWorkTree(), path), executable); new File(git.getRepository().getWorkTree(), path), executable);

368
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java

@ -68,9 +68,14 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.dircache.DirCacheCheckout; 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.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent; 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.ReflogEntry;
import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; 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.FileUtils;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.RawParseUtils;
@ -1200,9 +1206,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase // rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master") RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call(); .call();
assertEquals(Status.CONFLICTS, result.getStatus()); assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getConflicts().size()); assertEquals(1, result.getUncommittedChanges().size());
assertEquals("file2", result.getConflicts().get(0)); assertEquals("file2", result.getUncommittedChanges().get(0));
} }
@Test @Test
@ -1233,9 +1239,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
RebaseResult result = git.rebase().setUpstream("refs/heads/master") RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call(); .call();
assertEquals(Status.CONFLICTS, result.getStatus()); assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getConflicts().size()); assertEquals(1, result.getUncommittedChanges().size());
assertEquals("file2", result.getConflicts().get(0)); assertEquals("file2", result.getUncommittedChanges().get(0));
checkFile(uncommittedFile, "uncommitted file2"); checkFile(uncommittedFile, "uncommitted file2");
assertEquals(RepositoryState.SAFE, git.getRepository().getRepositoryState()); assertEquals(RepositoryState.SAFE, git.getRepository().getRepositoryState());
@ -1268,9 +1274,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase // rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master") RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call(); .call();
assertEquals(Status.CONFLICTS, result.getStatus()); assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getConflicts().size()); assertEquals(1, result.getUncommittedChanges().size());
assertEquals(FILE1, result.getConflicts().get(0)); assertEquals(FILE1, result.getUncommittedChanges().get(0));
} }
@Test @Test
@ -1302,9 +1308,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase // rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master") RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call(); .call();
assertEquals(Status.CONFLICTS, result.getStatus()); assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
assertEquals(1, result.getConflicts().size()); assertEquals(1, result.getUncommittedChanges().size());
assertEquals(FILE1, result.getConflicts().get(0)); assertEquals(FILE1, result.getUncommittedChanges().get(0));
} }
@Test @Test
@ -1333,7 +1339,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
writeTrashFile("file0", "unstaged modified file0"); writeTrashFile("file0", "unstaged modified file0");
// rebase // rebase
assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master") assertEquals(Status.UNCOMMITTED_CHANGES,
git.rebase().setUpstream("refs/heads/master")
.call().getStatus()); .call().getStatus());
} }
@ -1371,12 +1378,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase // rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master") RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call(); .call();
assertEquals(Status.FAILED, result.getStatus()); assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
// staged file0 causes DIRTY_INDEX assertEquals(1, result.getUncommittedChanges().size());
assertEquals(1, result.getFailingPaths().size());
assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths()
.get("file0"));
assertEquals("unstaged modified file0", read(file0));
// index shall be unchanged // index shall be unchanged
assertEquals(indexState, indexState(CONTENT)); assertEquals(indexState, indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState()); assertEquals(RepositoryState.SAFE, db.getRepositoryState());
@ -1412,7 +1415,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
writeTrashFile("file0", "unstaged modified file0"); writeTrashFile("file0", "unstaged modified file0");
// rebase // rebase
assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master") assertEquals(Status.UNCOMMITTED_CHANGES,
git.rebase().setUpstream("refs/heads/master")
.call().getStatus()); .call().getStatus());
} }
@ -1453,17 +1457,91 @@ public class RebaseCommandTest extends RepositoryTestCase {
// rebase // rebase
RebaseResult result = git.rebase().setUpstream("refs/heads/master") RebaseResult result = git.rebase().setUpstream("refs/heads/master")
.call(); .call();
assertEquals(Status.FAILED, result.getStatus()); assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus());
// staged file0 causes DIRTY_INDEX // staged file0 causes DIRTY_INDEX
assertEquals(1, result.getFailingPaths().size()); assertEquals(1, result.getUncommittedChanges().size());
assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths()
.get("file0"));
assertEquals("unstaged modified file0", read(file0)); assertEquals("unstaged modified file0", read(file0));
// index shall be unchanged // index shall be unchanged
assertEquals(indexState, indexState(CONTENT)); assertEquals(indexState, indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState()); 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 @Test
public void testRebaseWithUncommittedDelete() throws Exception { public void testRebaseWithUncommittedDelete() throws Exception {
// create file0 + file1, add and commit // create file0 + file1, add and commit
@ -1496,6 +1574,136 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertEquals(RepositoryState.SAFE, db.getRepositoryState()); 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 { private int countPicks() throws IOException {
int count = 0; int count = 0;
File todoFile = getTodoFile(); File todoFile = getTodoFile();
@ -1595,9 +1803,9 @@ public class RebaseCommandTest extends RepositoryTestCase {
// and attempt to rebase // and attempt to rebase
RebaseResult rebaseResult = git.rebase() RebaseResult rebaseResult = git.rebase()
.setUpstream("refs/heads/master").call(); .setUpstream("refs/heads/master").call();
assertEquals(Status.CONFLICTS, rebaseResult.getStatus()); assertEquals(Status.UNCOMMITTED_CHANGES, rebaseResult.getStatus());
assertEquals(1, rebaseResult.getConflicts().size()); assertEquals(1, rebaseResult.getUncommittedChanges().size());
assertEquals(FILE1, rebaseResult.getConflicts().get(0)); assertEquals(FILE1, rebaseResult.getUncommittedChanges().get(0));
checkFile(theFile, "dirty the file"); checkFile(theFile, "dirty the file");
@ -1641,6 +1849,19 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertEquals("2222222", steps.get(1).getCommit().name()); 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 @Test
public void testRebaseShouldNotFailIfUserAddCommentLinesInPrepareSteps() public void testRebaseShouldNotFailIfUserAddCommentLinesInPrepareSteps()
throws Exception { throws Exception {
@ -2237,6 +2458,44 @@ public class RebaseCommandTest extends RepositoryTestCase {
head1Commit.getFullMessage()); 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) @Test(expected = InvalidRebaseStepException.class)
public void testRebaseInteractiveFixupFirstCommitShouldFail() 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() { private File getTodoFile() {
File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO); File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO);
return todoFile; return todoFile;

87
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java

@ -408,6 +408,93 @@ public class StashApplyCommandTest extends RepositoryTestCase {
read(PATH)); 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 @Test
public void workingDirectoryContentMerge() throws Exception { public void workingDirectoryContentMerge() throws Exception {
writeTrashFile(PATH, "content\nmore content\n"); writeTrashFile(PATH, "content\nmore content\n");

12
org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java

@ -340,6 +340,18 @@ public class IgnoreMatcherTest {
assertEquals(r.getPattern(), "/patter?"); 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 * Check for a match. If target ends with "/", match will assume that the
* target is meant to be a directory. * target is meant to be a directory.

741
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java

@ -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
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java

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

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java

@ -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;
import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet; import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet;
import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.AnyObjectId; 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.RevObject;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.transport.PackParser;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java

@ -61,7 +61,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; 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.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test; import org.junit.Test;
public class RefUpdateTest extends SampleDataRepositoryTestCase { public class RefUpdateTest extends SampleDataRepositoryTestCase {

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java

@ -55,13 +55,13 @@ import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.List; import java.util.List;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.CheckoutEntry; import org.eclipse.jgit.lib.CheckoutEntry;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test; import org.junit.Test;
public class ReflogReaderTest extends SampleDataRepositoryTestCase { public class ReflogReaderTest extends SampleDataRepositoryTestCase {

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java

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

22
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java

@ -46,35 +46,35 @@
package org.eclipse.jgit.internal.storage.file; 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.IOException; 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.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test; import org.junit.Test;
public class T0004_PackReaderTest extends SampleDataRepositoryTestCase { public class T0004_PackReaderTest extends SampleDataRepositoryTestCase {
private static final String PACK_NAME = "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f"; private static final String PACK_NAME = "34be9032ac282b11fa9babdc2b2a93ca996c9c2f";
private static final File TEST_PACK = JGitTestUtil.getTestResourceFile(PACK_NAME + ".pack");
@Test @Test
public void test003_lookupCompressedObject() throws IOException { public void test003_lookupCompressedObject() throws IOException {
final PackFile pr;
final ObjectId id; final ObjectId id;
final ObjectLoader or; 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"); id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327");
pr = new PackFile(TEST_PACK, PACK.getBit() | INDEX.getBit());
or = pr.get(new WindowCursor(null), id); or = pr.get(new WindowCursor(null), id);
assertNotNull(or); assertNotNull(or);
assertEquals(Constants.OBJ_TREE, or.getType()); assertEquals(Constants.OBJ_TREE, or.getType());

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java

@ -56,11 +56,11 @@ import java.util.List;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.storage.file.WindowCacheConfig;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.MutableInteger;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java

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

25
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java

@ -467,7 +467,9 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
*3 D F D Y N N Keep *3 D F D Y N N Keep
*4 D F D N N N Conflict *4 D F D N N N Conflict
*5 D F F Y N N Y Keep *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 *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 *7 F D F Y Y N N Update
*8 F D F N Y N N Conflict *8 F D F N Y N N Conflict
*9 F D F Y N N N Update *9 F D F Y N N N Update
@ -540,7 +542,17 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// 5 // 5
doit(mk("DF/DF"), mk("DF"), mk("DF")); doit(mk("DF/DF"), mk("DF"), mk("DF"));
assertRemoved("DF/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 @Test
@ -550,6 +562,19 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
writeTrashFile("DF", "different"); writeTrashFile("DF", "different");
go(); go();
assertRemoved("DF/DF"); 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 @Test

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java

@ -59,9 +59,9 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test; import org.junit.Test;
/** /**

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java

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

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java

@ -55,7 +55,7 @@ import java.io.UnsupportedEncodingException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Test; import org.junit.Test;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java

@ -47,13 +47,13 @@ import static org.junit.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java

@ -52,12 +52,12 @@ import java.io.IOException;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.Test; import org.junit.Test;

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java

@ -47,9 +47,9 @@ import static org.junit.Assert.assertEquals;
import java.util.Arrays; import java.util.Arrays;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit; 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;
import org.eclipse.jgit.util.GitDateFormatter.Format; import org.eclipse.jgit.util.GitDateFormatter.Format;
import org.junit.Before; import org.junit.Before;

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

@ -44,10 +44,13 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package org.eclipse.jgit.junit; package org.eclipse.jgit.test.resources;
import java.io.File; 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. */ /** Test case which includes C Git generated pack files for testing. */
public abstract class SampleDataRepositoryTestCase extends RepositoryTestCase { 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"); final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack");
for (String n : packs) { for (String n : packs) {
copyFile(JGitTestUtil.getTestResourceFile(n + ".pack"), new File(packDir, n + ".pack")); JGitTestUtil.copyTestResource(n + ".pack", new File(packDir, n + ".pack"));
copyFile(JGitTestUtil.getTestResourceFile(n + ".idx"), new File(packDir, n + ".idx")); JGitTestUtil.copyTestResource(n + ".idx", new File(packDir, n + ".idx"));
} }
copyFile(JGitTestUtil.getTestResourceFile("packed-refs"), new File(db JGitTestUtil.copyTestResource("packed-refs",
.getDirectory(), "packed-refs")); new File(db.getDirectory(), "packed-refs"));
} }
} }

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java

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

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java

@ -55,7 +55,6 @@ import java.util.Map;
import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor; 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.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status; import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

2
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java

@ -55,12 +55,12 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.eclipse.jgit.junit.SampleDataRepositoryTestCase;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

6
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserBadlyFormattedTest.java

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

39
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserTest.java

@ -72,7 +72,8 @@ public class GitDateParserTest {
GregorianCalendar cal = new GregorianCalendar(SystemReader GregorianCalendar cal = new GregorianCalendar(SystemReader
.getInstance().getTimeZone(), SystemReader.getInstance() .getInstance().getTimeZone(), SystemReader.getInstance()
.getLocale()); .getLocale());
Date parse = GitDateParser.parse("yesterday", cal); Date parse = GitDateParser.parse("yesterday", cal, SystemReader
.getInstance().getLocale());
cal.add(Calendar.DATE, -1); cal.add(Calendar.DATE, -1);
cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0); cal.set(Calendar.MINUTE, 0);
@ -87,7 +88,8 @@ public class GitDateParserTest {
GregorianCalendar cal = new GregorianCalendar(SystemReader GregorianCalendar cal = new GregorianCalendar(SystemReader
.getInstance().getTimeZone(), SystemReader.getInstance() .getInstance().getTimeZone(), SystemReader.getInstance()
.getLocale()); .getLocale());
Date parse = GitDateParser.parse("never", cal); Date parse = GitDateParser.parse("never", cal, SystemReader
.getInstance().getLocale());
Assert.assertEquals(GitDateParser.NEVER, parse); Assert.assertEquals(GitDateParser.NEVER, parse);
parse = GitDateParser.parse("never", null); parse = GitDateParser.parse("never", null);
Assert.assertEquals(GitDateParser.NEVER, parse); Assert.assertEquals(GitDateParser.NEVER, parse);
@ -104,7 +106,8 @@ public class GitDateParserTest {
.getLocale()); .getLocale());
cal.setTime(refDate); cal.setTime(refDate);
Date parse = GitDateParser.parse("now", cal); Date parse = GitDateParser.parse("now", cal, SystemReader.getInstance()
.getLocale());
Assert.assertEquals(refDate, parse); Assert.assertEquals(refDate, parse);
long t1 = SystemReader.getInstance().getCurrentTime(); long t1 = SystemReader.getInstance().getCurrentTime();
parse = GitDateParser.parse("now", null); parse = GitDateParser.parse("now", null);
@ -123,7 +126,8 @@ public class GitDateParserTest {
.getLocale()); .getLocale());
cal.setTime(refDate); 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); Assert.assertEquals(df.parse("2007-02-07 15:35:00 +0100"), parse);
} }
@ -138,7 +142,8 @@ public class GitDateParserTest {
.getLocale()); .getLocale());
cal.setTime(refDate); 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); Assert.assertEquals(df.parse("2007-02-07 15:35:00 +0100"), parse);
parse = GitDateParser.parse("3 days 2 weeks ago", cal); parse = GitDateParser.parse("3 days 2 weeks ago", cal);
Assert.assertEquals(df.parse("2007-02-04 15:35:00 +0100"), parse); 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"; String dateStr = "2007-02-21 15:35:00 +0100";
Date exp = SystemReader.getInstance() Date exp = SystemReader.getInstance()
.getSimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(dateStr); .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); Assert.assertEquals(exp, parse);
} }
@ -161,7 +167,8 @@ public class GitDateParserTest {
Date exp = SystemReader.getInstance() Date exp = SystemReader.getInstance()
.getSimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z") .getSimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z")
.parse(dateStr); .parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null); Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse); Assert.assertEquals(exp, parse);
} }
@ -170,7 +177,8 @@ public class GitDateParserTest {
String dateStr = "2007-02-21"; String dateStr = "2007-02-21";
Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy-MM-dd") Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy-MM-dd")
.parse(dateStr); .parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null); Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse); Assert.assertEquals(exp, parse);
} }
@ -179,7 +187,8 @@ public class GitDateParserTest {
String dateStr = "2007.02.21"; String dateStr = "2007.02.21";
Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy.MM.dd") Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy.MM.dd")
.parse(dateStr); .parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null); Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse); Assert.assertEquals(exp, parse);
} }
@ -188,7 +197,8 @@ public class GitDateParserTest {
String dateStr = "02/21/2007"; String dateStr = "02/21/2007";
Date exp = SystemReader.getInstance().getSimpleDateFormat("MM/dd/yyyy") Date exp = SystemReader.getInstance().getSimpleDateFormat("MM/dd/yyyy")
.parse(dateStr); .parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null); Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse); Assert.assertEquals(exp, parse);
} }
@ -197,7 +207,8 @@ public class GitDateParserTest {
String dateStr = "21.02.2007"; String dateStr = "21.02.2007";
Date exp = SystemReader.getInstance().getSimpleDateFormat("dd.MM.yyyy") Date exp = SystemReader.getInstance().getSimpleDateFormat("dd.MM.yyyy")
.parse(dateStr); .parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null); Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse); Assert.assertEquals(exp, parse);
} }
@ -207,7 +218,8 @@ public class GitDateParserTest {
Date exp = SystemReader.getInstance() Date exp = SystemReader.getInstance()
.getSimpleDateFormat("EEE MMM dd HH:mm:ss yyyy Z") .getSimpleDateFormat("EEE MMM dd HH:mm:ss yyyy Z")
.parse(dateStr); .parse(dateStr);
Date parse = GitDateParser.parse(dateStr, null); Date parse = GitDateParser.parse(dateStr, null, SystemReader
.getInstance().getLocale());
Assert.assertEquals(exp, parse); Assert.assertEquals(exp, parse);
} }
@ -216,7 +228,8 @@ public class GitDateParserTest {
String dateStr = "Wed Feb 21 15:35:00 2007"; String dateStr = "Wed Feb 21 15:35:00 2007";
Date exp = SystemReader.getInstance() Date exp = SystemReader.getInstance()
.getSimpleDateFormat("EEE MMM dd HH:mm:ss yyyy").parse(dateStr); .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); Assert.assertEquals(exp, parse);
} }
} }

4
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

@ -270,6 +270,10 @@ invalidObject=Invalid {0} {1}:{2}
invalidOldIdSent=invalid old id sent invalidOldIdSent=invalid old id sent
invalidPacketLineHeader=Invalid packet line header: {0} invalidPacketLineHeader=Invalid packet line header: {0}
invalidPath=Invalid path: {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} invalidReflogRevision=Invalid reflog revision: {0}
invalidRefName=Invalid ref name: {0} invalidRefName=Invalid ref name: {0}
invalidRemote=Invalid remote: {0} invalidRemote=Invalid remote: {0}

12
org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java

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

3
org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java

@ -86,9 +86,10 @@ public class BlameCommand extends GitCommand<BlameResult> {
} }
/** /**
* Set file path * Set file path.
* *
* @param filePath * @param filePath
* file path (with <code>/</code> as separator)
* @return this command * @return this command
*/ */
public BlameCommand setFilePath(String filePath) { public BlameCommand setFilePath(String filePath) {

7
org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java

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

2
org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java

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

8
org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java

@ -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 * 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 * 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 * @param only
* path to commit * path to commit (with <code>/</code> as separator)
* @return {@code this} * @return {@code this}
*/ */
public CommitCommand setOnly(String only) { public CommitCommand setOnly(String only) {

20
org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java

@ -124,6 +124,26 @@ public class Git {
return new Git(repo); 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 * Returns a command object to execute a {@code clone} command
* *

6
org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java

@ -282,11 +282,11 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> {
/** /**
* Show only commits that affect any of the specified paths. The path must * 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 * either name a file or a directory exactly and use <code>/</code> (slash)
* wildcards are not supported. * as separator. Note that regex expressions or wildcards are not supported.
* *
* @param path * @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} * @return {@code this}
*/ */
public LogCommand addPath(String path) { public LogCommand addPath(String path) {

6
org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java

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

138
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java

@ -70,6 +70,7 @@ import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
import org.eclipse.jgit.api.errors.RefNotFoundException; 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.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffFormatter; 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.internal.JGitText;
import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId; 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 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 * The available operations
*/ */
@ -257,12 +263,27 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
.resolve(upstreamCommitId)); .resolve(upstreamCommitId));
break; break;
case BEGIN: 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(); RebaseResult res = initFilesAndRewind();
if (stopAfterInitialization) if (stopAfterInitialization)
return RebaseResult.INTERACTIVE_PREPARED_RESULT; return RebaseResult.INTERACTIVE_PREPARED_RESULT;
if (res != null) if (res != null) {
autoStashApply();
return res; return res;
} }
}
if (monitor.isCancelled()) if (monitor.isCancelled())
return abort(RebaseResult.ABORTED_RESULT); return abort(RebaseResult.ABORTED_RESULT);
@ -321,12 +342,63 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} }
return finishRebase(newHead, lastStepWasForward); return finishRebase(newHead, lastStepWasForward);
} catch (CheckoutConflictException cce) { } catch (CheckoutConflictException cce) {
return new RebaseResult(cce.getConflictingPaths()); return RebaseResult.conflicts(cce.getConflictingPaths());
} catch (IOException ioe) { } catch (IOException ioe) {
throw new JGitInternalException(ioe.getMessage(), 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) private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick)
throws IOException, GitAPIException { throws IOException, GitAPIException {
if (Action.COMMENT.equals(step.getAction())) if (Action.COMMENT.equals(step.getAction()))
@ -340,7 +412,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
RevCommit commitToPick = walk.parseCommit(ids.iterator().next()); RevCommit commitToPick = walk.parseCommit(ids.iterator().next());
if (shouldPick) { if (shouldPick) {
if (monitor.isCancelled()) if (monitor.isCancelled())
return new RebaseResult(commitToPick, Status.STOPPED); return RebaseResult.result(Status.STOPPED, commitToPick);
RebaseResult result = cherryPickCommit(commitToPick); RebaseResult result = cherryPickCommit(commitToPick);
if (result != null) if (result != null)
return result; return result;
@ -403,8 +475,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
switch (cherryPickResult.getStatus()) { switch (cherryPickResult.getStatus()) {
case FAILED: case FAILED:
if (operation == Operation.BEGIN) if (operation == Operation.BEGIN)
return abort(new RebaseResult( return abort(RebaseResult.failed(cherryPickResult
cherryPickResult.getFailingPaths())); .getFailingPaths()));
else else
return stop(commitToPick, Status.STOPPED); return stop(commitToPick, Status.STOPPED);
case CONFLICTING: case CONFLICTING:
@ -420,10 +492,13 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} }
private RebaseResult finishRebase(RevCommit newHead, private RebaseResult finishRebase(RevCommit newHead,
boolean lastStepWasForward) throws IOException { boolean lastStepWasForward) throws IOException, GitAPIException {
String headName = rebaseState.readFile(HEAD_NAME); String headName = rebaseState.readFile(HEAD_NAME);
updateHead(headName, newHead, upstreamCommit); updateHead(headName, newHead, upstreamCommit);
boolean stashConflicts = autoStashApply();
FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
if (stashConflicts)
return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
if (lastStepWasForward || newHead == null) if (lastStepWasForward || newHead == null)
return RebaseResult.FAST_FORWARD_RESULT; return RebaseResult.FAST_FORWARD_RESULT;
return RebaseResult.OK_RESULT; return RebaseResult.OK_RESULT;
@ -550,7 +625,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} else { } else {
sb.append("# The ").append(count).append(ordinal) sb.append("# The ").append(count).append(ordinal)
.append(" commit message will be skipped:\n# "); .append(" commit message will be skipped:\n# ");
sb.append(commitToPick.getFullMessage().replaceAll("([\n\r]+)", sb.append(commitToPick.getFullMessage().replaceAll("([\n\r])",
"$1# ")); "$1# "));
} }
// Add the previous message without header (i.e first line) // 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 // Remove cherry pick state file created by CherryPickCommand, it's not
// needed for rebase // needed for rebase
repo.writeCherryPickHead(null); repo.writeCherryPickHead(null);
return new RebaseResult(commitToPick, status); return RebaseResult.result(status, commitToPick);
} }
String toAuthorScript(PersonIdent author) { 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 // we need to store everything into files so that we can implement
// --skip, --continue, and --abort // --skip, --continue, and --abort
Ref head = repo.getRef(Constants.HEAD); Ref head = getHead();
if (head == null || head.getObjectId() == null)
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, Constants.HEAD));
String headName; String headName = getHeadName(head);
if (head.isSymbolic())
headName = head.getTarget().getName();
else
headName = head.getObjectId().getName();
ObjectId headId = head.getObjectId(); ObjectId headId = head.getObjectId();
if (headId == null) if (headId == null)
throw new RefNotFoundException(MessageFormat.format( throw new RefNotFoundException(MessageFormat.format(
@ -845,7 +913,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
Collections.reverse(cherryPickList); Collections.reverse(cherryPickList);
// create the folder for the meta information // create the folder for the meta information
FileUtils.mkdir(rebaseState.getDir()); FileUtils.mkdir(rebaseState.getDir(), true);
repo.writeOrigHead(headId); repo.writeOrigHead(headId);
rebaseState.createFile(REBASE_HEAD, headId.name()); rebaseState.createFile(REBASE_HEAD, headId.name());
@ -881,6 +949,23 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return null; 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() { private boolean isInteractive() {
return interactiveHandler != null; return interactiveHandler != null;
} }
@ -895,10 +980,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
*/ */
public RevCommit tryFastForward(RevCommit newCommit) throws IOException, public RevCommit tryFastForward(RevCommit newCommit) throws IOException,
GitAPIException { GitAPIException {
Ref head = repo.getRef(Constants.HEAD); Ref head = getHead();
if (head == null || head.getObjectId() == null)
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, Constants.HEAD));
ObjectId headId = head.getObjectId(); ObjectId headId = head.getObjectId();
if (headId == null) if (headId == null)
@ -908,11 +990,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
if (walk.isMergedInto(newCommit, headCommit)) if (walk.isMergedInto(newCommit, headCommit))
return newCommit; return newCommit;
String headName; String headName = getHeadName(head);
if (head.isSymbolic())
headName = head.getTarget().getName();
else
headName = head.getObjectId().getName();
return tryFastForward(headName, headCommit, newCommit); 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 { try {
ObjectId origHead = repo.readOrigHead(); ObjectId origHead = repo.readOrigHead();
String commitId = origHead != null ? origHead.name() : null; String commitId = origHead != null ? origHead.name() : null;
@ -1041,9 +1120,12 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
JGitText.get().abortingRebaseFailed); JGitText.get().abortingRebaseFailed);
} }
} }
boolean stashConflicts = autoStashApply();
// cleanup the files // cleanup the files
FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
repo.writeCherryPickHead(null); repo.writeCherryPickHead(null);
if (stashConflicts)
return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
return result; return result;
} finally { } finally {

89
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java

@ -104,6 +104,18 @@ public class RebaseResult {
return false; 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 * Conflicts: checkout of target HEAD failed
*/ */
@ -153,6 +165,18 @@ public class RebaseResult {
public boolean isSuccessful() { public boolean isSuccessful() {
return false; 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( static final RebaseResult INTERACTIVE_PREPARED_RESULT = new RebaseResult(
Status.INTERACTIVE_PREPARED); Status.INTERACTIVE_PREPARED);
static final RebaseResult STASH_APPLY_CONFLICTS_RESULT = new RebaseResult(
Status.STASH_APPLY_CONFLICTS);
private final Status status; private final Status status;
private final RevCommit currentCommit; private final RevCommit currentCommit;
@ -185,21 +212,29 @@ public class RebaseResult {
private List<String> conflicts; private List<String> conflicts;
private List<String> uncommittedChanges;
private RebaseResult(Status status) { private RebaseResult(Status status) {
this.status = status; this.status = status;
currentCommit = null; 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 * @param commit
* current commit * current commit
* @param status * @return the RebaseResult
*/ */
RebaseResult(RevCommit commit, RebaseResult.Status status) { static RebaseResult result(RebaseResult.Status status,
this.status = status; RevCommit commit) {
currentCommit = commit; return new RebaseResult(status, commit);
} }
/** /**
@ -207,11 +242,13 @@ public class RebaseResult {
* *
* @param failingPaths * @param failingPaths
* list of paths causing this rebase to fail * list of paths causing this rebase to fail
* @return the RebaseResult
*/ */
RebaseResult(Map<String, MergeFailureReason> failingPaths) { static RebaseResult failed(
status = Status.FAILED; Map<String, MergeFailureReason> failingPaths) {
currentCommit = null; RebaseResult result = new RebaseResult(Status.FAILED);
this.failingPaths = failingPaths; result.failingPaths = failingPaths;
return result;
} }
/** /**
@ -219,11 +256,26 @@ public class RebaseResult {
* *
* @param conflicts * @param conflicts
* the list of conflicting paths * the list of conflicting paths
* @return the RebaseResult
*/ */
RebaseResult(List<String> conflicts) { static RebaseResult conflicts(List<String> conflicts) {
status = Status.CONFLICTS; RebaseResult result = new RebaseResult(Status.CONFLICTS);
currentCommit = null; result.conflicts = conflicts;
this.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() { public List<String> getConflicts() {
return conflicts; return conflicts;
} }
/**
* @return the list of uncommitted changes if status is
* {@link Status#UNCOMMITTED_CHANGES}
*
* @since 3.2
*/
public List<String> getUncommittedChanges() {
return uncommittedChanges;
}
} }

9
org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java

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

4
org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java

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

3
org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java

@ -104,7 +104,8 @@ public class RmCommand extends GitCommand<DirCache> {
/** /**
* @param filepattern * @param filepattern
* File to remove. * repository-relative path of file to remove (with
* <code>/</code> as separator)
* @return {@code this} * @return {@code this}
*/ */
public RmCommand addFilepattern(String filepattern) { public RmCommand addFilepattern(String filepattern) {

16
org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java

@ -90,6 +90,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
private boolean applyIndex = true; private boolean applyIndex = true;
private boolean ignoreRepositoryState;
/** /**
* Create command to apply the changes of a stashed commit * Create command to apply the changes of a stashed commit
* *
@ -113,6 +115,16 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
return this; 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 { private ObjectId getStashId() throws GitAPIException {
final String revision = stashRef != null ? stashRef : DEFAULT_REF; final String revision = stashRef != null ? stashRef : DEFAULT_REF;
final ObjectId stashId; final ObjectId stashId;
@ -143,7 +155,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
StashApplyFailureException { StashApplyFailureException {
checkCallable(); checkCallable();
if (repo.getRepositoryState() != RepositoryState.SAFE) if (!ignoreRepositoryState
&& repo.getRepositoryState() != RepositoryState.SAFE)
throw new WrongRepositoryStateException(MessageFormat.format( throw new WrongRepositoryStateException(MessageFormat.format(
JGitText.get().stashApplyOnUnsafeRepository, JGitText.get().stashApplyOnUnsafeRepository,
repo.getRepositoryState())); repo.getRepositoryState()));
@ -185,6 +198,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
.newMerger(repo, true); .newMerger(repo, true);
ixMerger.setCommitNames(new String[] { "stashed HEAD", ixMerger.setCommitNames(new String[] { "stashed HEAD",
"HEAD", "stashed index" }); "HEAD", "stashed index" });
ixMerger.setBase(stashHeadCommit);
boolean ok = ixMerger.merge(headCommit, stashIndexCommit); boolean ok = ixMerger.merge(headCommit, stashIndexCommit);
if (ok) { if (ok) {
resetIndex(revWalk resetIndex(revWalk

3
org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java

@ -154,6 +154,7 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
/** /**
* Set the reference to update with the stashed commit id * Set the reference to update with the stashed commit id
* If null, no reference is updated
* <p> * <p>
* This value defaults to {@link Constants#R_STASH} * 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, private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
String refLogMessage) throws IOException { String refLogMessage) throws IOException {
if (ref == null)
return;
Ref currentRef = repo.getRef(ref); Ref currentRef = repo.getRef(ref);
RefUpdate refUpdate = repo.updateRef(ref); RefUpdate refUpdate = repo.updateRef(ref);
refUpdate.setNewObjectId(commitId); refUpdate.setNewObjectId(commitId);

44
org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java

@ -43,6 +43,7 @@
package org.eclipse.jgit.api; package org.eclipse.jgit.api;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -66,19 +67,22 @@ public class Status {
private final boolean clean; private final boolean clean;
private final boolean hasUncommittedChanges;;
/** /**
* @param diff * @param diff
*/ */
public Status(IndexDiff diff) { public Status(IndexDiff diff) {
super(); super();
this.diff = diff; this.diff = diff;
clean = diff.getAdded().isEmpty() // hasUncommittedChanges = !diff.getAdded().isEmpty() //
&& diff.getChanged().isEmpty() // || !diff.getChanged().isEmpty() //
&& diff.getRemoved().isEmpty() // || !diff.getRemoved().isEmpty() //
&& diff.getMissing().isEmpty() // || !diff.getMissing().isEmpty() //
&& diff.getModified().isEmpty() // || !diff.getModified().isEmpty() //
&& diff.getUntracked().isEmpty() // || !diff.getConflicting().isEmpty();
&& diff.getConflicting().isEmpty(); clean = !hasUncommittedChanges //
&& diff.getUntracked().isEmpty();
} }
/** /**
@ -89,6 +93,15 @@ public class Status {
return clean; 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 * @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) * if you call 'git add ...' on a newly created file)
@ -168,4 +181,21 @@ public class Status {
public Set<String> getIgnoredNotInIndex() { public Set<String> getIgnoredNotInIndex() {
return Collections.unmodifiableSet(diff.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;
}
} }

3
org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java

@ -89,7 +89,8 @@ public class StatusCommand extends GitCommand<Status> {
* supported. * supported.
* *
* @param path * @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} * @return {@code this}
* @since 3.1 * @since 3.1
*/ */

1
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java

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

1
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java

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

1
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java

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

1
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java

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

1
org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java

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

68
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java vendored

@ -76,6 +76,7 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.AutoCRLFOutputStream; import org.eclipse.jgit.util.io.AutoCRLFOutputStream;
@ -306,8 +307,7 @@ public class DirCacheCheckout {
void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i, void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
WorkingTreeIterator f) throws IOException { WorkingTreeIterator f) throws IOException {
if (m != null) { if (m != null) {
if (!isValidPath(m)) checkValidPath(m);
throw new InvalidPathException(m.getEntryPathString());
// There is an entry in the merge commit. Means: we want to update // 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 // what's currently in the index and working-tree to that one
if (i == null) { if (i == null) {
@ -522,8 +522,8 @@ public class DirCacheCheckout {
String name = walk.getPathString(); String name = walk.getPathString();
if (m != null && !isValidPath(m)) if (m != null)
throw new InvalidPathException(m.getEntryPathString()); checkValidPath(m);
if (i == null && m == null && h == null) { if (i == null && m == null && h == null) {
// File/Directory conflict case #20 // File/Directory conflict case #20
@ -555,7 +555,9 @@ public class DirCacheCheckout {
* 3 D F D Y N N Keep * 3 D F D Y N N Keep
* 4 D F D N N N Conflict * 4 D F D N N N Conflict
* 5 D F F Y N N Y Keep * 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 * 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 * 7 F D F Y Y N N Update
* 8 F D F N Y N N Conflict * 8 F D F N Y N N Conflict
* 9 F D F Y N N N Update * 9 F D F Y N N N Update
@ -620,7 +622,11 @@ public class DirCacheCheckout {
case 0xF0D: // 18 case 0xF0D: // 18
remove(name); remove(name);
break; 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 case 0xFDD: // 10 11
// TODO: make use of tree extension as soon as available in jgit // TODO: make use of tree extension as soon as available in jgit
// we would like to do something like // we would like to do something like
@ -1151,14 +1157,14 @@ public class DirCacheCheckout {
forbidden[i] = Constants.encodeASCII(list[i]); 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()) for (CanonicalTreeParser i = t; i != null; i = i.getParent())
if (!isValidPathSegment(i)) checkValidPathSegment(i);
return false;
return true;
} }
private static boolean isValidPathSegment(CanonicalTreeParser t) { private static void checkValidPathSegment(CanonicalTreeParser t)
throws InvalidPathException {
boolean isWindows = SystemReader.getInstance().isWindows(); boolean isWindows = SystemReader.getInstance().isWindows();
boolean isOSX = SystemReader.getInstance().isMacOS(); boolean isOSX = SystemReader.getInstance().isMacOS();
boolean ignCase = isOSX || isWindows; boolean ignCase = isOSX || isWindows;
@ -1171,23 +1177,29 @@ public class DirCacheCheckout {
int start = ptr; int start = ptr;
while (ptr < end) { while (ptr < end) {
if (raw[ptr] == '/') if (raw[ptr] == '/')
return false; throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
"/", t.getEntryPathString()); //$NON-NLS-1$
if (isWindows) { if (isWindows) {
if (raw[ptr] == '\\') if (raw[ptr] == '\\')
return false; throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
"\\", t.getEntryPathString()); //$NON-NLS-1$
if (raw[ptr] == ':') if (raw[ptr] == ':')
return false; throw new InvalidPathException(
JGitText.get().invalidPathContainsSeparator,
":", t.getEntryPathString()); //$NON-NLS-1$
} }
ptr++; ptr++;
} }
// '.' and '.'' are invalid here // '.' and '..' are invalid here
if (ptr - start == 1) { if (ptr - start == 1) {
if (raw[start] == '.') if (raw[start] == '.')
return false; throw new InvalidPathException(t.getEntryPathString());
} else if (ptr - start == 2) { } else if (ptr - start == 2) {
if (raw[start] == '.') if (raw[start] == '.')
if (raw[start + 1] == '.') if (raw[start + 1] == '.')
return false; throw new InvalidPathException(t.getEntryPathString());
} else if (ptr - start == 4) { } else if (ptr - start == 4) {
// .git (possibly case insensitive) is disallowed // .git (possibly case insensitive) is disallowed
if (raw[start] == '.') if (raw[start] == '.')
@ -1196,15 +1208,24 @@ public class DirCacheCheckout {
|| (ignCase && raw[start + 2] == 'I')) || (ignCase && raw[start + 2] == 'I'))
if (raw[start + 3] == 't' if (raw[start + 3] == 't'
|| (ignCase && raw[start + 3] == 'T')) || (ignCase && raw[start + 3] == 'T'))
return false; throw new InvalidPathException(
t.getEntryPathString());
} }
if (isWindows) { if (isWindows) {
// Space or period at end of file name is ignored by Windows. // 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 // Treat this as a bad path for now. We may want to handle
// this as case insensitivity in the future. // this as case insensitivity in the future.
if (ptr > 0) if (ptr > 0) {
if (raw[ptr - 1] == '.' || raw[ptr - 1] == ' ') if (raw[ptr - 1] == '.')
return false; throw new InvalidPathException(
JGitText.get().invalidPathPeriodAtEndWindows,
t.getEntryPathString());
if (raw[ptr - 1] == ' ')
throw new InvalidPathException(
JGitText.get().invalidPathSpaceAtEndWindows,
t.getEntryPathString());
}
int i; int i;
// Bad names, eliminate suffix first // Bad names, eliminate suffix first
for (i = start; i < ptr; ++i) for (i = start; i < ptr; ++i)
@ -1222,13 +1243,14 @@ public class DirCacheCheckout {
break; break;
} }
if (k == len) 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) { private static byte toUpper(byte b) {

6
org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java vendored

@ -60,6 +60,10 @@ public class InvalidPathException extends IllegalArgumentException {
* @param path * @param path
*/ */
public InvalidPathException(String 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
org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java

@ -198,6 +198,7 @@ public class IgnoreRule {
} }
} else { } else {
matcher.reset();
matcher.append(target); matcher.append(target);
if (matcher.isMatch()) if (matcher.isMatch())
return true; return true;

4
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

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

4
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java

@ -556,5 +556,9 @@ public final class DfsBlockCache {
hot = true; hot = true;
return v; return v;
} }
boolean has() {
return value != null;
}
} }
} }

2
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java

@ -191,7 +191,7 @@ public final class DfsPackFile {
*/ */
public boolean isIndexLoaded() { public boolean isIndexLoaded() {
DfsBlockCache.Ref<PackIndex> idxref = index; 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. */ /** @return bytes cached in memory for this pack, excluding the index. */

61
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java

@ -97,6 +97,7 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.GitDateParser; import org.eclipse.jgit.util.GitDateParser;
import org.eclipse.jgit.util.SystemReader;
/** /**
* A garbage collector for git {@link FileRepository}. Instances of this class * A garbage collector for git {@link FileRepository}. Instances of this class
@ -175,21 +176,9 @@ public class GC {
* *
* @param oldPacks * @param oldPacks
* @param newPacks * @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, private void deleteOldPacks(Collection<PackFile> oldPacks,
Collection<PackFile> newPacks, boolean ignoreErrors) Collection<PackFile> newPacks) {
throws IOException {
int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING;
if (ignoreErrors)
deleteOptions |= FileUtils.IGNORE_ERRORS;
oldPackLoop: for (PackFile oldPack : oldPacks) { oldPackLoop: for (PackFile oldPack : oldPacks) {
String oldName = oldPack.getPackName(); String oldName = oldPack.getPackName();
// check whether an old pack file is also among the list of new // check whether an old pack file is also among the list of new
@ -200,10 +189,7 @@ public class GC {
if (!oldPack.shouldBeKept()) { if (!oldPack.shouldBeKept()) {
oldPack.close(); oldPack.close();
for (PackExt ext : PackExt.values()) { prunePack(oldName);
File f = nameFor(oldName, "." + ext.getExtension()); //$NON-NLS-1$
FileUtils.delete(f, deleteOptions);
}
} }
} }
// close the complete object database. Thats my only chance to force // close the complete object database. Thats my only chance to force
@ -211,6 +197,42 @@ public class GC {
repo.getObjectDatabase().close(); 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 * 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. * 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); ConfigConstants.CONFIG_KEY_PRUNEEXPIRE);
if (pruneExpireStr == null) if (pruneExpireStr == null)
pruneExpireStr = PRUNE_EXPIRE_DEFAULT; pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
expire = GitDateParser.parse(pruneExpireStr, null); expire = GitDateParser.parse(pruneExpireStr, null, SystemReader
.getInstance().getLocale());
expireAgeMillis = -1; expireAgeMillis = -1;
} }
if (expire != null) if (expire != null)
@ -533,7 +556,7 @@ public class GC {
if (rest != null) if (rest != null)
ret.add(rest); ret.add(rest);
} }
deleteOldPacks(toBeDeleted, ret, true); deleteOldPacks(toBeDeleted, ret);
prunePacked(); prunePacked();
lastPackedRefs = refsBefore; lastPackedRefs = refsBefore;

8
org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java

@ -100,7 +100,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
&& ref[7] == ' '; && ref[7] == ' ';
} }
private static File getSymRef(File workTree, File dotGit) private static File getSymRef(File workTree, File dotGit, FS fs)
throws IOException { throws IOException {
byte[] content = IO.readFully(dotGit); byte[] content = IO.readFully(dotGit);
if (!isSymRef(content)) if (!isSymRef(content))
@ -116,7 +116,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath())); JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath()));
String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd); String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd);
File gitdirFile = new File(gitdirPath); File gitdirFile = fs.resolve(workTree, gitdirPath);
if (gitdirFile.isAbsolute()) if (gitdirFile.isAbsolute())
return gitdirFile; return gitdirFile;
else else
@ -516,7 +516,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
break; break;
} else if (dir.isFile()) } else if (dir.isFile())
try { try {
setGitDir(getSymRef(current, dir)); setGitDir(getSymRef(current, dir, tryFS));
break; break;
} catch (IOException ignored) { } catch (IOException ignored) {
// Continue searching if gitdir ref isn't found // Continue searching if gitdir ref isn't found
@ -597,7 +597,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
if (!dotGit.isFile()) if (!dotGit.isFile())
setGitDir(dotGit); setGitDir(dotGit);
else else
setGitDir(getSymRef(getWorkTree(), dotGit)); setGitDir(getSymRef(getWorkTree(), dotGit, safeFS()));
} }
} }

4
org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java

@ -435,7 +435,7 @@ public class Config {
} }
/** /**
* Get string value * Get string value or null if not found.
* *
* @param section * @param section
* the section * the section
@ -443,7 +443,7 @@ public class Config {
* the subsection for the value * the subsection for the value
* @param name * @param name
* the key 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, public String getString(final String section, String subsection,
final String name) { final String name) {

15
org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java

@ -77,6 +77,13 @@ public class ConfigConstants {
/** The "submodule" section */ /** The "submodule" section */
public static final String CONFIG_SUBMODULE_SECTION = "submodule"; 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 */ /** The "gc" section */
public static final String CONFIG_GC_SECTION = "gc"; public static final String CONFIG_GC_SECTION = "gc";
@ -136,6 +143,14 @@ public class ConfigConstants {
/** The "autosetuprebase" key */ /** The "autosetuprebase" key */
public static final String CONFIG_KEY_AUTOSETUPREBASE = "autosetuprebase"; 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 */ /** The "name" key */
public static final String CONFIG_KEY_NAME = "name"; public static final String CONFIG_KEY_NAME = "name";

7
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java

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

18
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java

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

2
org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java

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

7
org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java

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

29
org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java

@ -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. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * 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.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree; 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.revwalk.filter.RevFilter;
import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
/** /**
* Instance of a specific {@link MergeStrategy} for a single {@link Repository}. * 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. * @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
* @param a * @since 3.2
* 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
*/ */
protected AbstractTreeIterator mergeBase(RevCommit a, RevCommit b) public abstract ObjectId getBaseCommitId();
throws IOException {
RevCommit base = getBaseCommit(a, b);
return (base == null) ? new EmptyTreeIterator() : openTree(base.getTree());
}
/** /**
* Return the merge base of two commits. * Return the merge base of two commits.
@ -217,7 +203,10 @@ public abstract class Merger {
* one of the input objects is not a commit. * one of the input objects is not a commit.
* @throws IOException * @throws IOException
* objects are missing or multiple merge bases were found. * 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) public RevCommit getBaseCommit(final int aIdx, final int bIdx)
throws IncorrectObjectTypeException, throws IncorrectObjectTypeException,
IOException { IOException {

5
org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java

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

21
org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java

@ -49,14 +49,19 @@ import java.io.IOException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
/** A merge of 2 trees, using a common base ancestor tree. */ /** A merge of 2 trees, using a common base ancestor tree. */
public abstract class ThreeWayMerger extends Merger { public abstract class ThreeWayMerger extends Merger {
private RevTree baseTree; private RevTree baseTree;
private ObjectId baseCommitId;
/** /**
* Create a new merge instance for a repository. * Create a new merge instance for a repository.
* *
@ -109,6 +114,11 @@ public abstract class ThreeWayMerger extends Merger {
return super.merge(tips); return super.merge(tips);
} }
@Override
public ObjectId getBaseCommitId() {
return baseCommitId;
}
/** /**
* Create an iterator to walk the merge base. * Create an iterator to walk the merge base.
* *
@ -119,6 +129,15 @@ public abstract class ThreeWayMerger extends Merger {
protected AbstractTreeIterator mergeBase() throws IOException { protected AbstractTreeIterator mergeBase() throws IOException {
if (baseTree != null) if (baseTree != null)
return openTree(baseTree); 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());
}
} }
} }

92
org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java

@ -49,6 +49,7 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
@ -71,23 +72,40 @@ public class GitDateParser {
// Since SimpleDateFormat instances are expensive to instantiate they should // Since SimpleDateFormat instances are expensive to instantiate they should
// be cached. Since they are also not threadsafe they are cached using // be cached. Since they are also not threadsafe they are cached using
// ThreadLocal. // ThreadLocal.
private static ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>> formatCache = new ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>>() { private static ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>> formatCache =
protected Map<ParseableSimpleDateFormat, SimpleDateFormat> initialValue() { new ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>>() {
return new HashMap<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 // Gets an instance of a SimpleDateFormat for the specified locale. If there
// appropriate instance in the (ThreadLocal) cache the create one and put in // is not already an appropriate instance in the (ThreadLocal) cache then
// into the cache // create one and put it into the cache.
private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f) { private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f,
Map<ParseableSimpleDateFormat, SimpleDateFormat> map = formatCache Locale locale) {
Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> cache = formatCache
.get(); .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); SimpleDateFormat dateFormat = map.get(f);
if (dateFormat != null) if (dateFormat != null)
return dateFormat; 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( SimpleDateFormat df = SystemReader.getInstance().getSimpleDateFormat(
f.formatStr); f.formatStr, locale);
map.put(f, df); map.put(f, df);
return df; return df;
} }
@ -115,9 +133,9 @@ public class GitDateParser {
} }
/** /**
* Parses a string into a {@link Date}. Since this parser also supports * Parses a string into a {@link Date} using the default locale. Since this
* relative formats (e.g. "yesterday") the caller can specify the reference * parser also supports relative formats (e.g. "yesterday") the caller can
* date. These types of strings can be parsed: * specify the reference date. These types of strings can be parsed:
* <ul> * <ul>
* <li>"never"</li> * <li>"never"</li>
* <li>"now"</li> * <li>"now"</li>
@ -151,6 +169,49 @@ public class GitDateParser {
*/ */
public static Date parse(String dateStr, Calendar now) public static Date parse(String dateStr, Calendar now)
throws ParseException { 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(); dateStr = dateStr.trim();
Date ret; Date ret;
@ -161,7 +222,7 @@ public class GitDateParser {
return ret; return ret;
for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) { for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) {
try { try {
return parse_simple(dateStr, f); return parse_simple(dateStr, f, locale);
} catch (ParseException e) { } catch (ParseException e) {
// simply proceed with the next parser // simply proceed with the next parser
} }
@ -177,9 +238,10 @@ public class GitDateParser {
} }
// tries to parse a string with the formats supported by SimpleDateFormat // 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 { throws ParseException {
SimpleDateFormat dateFormat = getDateFormat(f); SimpleDateFormat dateFormat = getDateFormat(f, locale);
dateFormat.setLenient(false); dateFormat.setLenient(false);
return dateFormat.parse(dateStr); return dateFormat.parse(dateStr);
} }

15
org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java

@ -229,6 +229,21 @@ public abstract class SystemReader {
return new SimpleDateFormat(pattern); 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. * Returns a date/time format instance for the given styles.
* *

2
pom.xml

@ -184,7 +184,7 @@
<commons-compress-version>1.4.1</commons-compress-version> <commons-compress-version>1.4.1</commons-compress-version>
<osgi-core-version>4.3.1</osgi-core-version> <osgi-core-version>4.3.1</osgi-core-version>
<servlet-api-version>2.5</servlet-api-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> <clirr-version>2.4</clirr-version>
</properties> </properties>

Loading…
Cancel
Save