summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.test
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.test')
-rw-r--r--org.eclipse.jgit.test/META-INF/MANIFEST.MF116
-rw-r--r--org.eclipse.jgit.test/pom.xml2
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PostImage2
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PreImage2
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/XAndY.patch32
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl.patch11
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf.patch11
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d.patch13
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf.patch13
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e.patch14
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf.patch14
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl.patch15
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf.patch15
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e.patch297
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PostImage233
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PreImage209
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl.patch17
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl.patch18
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl.patch17
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PostImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PreImage8
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/commit-graph.v1bin0 -> 1680 bytes
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java37
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilderTest.java82
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoaderTest.java75
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphTest.java255
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriterTest.java113
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/GraphCommitsTest.java119
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java17
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java130
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java173
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java35
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java73
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java39
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1Test.java392
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java37
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UInt24ArrayTest.java79
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/memory/TernarySearchTreeTest.java330
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchConfigTest.java45
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java14
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java5
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java392
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java355
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java48
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java4
66 files changed, 3956 insertions, 125 deletions
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index a68d5e3b99..e2390b3e17 100644
--- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Automatic-Module-Name: org.eclipse.jgit.test
Bundle-SymbolicName: org.eclipse.jgit.test
-Bundle-Version: 6.4.1.qualifier
+Bundle-Version: 6.5.1.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %Bundle-Vendor
Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -16,62 +16,64 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
org.apache.commons.compress.compressors.gzip;version="[1.15.0,2.0)",
org.apache.commons.compress.compressors.xz;version="[1.15.0,2.0)",
org.assertj.core.api;version="[3.14.0,4.0.0)",
- org.eclipse.jgit.annotations;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.api;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.api.errors;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.archive;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.attributes;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.awtui;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.blame;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.diff;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.dircache;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.errors;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.events;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.fnmatch;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.gitrepo;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.hooks;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.ignore;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.ignore.internal;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.diff;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.diffmergetool;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.fsck;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.revwalk;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.storage.dfs;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.storage.file;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.storage.io;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.storage.pack;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.storage.reftable;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.transport.connectivity;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.transport.http;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.transport.parser;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.internal.transport.ssh;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.junit;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.junit.time;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.lfs;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.lib;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.lib.internal;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.logging;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.merge;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.nls;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.notes;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.patch;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.pgm;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.pgm.internal;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.revplot;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.revwalk;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.revwalk.filter;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.storage.file;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.storage.pack;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.submodule;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.transport;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.transport.http;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.transport.resolver;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.treewalk;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.treewalk.filter;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.util;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.util.io;version="[6.4.1,6.5.0)",
- org.eclipse.jgit.util.sha1;version="[6.4.1,6.5.0)",
+ org.eclipse.jgit.annotations;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.api;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.api.errors;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.archive;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.attributes;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.awtui;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.blame;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.diff;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.dircache;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.errors;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.events;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.fnmatch;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.gitrepo;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.hooks;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.ignore;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.ignore.internal;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.diff;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.diffmergetool;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.fsck;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.revwalk;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.storage.commitgraph;version="6.5.1",
+ org.eclipse.jgit.internal.storage.dfs;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.storage.file;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.storage.io;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.storage.memory;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.storage.pack;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.storage.reftable;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.transport.connectivity;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.transport.http;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.transport.parser;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.internal.transport.ssh;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.junit;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.junit.time;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.lfs;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.lib;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.lib.internal;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.logging;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.merge;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.nls;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.notes;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.patch;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.pgm;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.pgm.internal;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.revplot;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.revwalk;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.revwalk.filter;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.storage.file;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.storage.pack;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.submodule;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.transport;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.transport.http;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.transport.resolver;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.treewalk;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.treewalk.filter;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.util;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.util.io;version="[6.5.1,6.6.0)",
+ org.eclipse.jgit.util.sha1;version="[6.5.1,6.6.0)",
org.hamcrest;version="[1.1.0,3.0.0)",
org.hamcrest.collection;version="[1.1.0,3.0.0)",
org.junit;version="[4.13,5.0.0)",
diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml
index 0274fa981a..b7d932a408 100644
--- a/org.eclipse.jgit.test/pom.xml
+++ b/org.eclipse.jgit.test/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>6.4.1-SNAPSHOT</version>
+ <version>6.5.1-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit.test</artifactId>
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PostImage
new file mode 100644
index 0000000000..6b664d90c4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PostImage
@@ -0,0 +1,2 @@
+This file
+should not be changed. \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PreImage
new file mode 100644
index 0000000000..6b664d90c4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Unaffected_PreImage
@@ -0,0 +1,2 @@
+This file
+should not be changed. \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/XAndY.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/XAndY.patch
new file mode 100644
index 0000000000..35950f3d08
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/XAndY.patch
@@ -0,0 +1,32 @@
+diff --git a/X b/X
+index a3648a1..2d44096 100644
+--- a/X
++++ b/X
+@@ -2,2 +2,3 @@ a
+ b
++c
+ d
+@@ -16,4 +17,2 @@ p
+ q
+-r
+-s
+ t
+@@ -22,4 +21,8 @@ v
+ w
+-x
+-y
++0
++1
++2
++3
++4
++5
+ z
+diff --git a/Y b/Y
+index 2e65efe..7898192 100644
+--- a/Y
++++ b/Y
+@@ -1 +1 @@
+-a
+\ No newline at end of file
++a \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl.patch
new file mode 100644
index 0000000000..444f7f7438
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl.patch
@@ -0,0 +1,11 @@
+diff --git a/x_add_nl b/x_add_nl
+index 33a3e0e..71ac1b5 100644
+--- a/x_add_nl
++++ b/x_add_nl
+@@ -5,4 +5,4 @@ d
+ e
+ f
+ g
+-h
+\ No newline at end of file
++h
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PostImage
new file mode 100644
index 0000000000..71ac1b5791
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PreImage
new file mode 100644
index 0000000000..33a3e0ed4d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf.patch
new file mode 100644
index 0000000000..503d34562f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf.patch
@@ -0,0 +1,11 @@
+diff --git a/x_add_nl_crlf b/x_add_nl_crlf
+index 33a3e0e..71ac1b5 100644
+--- a/x_add_nl_crlf
++++ b/x_add_nl_crlf
+@@ -5,4 +5,4 @@ d
+ e
+ f
+ g
+-h
+\ No newline at end of file
++h
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PostImage
new file mode 100644
index 0000000000..95801b09e9
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PreImage
new file mode 100644
index 0000000000..a8a98da26c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_add_nl_crlf_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d.patch
new file mode 100644
index 0000000000..cc9025608c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d.patch
@@ -0,0 +1,13 @@
+diff --git a/x_d b/x_d
+index 33a3e0e..9d2bc43 100644
+--- a/x_d
++++ b/x_d
+@@ -1,7 +1,7 @@
+ a
+ b
+ c
+-d
++D
+ e
+ f
+ g
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PostImage
new file mode 100644
index 0000000000..9d2bc43600
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+D
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PreImage
new file mode 100644
index 0000000000..33a3e0ed4d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf.patch
new file mode 100644
index 0000000000..8ec3f8b105
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf.patch
@@ -0,0 +1,13 @@
+diff --git a/x_d_crlf b/x_d_crlf
+index 33a3e0e..9d2bc43 100644
+--- a/x_d_crlf
++++ b/x_d_crlf
+@@ -1,7 +1,7 @@
+ a
+ b
+ c
+-d
++D
+ e
+ f
+ g
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PostImage
new file mode 100644
index 0000000000..ecae1d61cd
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+D
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PreImage
new file mode 100644
index 0000000000..a8a98da26c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_d_crlf_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e.patch
new file mode 100644
index 0000000000..413e4f88e9
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e.patch
@@ -0,0 +1,14 @@
+diff --git a/x_e b/x_e
+index 33a3e0e..b3ab996 100644
+--- a/x_e
++++ b/x_e
+@@ -2,7 +2,7 @@ a
+ b
+ c
+ d
+-e
++E
+ f
+ g
+ h
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PostImage
new file mode 100644
index 0000000000..b3ab996d14
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+E
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PreImage
new file mode 100644
index 0000000000..33a3e0ed4d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf.patch
new file mode 100644
index 0000000000..e674454220
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf.patch
@@ -0,0 +1,14 @@
+diff --git a/x_e_crlf b/x_e_crlf
+index 33a3e0e..b3ab996 100644
+--- a/x_e_crlf
++++ b/x_e_crlf
+@@ -2,7 +2,7 @@ a
+ b
+ c
+ d
+-e
++E
+ f
+ g
+ h
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PostImage
new file mode 100644
index 0000000000..d327752d12
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+E
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PreImage
new file mode 100644
index 0000000000..a8a98da26c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_e_crlf_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl.patch
new file mode 100644
index 0000000000..34ed246378
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl.patch
@@ -0,0 +1,15 @@
+diff --git a/x_last_rm_nl b/x_last_rm_nl
+index 71ac1b5..b3ab996 100644
+--- a/x_last_rm_nl
++++ b/x_last_rm_nl
+@@ -2,7 +2,7 @@ a
+ b
+ c
+ d
+-e
++E
+ f
+ g
+-h
++h
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PostImage
new file mode 100644
index 0000000000..b3ab996d14
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+E
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PreImage
new file mode 100644
index 0000000000..71ac1b5791
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf.patch
new file mode 100644
index 0000000000..3f74024ec0
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf.patch
@@ -0,0 +1,15 @@
+diff --git a/x_last_rm_nl_crlf b/x_last_rm_nl_crlf
+index 71ac1b5..b3ab996 100644
+--- a/x_last_rm_nl_crlf
++++ b/x_last_rm_nl_crlf
+@@ -2,7 +2,7 @@ a
+ b
+ c
+ d
+-e
++E
+ f
+ g
+-h
++h
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PostImage
new file mode 100644
index 0000000000..d327752d12
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+E
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PreImage
new file mode 100644
index 0000000000..95801b09e9
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/x_last_rm_nl_crlf_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e.patch
new file mode 100644
index 0000000000..f531033845
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e.patch
@@ -0,0 +1,297 @@
+diff --git a/z_e b/z_e
+index 8d8786f..7888356 100644
+--- a/z_e
++++ b/z_e
+@@ -20,6 +20,7 @@
+ package org.jsonschema2pojo.util;
+
+ import java.util.ArrayList;
++import java.util.Collections;
+ import java.util.List;
+ import java.util.regex.Matcher;
+ import java.util.regex.Pattern;
+@@ -36,76 +37,81 @@
+ private static final Pattern UNDERSCORE_PATTERN_1 = Pattern.compile("([A-Z]+)([A-Z][a-z])");
+ private static final Pattern UNDERSCORE_PATTERN_2 = Pattern.compile("([a-z\\d])([A-Z])");
+
+- private List<RuleAndReplacement> plurals = new ArrayList<RuleAndReplacement>();
+- private List<RuleAndReplacement> singulars = new ArrayList<RuleAndReplacement>();
+- private List<String> uncountables = new ArrayList<String>();
++ private final List<RuleAndReplacement> plurals;
++ private final List<RuleAndReplacement> singulars;
++ private final List<String> uncountables;
+
+- private static Inflector instance = new Inflector();
++ private static Inflector instance = createDefaultBuilder().build();
+
+- private Inflector() {
+- // Woo, you can't touch me.
+-
+- initialize();
++ private Inflector(Builder builder) {
++ plurals = Collections.unmodifiableList(builder.plurals);
++ singulars = Collections.unmodifiableList(builder.singulars);
++ uncountables = Collections.unmodifiableList(builder.uncountables);
+ }
+
+- private void initialize() {
+- plural("$", "s");
+- plural("s$", "s");
+- plural("(ax|test)is$", "$1es");
+- plural("(octop|vir)us$", "$1i");
+- plural("(alias|status)$", "$1es");
+- plural("(bu)s$", "$1es");
+- plural("(buffal|tomat)o$", "$1oes");
+- plural("([ti])um$", "$1a");
+- plural("sis$", "ses");
+- plural("(?:([^f])fe|([lr])f)$", "$1$2ves");
+- plural("(hive)$", "$1s");
+- plural("([^aeiouy]|qu)y$", "$1ies");
+- plural("([^aeiouy]|qu)ies$", "$1y");
+- plural("(x|ch|ss|sh)$", "$1es");
+- plural("(matr|vert|ind)ix|ex$", "$1ices");
+- plural("([m|l])ouse$", "$1ice");
+- plural("(ox)$", "$1en");
+- plural("(quiz)$", "$1zes");
++ public static Inflector.Builder createDefaultBuilder()
++ {
++ Builder builder = builder();
+
+- singular("s$", "");
+- singular("(n)ews$", "$1ews");
+- singular("([ti])a$", "$1um");
+- singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
+- singular("(^analy)ses$", "$1sis");
+- singular("([^f])ves$", "$1fe");
+- singular("(hive)s$", "$1");
+- singular("(tive)s$", "$1");
+- singular("([lr])ves$", "$1f");
+- singular("([^aeiouy]|qu)ies$", "$1y");
+- singular("(s)eries$", "$1eries");
+- singular("(m)ovies$", "$1ovie");
+- singular("(x|ch|ss|sh)es$", "$1");
+- singular("([m|l])ice$", "$1ouse");
+- singular("(bus)es$", "$1");
+- singular("(o)es$", "$1");
+- singular("(shoe)s$", "$1");
+- singular("(cris|ax|test)es$", "$1is");
+- singular("([octop|vir])i$", "$1us");
+- singular("(alias|status)es$", "$1");
+- singular("^(ox)en", "$1");
+- singular("(vert|ind)ices$", "$1ex");
+- singular("(matr)ices$", "$1ix");
+- singular("(quiz)zes$", "$1");
+- singular("(ess)$", "$1");
++ builder.plural("$", "s")
++ .plural("s$", "s")
++ .plural("(ax|test)is$", "$1es")
++ .plural("(octop|vir)us$", "$1i")
++ .plural("(alias|status)$", "$1es")
++ .plural("(bu)s$", "$1es")
++ .plural("(buffal|tomat)o$", "$1oes")
++ .plural("([ti])um$", "$1a")
++ .plural("sis$", "ses")
++ .plural("(?:([^f])fe|([lr])f)$", "$1$2ves")
++ .plural("(hive)$", "$1s")
++ .plural("([^aeiouy]|qu)y$", "$1ies")
++ .plural("([^aeiouy]|qu)ies$", "$1y")
++ .plural("(x|ch|ss|sh)$", "$1es")
++ .plural("(matr|vert|ind)ix|ex$", "$1ices")
++ .plural("([m|l])ouse$", "$1ice")
++ .plural("(ox)$", "$1en")
++ .plural("(quiz)$", "$1zes");
+
+- singular("men$", "man");
+- plural("man$", "men");
++ builder.singular("s$", "")
++ .singular("(n)ews$", "$1ews")
++ .singular("([ti])a$", "$1um")
++ .singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis")
++ .singular("(^analy)ses$", "$1sis")
++ .singular("([^f])ves$", "$1fe")
++ .singular("(hive)s$", "$1")
++ .singular("(tive)s$", "$1")
++ .singular("([lr])ves$", "$1f")
++ .singular("([^aeiouy]|qu)ies$", "$1y")
++ .singular("(s)eries$", "$1eries")
++ .singular("(m)ovies$", "$1ovie")
++ .singular("(x|ch|ss|sh)es$", "$1")
++ .singular("([m|l])ice$", "$1ouse")
++ .singular("(bus)es$", "$1")
++ .singular("(o)es$", "$1")
++ .singular("(shoe)s$", "$1")
++ .singular("(cris|ax|test)es$", "$1is")
++ .singular("([octop|vir])i$", "$1us")
++ .singular("(alias|status)es$", "$1")
++ .singular("^(ox)en", "$1")
++ .singular("(vert|ind)ices$", "$1ex")
++ .singular("(matr)ices$", "$1ix")
++ .singular("(quiz)zes$", "$1")
++ .singular("(ess)$", "$1");
+
+- irregular("curve", "curves");
+- irregular("leaf", "leaves");
+- irregular("roof", "rooves");
+- irregular("person", "people");
+- irregular("child", "children");
+- irregular("sex", "sexes");
+- irregular("move", "moves");
++ builder.singular("men$", "man")
++ .plural("man$", "men");
+
+- uncountable(new String[] { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "s" });
++ builder.irregular("curve", "curves")
++ .irregular("leaf", "leaves")
++ .irregular("roof", "rooves")
++ .irregular("person", "people")
++ .irregular("child", "children")
++ .irregular("sex", "sexes")
++ .irregular("move", "moves");
++
++ builder.uncountable(new String[] { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "s" });
++
++ return builder;
+ }
+
+ public static Inflector getInstance() {
+@@ -122,28 +128,27 @@
+ return underscoredWord;
+ }
+
+- public synchronized String pluralize(String word) {
++ public String pluralize(String word) {
+ if (uncountables.contains(word.toLowerCase())) {
+ return word;
+ }
+ return replaceWithFirstRule(word, plurals);
+ }
+
+- public synchronized String singularize(String word) {
++ public String singularize(String word) {
+ if (uncountables.contains(word.toLowerCase())) {
+ return word;
+ }
+ return replaceWithFirstRule(word, singulars);
+ }
+
+- private String replaceWithFirstRule(String word, List<RuleAndReplacement> ruleAndReplacements) {
++ private static String replaceWithFirstRule(String word, List<RuleAndReplacement> ruleAndReplacements) {
+
+ for (RuleAndReplacement rar : ruleAndReplacements) {
+- String rule = rar.getRule();
+ String replacement = rar.getReplacement();
+
+ // Return if we find a match.
+- Matcher matcher = Pattern.compile(rule, Pattern.CASE_INSENSITIVE).matcher(word);
++ Matcher matcher = rar.getPattern().matcher(word);
+ if (matcher.find()) {
+ return matcher.replaceAll(replacement);
+ }
+@@ -161,49 +166,68 @@
+ return tableize(className);
+ }
+
+- private void plural(String rule, String replacement) {
+- plurals.add(0, new RuleAndReplacement(rule, replacement));
++ public static Builder builder()
++ {
++ return new Builder();
+ }
+
+- private void singular(String rule, String replacement) {
+- singulars.add(0, new RuleAndReplacement(rule, replacement));
++ // Ugh, no open structs in Java (not-natively at least).
++ private static class RuleAndReplacement {
++ private final String rule;
++ private final String replacement;
++ private final Pattern pattern;
++
++ public RuleAndReplacement(String rule, String replacement) {
++ this.rule = rule;
++ this.replacement = replacement;
++ this.pattern = Pattern.compile(rule, Pattern.CASE_INSENSITIVE);
++ }
++
++ public String getReplacement() {
++ return replacement;
++ }
++
++ public String getRule() {
++ return rule;
++ }
++
++ public Pattern getPattern() {
++ return pattern;
++ }
+ }
+
+- private void irregular(String singular, String plural) {
+- plural(singular, plural);
+- singular(plural, singular);
+- }
++ public static class Builder
++ {
++ private List<RuleAndReplacement> plurals = new ArrayList<RuleAndReplacement>();
++ private List<RuleAndReplacement> singulars = new ArrayList<RuleAndReplacement>();
++ private List<String> uncountables = new ArrayList<String>();
+
+- private void uncountable(String... words) {
+- for (String word : words) {
+- uncountables.add(word);
++ public Builder plural(String rule, String replacement) {
++ plurals.add(0, new RuleAndReplacement(rule, replacement));
++ return this;
++ }
++
++ public Builder singular(String rule, String replacement) {
++ singulars.add(0, new RuleAndReplacement(rule, replacement));
++ return this;
++ }
++
++ public Builder irregular(String singular, String plural) {
++ plural(singular, plural);
++ singular(plural, singular);
++ return this;
++ }
++
++ public Builder uncountable(String... words) {
++ for (String word : words) {
++ uncountables.add(word);
++ }
++ return this;
++ }
++
++ public Inflector build()
++ {
++ return new Inflector(this);
+ }
+ }
+ }
+-
+-// Ugh, no open structs in Java (not-natively at least).
+-class RuleAndReplacement {
+- private String rule;
+- private String replacement;
+-
+- public RuleAndReplacement(String rule, String replacement) {
+- this.rule = rule;
+- this.replacement = replacement;
+- }
+-
+- public String getReplacement() {
+- return replacement;
+- }
+-
+- public void setReplacement(String replacement) {
+- this.replacement = replacement;
+- }
+-
+- public String getRule() {
+- return rule;
+- }
+-
+- public void setRule(String rule) {
+- this.rule = rule;
+- }
+-}
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PostImage
new file mode 100644
index 0000000000..7888356948
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PostImage
@@ -0,0 +1,233 @@
+/**
+ * Copyright © 2007 Chu Yeow Cheah
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copied verbatim from http://dzone.com/snippets/java-inflections, used
+ * and licensed with express permission from the author Chu Yeow Cheah.
+ */
+
+package org.jsonschema2pojo.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Transforms words (from singular to plural, from camelCase to under_score,
+ * etc.). I got bored of doing Real Work...
+ *
+ * @author chuyeow
+ */
+public class Inflector {
+
+ // Pfft, can't think of a better name, but this is needed to avoid the price of initializing the pattern on each call.
+ private static final Pattern UNDERSCORE_PATTERN_1 = Pattern.compile("([A-Z]+)([A-Z][a-z])");
+ private static final Pattern UNDERSCORE_PATTERN_2 = Pattern.compile("([a-z\\d])([A-Z])");
+
+ private final List<RuleAndReplacement> plurals;
+ private final List<RuleAndReplacement> singulars;
+ private final List<String> uncountables;
+
+ private static Inflector instance = createDefaultBuilder().build();
+
+ private Inflector(Builder builder) {
+ plurals = Collections.unmodifiableList(builder.plurals);
+ singulars = Collections.unmodifiableList(builder.singulars);
+ uncountables = Collections.unmodifiableList(builder.uncountables);
+ }
+
+ public static Inflector.Builder createDefaultBuilder()
+ {
+ Builder builder = builder();
+
+ builder.plural("$", "s")
+ .plural("s$", "s")
+ .plural("(ax|test)is$", "$1es")
+ .plural("(octop|vir)us$", "$1i")
+ .plural("(alias|status)$", "$1es")
+ .plural("(bu)s$", "$1es")
+ .plural("(buffal|tomat)o$", "$1oes")
+ .plural("([ti])um$", "$1a")
+ .plural("sis$", "ses")
+ .plural("(?:([^f])fe|([lr])f)$", "$1$2ves")
+ .plural("(hive)$", "$1s")
+ .plural("([^aeiouy]|qu)y$", "$1ies")
+ .plural("([^aeiouy]|qu)ies$", "$1y")
+ .plural("(x|ch|ss|sh)$", "$1es")
+ .plural("(matr|vert|ind)ix|ex$", "$1ices")
+ .plural("([m|l])ouse$", "$1ice")
+ .plural("(ox)$", "$1en")
+ .plural("(quiz)$", "$1zes");
+
+ builder.singular("s$", "")
+ .singular("(n)ews$", "$1ews")
+ .singular("([ti])a$", "$1um")
+ .singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis")
+ .singular("(^analy)ses$", "$1sis")
+ .singular("([^f])ves$", "$1fe")
+ .singular("(hive)s$", "$1")
+ .singular("(tive)s$", "$1")
+ .singular("([lr])ves$", "$1f")
+ .singular("([^aeiouy]|qu)ies$", "$1y")
+ .singular("(s)eries$", "$1eries")
+ .singular("(m)ovies$", "$1ovie")
+ .singular("(x|ch|ss|sh)es$", "$1")
+ .singular("([m|l])ice$", "$1ouse")
+ .singular("(bus)es$", "$1")
+ .singular("(o)es$", "$1")
+ .singular("(shoe)s$", "$1")
+ .singular("(cris|ax|test)es$", "$1is")
+ .singular("([octop|vir])i$", "$1us")
+ .singular("(alias|status)es$", "$1")
+ .singular("^(ox)en", "$1")
+ .singular("(vert|ind)ices$", "$1ex")
+ .singular("(matr)ices$", "$1ix")
+ .singular("(quiz)zes$", "$1")
+ .singular("(ess)$", "$1");
+
+ builder.singular("men$", "man")
+ .plural("man$", "men");
+
+ builder.irregular("curve", "curves")
+ .irregular("leaf", "leaves")
+ .irregular("roof", "rooves")
+ .irregular("person", "people")
+ .irregular("child", "children")
+ .irregular("sex", "sexes")
+ .irregular("move", "moves");
+
+ builder.uncountable(new String[] { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "s" });
+
+ return builder;
+ }
+
+ public static Inflector getInstance() {
+ return instance;
+ }
+
+ private String underscore(String camelCasedWord) {
+
+ // Regexes in Java are fucking stupid...
+ String underscoredWord = UNDERSCORE_PATTERN_1.matcher(camelCasedWord).replaceAll("$1_$2");
+ underscoredWord = UNDERSCORE_PATTERN_2.matcher(underscoredWord).replaceAll("$1_$2");
+ underscoredWord = underscoredWord.replace('-', '_').toLowerCase();
+
+ return underscoredWord;
+ }
+
+ public String pluralize(String word) {
+ if (uncountables.contains(word.toLowerCase())) {
+ return word;
+ }
+ return replaceWithFirstRule(word, plurals);
+ }
+
+ public String singularize(String word) {
+ if (uncountables.contains(word.toLowerCase())) {
+ return word;
+ }
+ return replaceWithFirstRule(word, singulars);
+ }
+
+ private static String replaceWithFirstRule(String word, List<RuleAndReplacement> ruleAndReplacements) {
+
+ for (RuleAndReplacement rar : ruleAndReplacements) {
+ String replacement = rar.getReplacement();
+
+ // Return if we find a match.
+ Matcher matcher = rar.getPattern().matcher(word);
+ if (matcher.find()) {
+ return matcher.replaceAll(replacement);
+ }
+ }
+ return word;
+ }
+
+ private String tableize(String className) {
+ return pluralize(underscore(className));
+ }
+
+ private String tableize(Class<?> klass) {
+ // Strip away package name - we only want the 'base' class name.
+ String className = klass.getName().replace(klass.getPackage().getName() + ".", "");
+ return tableize(className);
+ }
+
+ public static Builder builder()
+ {
+ return new Builder();
+ }
+
+ // Ugh, no open structs in Java (not-natively at least).
+ private static class RuleAndReplacement {
+ private final String rule;
+ private final String replacement;
+ private final Pattern pattern;
+
+ public RuleAndReplacement(String rule, String replacement) {
+ this.rule = rule;
+ this.replacement = replacement;
+ this.pattern = Pattern.compile(rule, Pattern.CASE_INSENSITIVE);
+ }
+
+ public String getReplacement() {
+ return replacement;
+ }
+
+ public String getRule() {
+ return rule;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+ }
+
+ public static class Builder
+ {
+ private List<RuleAndReplacement> plurals = new ArrayList<RuleAndReplacement>();
+ private List<RuleAndReplacement> singulars = new ArrayList<RuleAndReplacement>();
+ private List<String> uncountables = new ArrayList<String>();
+
+ public Builder plural(String rule, String replacement) {
+ plurals.add(0, new RuleAndReplacement(rule, replacement));
+ return this;
+ }
+
+ public Builder singular(String rule, String replacement) {
+ singulars.add(0, new RuleAndReplacement(rule, replacement));
+ return this;
+ }
+
+ public Builder irregular(String singular, String plural) {
+ plural(singular, plural);
+ singular(plural, singular);
+ return this;
+ }
+
+ public Builder uncountable(String... words) {
+ for (String word : words) {
+ uncountables.add(word);
+ }
+ return this;
+ }
+
+ public Inflector build()
+ {
+ return new Inflector(this);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PreImage
new file mode 100644
index 0000000000..8d8786f58c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_PreImage
@@ -0,0 +1,209 @@
+/**
+ * Copyright © 2007 Chu Yeow Cheah
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copied verbatim from http://dzone.com/snippets/java-inflections, used
+ * and licensed with express permission from the author Chu Yeow Cheah.
+ */
+
+package org.jsonschema2pojo.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Transforms words (from singular to plural, from camelCase to under_score,
+ * etc.). I got bored of doing Real Work...
+ *
+ * @author chuyeow
+ */
+public class Inflector {
+
+ // Pfft, can't think of a better name, but this is needed to avoid the price of initializing the pattern on each call.
+ private static final Pattern UNDERSCORE_PATTERN_1 = Pattern.compile("([A-Z]+)([A-Z][a-z])");
+ private static final Pattern UNDERSCORE_PATTERN_2 = Pattern.compile("([a-z\\d])([A-Z])");
+
+ private List<RuleAndReplacement> plurals = new ArrayList<RuleAndReplacement>();
+ private List<RuleAndReplacement> singulars = new ArrayList<RuleAndReplacement>();
+ private List<String> uncountables = new ArrayList<String>();
+
+ private static Inflector instance = new Inflector();
+
+ private Inflector() {
+ // Woo, you can't touch me.
+
+ initialize();
+ }
+
+ private void initialize() {
+ plural("$", "s");
+ plural("s$", "s");
+ plural("(ax|test)is$", "$1es");
+ plural("(octop|vir)us$", "$1i");
+ plural("(alias|status)$", "$1es");
+ plural("(bu)s$", "$1es");
+ plural("(buffal|tomat)o$", "$1oes");
+ plural("([ti])um$", "$1a");
+ plural("sis$", "ses");
+ plural("(?:([^f])fe|([lr])f)$", "$1$2ves");
+ plural("(hive)$", "$1s");
+ plural("([^aeiouy]|qu)y$", "$1ies");
+ plural("([^aeiouy]|qu)ies$", "$1y");
+ plural("(x|ch|ss|sh)$", "$1es");
+ plural("(matr|vert|ind)ix|ex$", "$1ices");
+ plural("([m|l])ouse$", "$1ice");
+ plural("(ox)$", "$1en");
+ plural("(quiz)$", "$1zes");
+
+ singular("s$", "");
+ singular("(n)ews$", "$1ews");
+ singular("([ti])a$", "$1um");
+ singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
+ singular("(^analy)ses$", "$1sis");
+ singular("([^f])ves$", "$1fe");
+ singular("(hive)s$", "$1");
+ singular("(tive)s$", "$1");
+ singular("([lr])ves$", "$1f");
+ singular("([^aeiouy]|qu)ies$", "$1y");
+ singular("(s)eries$", "$1eries");
+ singular("(m)ovies$", "$1ovie");
+ singular("(x|ch|ss|sh)es$", "$1");
+ singular("([m|l])ice$", "$1ouse");
+ singular("(bus)es$", "$1");
+ singular("(o)es$", "$1");
+ singular("(shoe)s$", "$1");
+ singular("(cris|ax|test)es$", "$1is");
+ singular("([octop|vir])i$", "$1us");
+ singular("(alias|status)es$", "$1");
+ singular("^(ox)en", "$1");
+ singular("(vert|ind)ices$", "$1ex");
+ singular("(matr)ices$", "$1ix");
+ singular("(quiz)zes$", "$1");
+ singular("(ess)$", "$1");
+
+ singular("men$", "man");
+ plural("man$", "men");
+
+ irregular("curve", "curves");
+ irregular("leaf", "leaves");
+ irregular("roof", "rooves");
+ irregular("person", "people");
+ irregular("child", "children");
+ irregular("sex", "sexes");
+ irregular("move", "moves");
+
+ uncountable(new String[] { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "s" });
+ }
+
+ public static Inflector getInstance() {
+ return instance;
+ }
+
+ private String underscore(String camelCasedWord) {
+
+ // Regexes in Java are fucking stupid...
+ String underscoredWord = UNDERSCORE_PATTERN_1.matcher(camelCasedWord).replaceAll("$1_$2");
+ underscoredWord = UNDERSCORE_PATTERN_2.matcher(underscoredWord).replaceAll("$1_$2");
+ underscoredWord = underscoredWord.replace('-', '_').toLowerCase();
+
+ return underscoredWord;
+ }
+
+ public synchronized String pluralize(String word) {
+ if (uncountables.contains(word.toLowerCase())) {
+ return word;
+ }
+ return replaceWithFirstRule(word, plurals);
+ }
+
+ public synchronized String singularize(String word) {
+ if (uncountables.contains(word.toLowerCase())) {
+ return word;
+ }
+ return replaceWithFirstRule(word, singulars);
+ }
+
+ private String replaceWithFirstRule(String word, List<RuleAndReplacement> ruleAndReplacements) {
+
+ for (RuleAndReplacement rar : ruleAndReplacements) {
+ String rule = rar.getRule();
+ String replacement = rar.getReplacement();
+
+ // Return if we find a match.
+ Matcher matcher = Pattern.compile(rule, Pattern.CASE_INSENSITIVE).matcher(word);
+ if (matcher.find()) {
+ return matcher.replaceAll(replacement);
+ }
+ }
+ return word;
+ }
+
+ private String tableize(String className) {
+ return pluralize(underscore(className));
+ }
+
+ private String tableize(Class<?> klass) {
+ // Strip away package name - we only want the 'base' class name.
+ String className = klass.getName().replace(klass.getPackage().getName() + ".", "");
+ return tableize(className);
+ }
+
+ private void plural(String rule, String replacement) {
+ plurals.add(0, new RuleAndReplacement(rule, replacement));
+ }
+
+ private void singular(String rule, String replacement) {
+ singulars.add(0, new RuleAndReplacement(rule, replacement));
+ }
+
+ private void irregular(String singular, String plural) {
+ plural(singular, plural);
+ singular(plural, singular);
+ }
+
+ private void uncountable(String... words) {
+ for (String word : words) {
+ uncountables.add(word);
+ }
+ }
+}
+
+// Ugh, no open structs in Java (not-natively at least).
+class RuleAndReplacement {
+ private String rule;
+ private String replacement;
+
+ public RuleAndReplacement(String rule, String replacement) {
+ this.rule = rule;
+ this.replacement = replacement;
+ }
+
+ public String getReplacement() {
+ return replacement;
+ }
+
+ public void setReplacement(String replacement) {
+ this.replacement = replacement;
+ }
+
+ public String getRule() {
+ return rule;
+ }
+
+ public void setRule(String rule) {
+ this.rule = rule;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl.patch
new file mode 100644
index 0000000000..0fe4b4593b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl.patch
@@ -0,0 +1,17 @@
+diff --git a/z_e_add_nl b/z_e_add_nl
+index 33a3e0e..274cb0e 100644
+--- a/z_e_add_nl
++++ b/z_e_add_nl
+@@ -2,7 +2,7 @@ a
+ b
+ c
+ d
+-e
+-f
+-g
+-h
+\ No newline at end of file
++E
++F
++G
++H
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PostImage
new file mode 100644
index 0000000000..274cb0e649
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+E
+F
+G
+H
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PreImage
new file mode 100644
index 0000000000..33a3e0ed4d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_add_nl_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl.patch
new file mode 100644
index 0000000000..bce13e463e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl.patch
@@ -0,0 +1,18 @@
+diff --git a/z_e_no_nl b/z_e_no_nl
+index 33a3e0e..234fedc 100644
+--- a/z_e_no_nl
++++ b/z_e_no_nl
+@@ -2,7 +2,7 @@ a
+ b
+ c
+ d
+-e
+-f
+-g
+-h
+\ No newline at end of file
++E
++F
++G
++H
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PostImage
new file mode 100644
index 0000000000..234fedc28e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+E
+F
+G
+H \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PreImage
new file mode 100644
index 0000000000..33a3e0ed4d
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_no_nl_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl.patch
new file mode 100644
index 0000000000..d669706eaa
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl.patch
@@ -0,0 +1,17 @@
+diff --git a/z_e_rm_nl b/z_e_rm_nl
+index 71ac1b5..234fedc 100644
+--- a/z_e_rm_nl
++++ b/z_e_rm_nl
+@@ -2,7 +2,7 @@ a
+ b
+ c
+ d
+-e
+-f
+-g
+-h
++E
++F
++G
++H
+\ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PostImage
new file mode 100644
index 0000000000..234fedc28e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PostImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+E
+F
+G
+H \ No newline at end of file
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PreImage
new file mode 100644
index 0000000000..71ac1b5791
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/z_e_rm_nl_PreImage
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+h
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/commit-graph.v1 b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/commit-graph.v1
new file mode 100644
index 0000000000..941c0a7cec
--- /dev/null
+++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/commit-graph.v1
Binary files differ
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
index 6a84f0a38d..12300b3390 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java
@@ -378,44 +378,59 @@ public class PullCommandTest extends RepositoryTestCase {
}
private enum TestPullMode {
- MERGE, REBASE, REBASE_PREASERVE
+ MERGE, REBASE, REBASE_MERGES
}
@Test
/** global rebase config should be respected */
- public void testPullWithRebasePreserve1Config() throws Exception {
+ public void testPullWithRebaseMerges1Config() throws Exception {
+ Callable<PullResult> setup = () -> {
+ StoredConfig config = dbTarget.getConfig();
+ config.setString("pull", null, "rebase", "merges");
+ config.save();
+ return target.pull().call();
+ };
+ doTestPullWithRebase(setup, TestPullMode.REBASE_MERGES);
+ }
+
+ @Test
+ /**
+ * global rebase config using old "preserve" value which was renamed to
+ * "merges" should be respected to ensure backwards compatibility
+ */
+ public void testPullWithRebaseMerges1ConfigAlias() throws Exception {
Callable<PullResult> setup = () -> {
StoredConfig config = dbTarget.getConfig();
config.setString("pull", null, "rebase", "preserve");
config.save();
return target.pull().call();
};
- doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
+ doTestPullWithRebase(setup, TestPullMode.REBASE_MERGES);
}
@Test
/** the branch-local config should win over the global config */
- public void testPullWithRebasePreserveConfig2() throws Exception {
+ public void testPullWithRebaseMergesConfig2() throws Exception {
Callable<PullResult> setup = () -> {
StoredConfig config = dbTarget.getConfig();
config.setString("pull", null, "rebase", "false");
- config.setString("branch", "master", "rebase", "preserve");
+ config.setString("branch", "master", "rebase", "merges");
config.save();
return target.pull().call();
};
- doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
+ doTestPullWithRebase(setup, TestPullMode.REBASE_MERGES);
}
@Test
/** the branch-local config should be respected */
- public void testPullWithRebasePreserveConfig3() throws Exception {
+ public void testPullWithRebaseMergesConfig3() throws Exception {
Callable<PullResult> setup = () -> {
StoredConfig config = dbTarget.getConfig();
- config.setString("branch", "master", "rebase", "preserve");
+ config.setString("branch", "master", "rebase", "merges");
config.save();
return target.pull().call();
};
- doTestPullWithRebase(setup, TestPullMode.REBASE_PREASERVE);
+ doTestPullWithRebase(setup, TestPullMode.REBASE_MERGES);
}
@Test
@@ -435,7 +450,7 @@ public class PullCommandTest extends RepositoryTestCase {
public void testPullWithRebaseConfig2() throws Exception {
Callable<PullResult> setup = () -> {
StoredConfig config = dbTarget.getConfig();
- config.setString("pull", null, "rebase", "preserve");
+ config.setString("pull", null, "rebase", "merges");
config.setString("branch", "master", "rebase", "true");
config.save();
return target.pull().call();
@@ -543,7 +558,7 @@ public class PullCommandTest extends RepositoryTestCase {
assertEquals(sourceCommit, next.getParent(1));
// since both parents are known do no further checks here
} else {
- if (expectedPullMode == TestPullMode.REBASE_PREASERVE) {
+ if (expectedPullMode == TestPullMode.REBASE_MERGES) {
next = rw.next();
assertEquals(2, next.getParentCount());
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilderTest.java
new file mode 100644
index 0000000000..8ecf5df4e5
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilderTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022, Tencent.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.commitgraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+public class CommitGraphBuilderTest {
+
+ @Test
+ public void testRepeatedChunk() throws Exception {
+ byte[] buffer = new byte[2048];
+
+ CommitGraphBuilder builder1 = CommitGraphBuilder.builder();
+ builder1.addOidFanout(buffer);
+ Exception e1 = assertThrows(CommitGraphFormatException.class, () -> {
+ builder1.addOidFanout(buffer);
+ });
+ assertEquals("commit-graph chunk id 0x4f494446 appears multiple times",
+ e1.getMessage());
+
+ CommitGraphBuilder builder2 = CommitGraphBuilder.builder();
+ builder2.addOidLookUp(buffer);
+ Exception e2 = assertThrows(CommitGraphFormatException.class, () -> {
+ builder2.addOidLookUp(buffer);
+ });
+ assertEquals("commit-graph chunk id 0x4f49444c appears multiple times",
+ e2.getMessage());
+
+ CommitGraphBuilder builder3 = CommitGraphBuilder.builder();
+ builder3.addCommitData(buffer);
+ Exception e3 = assertThrows(CommitGraphFormatException.class, () -> {
+ builder3.addCommitData(buffer);
+ });
+ assertEquals("commit-graph chunk id 0x43444154 appears multiple times",
+ e3.getMessage());
+
+ CommitGraphBuilder builder4 = CommitGraphBuilder.builder();
+ builder4.addExtraList(buffer);
+ Exception e4 = assertThrows(CommitGraphFormatException.class, () -> {
+ builder4.addExtraList(buffer);
+ });
+ assertEquals("commit-graph chunk id 0x45444745 appears multiple times",
+ e4.getMessage());
+ }
+
+ @Test
+ public void testNeededChunk() {
+ byte[] buffer = new byte[2048];
+
+ Exception e1 = assertThrows(CommitGraphFormatException.class, () -> {
+ CommitGraphBuilder.builder().addOidLookUp(buffer)
+ .addCommitData(buffer).build();
+ });
+ assertEquals("commit-graph 0x4f494446 chunk has not been loaded",
+ e1.getMessage());
+
+ Exception e2 = assertThrows(CommitGraphFormatException.class, () -> {
+ CommitGraphBuilder.builder().addOidFanout(buffer)
+ .addCommitData(buffer).build();
+ });
+ assertEquals("commit-graph 0x4f49444c chunk has not been loaded",
+ e2.getMessage());
+
+ Exception e3 = assertThrows(CommitGraphFormatException.class, () -> {
+ CommitGraphBuilder.builder().addOidFanout(buffer)
+ .addOidLookUp(buffer).build();
+ });
+ assertEquals("commit-graph 0x43444154 chunk has not been loaded",
+ e3.getMessage());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoaderTest.java
new file mode 100644
index 0000000000..fd427a117e
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoaderTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022, Tencent.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.commitgraph;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph.CommitData;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+/**
+ * Test CommitGraphLoader by reading the commit-graph file generated by Cgit.
+ */
+public class CommitGraphLoaderTest {
+
+ private CommitGraph commitGraph;
+
+ @Test
+ public void readCommitGraphV1() throws Exception {
+ commitGraph = CommitGraphLoader
+ .open(JGitTestUtil.getTestResourceFile("commit-graph.v1"));
+ assertNotNull(commitGraph);
+ assertEquals(10, commitGraph.getCommitCnt());
+ verifyGraphObjectIndex();
+
+ assertCommitData("85b0176af27fa1640868f061f224d01e0b295f59",
+ new int[] { 5, 6 }, 1670570408L, 3, 0);
+ assertCommitData("d4f7c00aab3f0160168c9e5991abb6194a4e0d9e",
+ new int[] {}, 1670569901L, 1, 1);
+ assertCommitData("4d03aaf9c20c97d6ccdc05cb7f146b1deb6c01d5",
+ new int[] { 5 }, 1670570119L, 3, 2);
+ assertCommitData("a2f409b753880bf83b18bfb433dd340a6185e8be",
+ new int[] { 7 }, 1670569935L, 3, 3);
+ assertCommitData("431343847343979bbe31127ed905a24fed9a636c",
+ new int[] { 3, 2, 8 }, 1670570644L, 4, 4);
+ assertCommitData("c3f745ad8928ef56b5dbf33740fc8ede6b598290",
+ new int[] { 1 }, 1670570106L, 2, 5);
+ assertCommitData("95b12422c8ea4371e54cd58925eeed9d960ff1f0",
+ new int[] { 1 }, 1670570163L, 2, 6);
+ assertCommitData("de0ea882503cdd9c984c0a43238014569a123cac",
+ new int[] { 1 }, 1670569921L, 2, 7);
+ assertCommitData("102c9d6481559b1a113eb66bf55085903de6fb00",
+ new int[] { 6 }, 1670570616L, 3, 8);
+ assertCommitData("b5de2a84867f8ffc6321649dabf8c0680661ec03",
+ new int[] { 7, 5 }, 1670570364L, 3, 9);
+ }
+
+ private void verifyGraphObjectIndex() {
+ for (int i = 0; i < commitGraph.getCommitCnt(); i++) {
+ ObjectId id = commitGraph.getObjectId(i);
+ int pos = commitGraph.findGraphPosition(id);
+ assertEquals(i, pos);
+ }
+ }
+
+ private void assertCommitData(String expectedTree, int[] expectedParents,
+ long expectedCommitTime, int expectedGeneration, int graphPos) {
+ CommitData commitData = commitGraph.getCommitData(graphPos);
+ assertEquals(ObjectId.fromString(expectedTree), commitData.getTree());
+ assertArrayEquals(expectedParents, commitData.getParents());
+ assertEquals(expectedCommitTime, commitData.getCommitTime());
+ assertEquals(expectedGeneration, commitData.getGeneration());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphTest.java
new file mode 100644
index 0000000000..97976564d8
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022, Tencent.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.commitgraph;
+
+import static org.eclipse.jgit.lib.Constants.COMMIT_GENERATION_UNKNOWN;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test writing and then reading the commit-graph.
+ */
+public class CommitGraphTest extends RepositoryTestCase {
+
+ private TestRepository<FileRepository> tr;
+
+ private CommitGraph commitGraph;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ tr = new TestRepository<>(db, new RevWalk(db), mockSystemReader);
+ }
+
+ @Test
+ public void testGraphWithSingleCommit() throws Exception {
+ RevCommit root = commit();
+ writeAndReadCommitGraph(Collections.singleton(root));
+ verifyCommitGraph();
+ assertEquals(1, getGenerationNumber(root));
+ }
+
+ @Test
+ public void testGraphWithManyParents() throws Exception {
+ int parentsNum = 40;
+ RevCommit root = commit();
+
+ RevCommit[] parents = new RevCommit[parentsNum];
+ for (int i = 0; i < parents.length; i++) {
+ parents[i] = commit(root);
+ }
+ RevCommit tip = commit(parents);
+
+ Set<ObjectId> wants = Collections.singleton(tip);
+ writeAndReadCommitGraph(wants);
+ assertEquals(parentsNum + 2, commitGraph.getCommitCnt());
+ verifyCommitGraph();
+
+ assertEquals(1, getGenerationNumber(root));
+ for (RevCommit parent : parents) {
+ assertEquals(2, getGenerationNumber(parent));
+ }
+ assertEquals(3, getGenerationNumber(tip));
+ }
+
+ @Test
+ public void testGraphLinearHistory() throws Exception {
+ int commitNum = 20;
+ RevCommit[] commits = new RevCommit[commitNum];
+ for (int i = 0; i < commitNum; i++) {
+ if (i == 0) {
+ commits[i] = commit();
+ } else {
+ commits[i] = commit(commits[i - 1]);
+ }
+ }
+
+ Set<ObjectId> wants = Collections.singleton(commits[commitNum - 1]);
+ writeAndReadCommitGraph(wants);
+ assertEquals(commitNum, commitGraph.getCommitCnt());
+ verifyCommitGraph();
+ for (int i = 0; i < commitNum; i++) {
+ assertEquals(i + 1, getGenerationNumber(commits[i]));
+ }
+ }
+
+ @Test
+ public void testGraphWithMerges() throws Exception {
+ RevCommit c1 = commit();
+ RevCommit c2 = commit(c1);
+ RevCommit c3 = commit(c2);
+ RevCommit c4 = commit(c1);
+ RevCommit c5 = commit(c4);
+ RevCommit c6 = commit(c1);
+ RevCommit c7 = commit(c6);
+
+ RevCommit m1 = commit(c2, c4);
+ RevCommit m2 = commit(c4, c6);
+ RevCommit m3 = commit(c3, c5, c7);
+
+ Set<ObjectId> wants = new HashSet<>();
+
+ /*
+ * <pre>
+ * current graph structure:
+ * M1
+ * / \
+ * 2 4
+ * |___/
+ * 1
+ * </pre>
+ */
+ wants.add(m1);
+ writeAndReadCommitGraph(wants);
+ assertEquals(4, commitGraph.getCommitCnt());
+ verifyCommitGraph();
+
+ /*
+ * <pre>
+ * current graph structure:
+ * M1 M2
+ * / \ / \
+ * 2 4 6
+ * |___/____/
+ * 1
+ * </pre>
+ */
+ wants.add(m2);
+ writeAndReadCommitGraph(wants);
+ assertEquals(6, commitGraph.getCommitCnt());
+ verifyCommitGraph();
+
+ /*
+ * <pre>
+ * current graph structure:
+ *
+ * __M3___
+ * / | \
+ * 3 M1 5 M2 7
+ * |/ \|/ \|
+ * 2 4 6
+ * |___/____/
+ * 1
+ * </pre>
+ */
+ wants.add(m3);
+ writeAndReadCommitGraph(wants);
+ assertEquals(10, commitGraph.getCommitCnt());
+ verifyCommitGraph();
+
+ /*
+ * <pre>
+ * current graph structure:
+ * 8
+ * |
+ * __M3___
+ * / | \
+ * 3 M1 5 M2 7
+ * |/ \|/ \|
+ * 2 4 6
+ * |___/____/
+ * 1
+ * </pre>
+ */
+ RevCommit c8 = commit(m3);
+ wants.add(c8);
+ writeAndReadCommitGraph(wants);
+ assertEquals(11, commitGraph.getCommitCnt());
+ verifyCommitGraph();
+
+ assertEquals(getGenerationNumber(c1), 1);
+ assertEquals(getGenerationNumber(c2), 2);
+ assertEquals(getGenerationNumber(c4), 2);
+ assertEquals(getGenerationNumber(c6), 2);
+ assertEquals(getGenerationNumber(c3), 3);
+ assertEquals(getGenerationNumber(c5), 3);
+ assertEquals(getGenerationNumber(c7), 3);
+ assertEquals(getGenerationNumber(m1), 3);
+ assertEquals(getGenerationNumber(m2), 3);
+ assertEquals(getGenerationNumber(m3), 4);
+ assertEquals(getGenerationNumber(c8), 5);
+ }
+
+ void writeAndReadCommitGraph(Set<ObjectId> wants) throws Exception {
+ NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+ try (RevWalk walk = new RevWalk(db)) {
+ CommitGraphWriter writer = new CommitGraphWriter(
+ GraphCommits.fromWalk(m, wants, walk));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ writer.write(m, os);
+ InputStream inputStream = new ByteArrayInputStream(
+ os.toByteArray());
+ commitGraph = CommitGraphLoader.read(inputStream);
+ }
+ }
+
+ void verifyCommitGraph() throws Exception {
+ try (RevWalk walk = new RevWalk(db)) {
+ for (int i = 0; i < commitGraph.getCommitCnt(); i++) {
+ ObjectId objId = commitGraph.getObjectId(i);
+
+ // check the objectId index of commit-graph
+ int pos = commitGraph.findGraphPosition(objId);
+ assertEquals(i, pos);
+
+ // check the commit meta of commit-graph
+ CommitGraph.CommitData commit = commitGraph.getCommitData(i);
+ int[] pList = commit.getParents();
+
+ RevCommit expect = walk.lookupCommit(objId);
+ walk.parseBody(expect);
+
+ assertEquals(expect.getCommitTime(), commit.getCommitTime());
+ assertEquals(expect.getTree(), commit.getTree());
+ assertEquals(expect.getParentCount(), pList.length);
+
+ if (pList.length > 0) {
+ ObjectId[] parents = new ObjectId[pList.length];
+ for (int j = 0; j < parents.length; j++) {
+ parents[j] = commitGraph.getObjectId(pList[j]);
+ }
+ assertArrayEquals(expect.getParents(), parents);
+ }
+ }
+ }
+ }
+
+ int getGenerationNumber(ObjectId id) {
+ int graphPos = commitGraph.findGraphPosition(id);
+ CommitGraph.CommitData commitData = commitGraph.getCommitData(graphPos);
+ if (commitData != null) {
+ return commitData.getGeneration();
+ }
+ return COMMIT_GENERATION_UNKNOWN;
+ }
+
+ RevCommit commit(RevCommit... parents) throws Exception {
+ return tr.commit(parents);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriterTest.java
new file mode 100644
index 0000000000..6c5e5e5605
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriterTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021, Tencent.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.commitgraph;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.NB;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CommitGraphWriterTest extends RepositoryTestCase {
+
+ private TestRepository<FileRepository> tr;
+
+ private ByteArrayOutputStream os;
+
+ private CommitGraphWriter writer;
+
+ private RevWalk walk;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ os = new ByteArrayOutputStream();
+ tr = new TestRepository<>(db, new RevWalk(db), mockSystemReader);
+ walk = new RevWalk(db);
+ }
+
+ @Test
+ public void testWriteInEmptyRepo() throws Exception {
+ NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+ writer = new CommitGraphWriter(
+ GraphCommits.fromWalk(m, Collections.emptySet(), walk));
+ writer.write(m, os);
+ assertEquals(0, os.size());
+ }
+
+ @Test
+ public void testWriterWithExtraEdgeList() throws Exception {
+ RevCommit root = commit();
+ RevCommit a = commit(root);
+ RevCommit b = commit(root);
+ RevCommit c = commit(root);
+ RevCommit tip = commit(a, b, c);
+
+ Set<ObjectId> wants = Collections.singleton(tip);
+ NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+ GraphCommits graphCommits = GraphCommits.fromWalk(m, wants, walk);
+ writer = new CommitGraphWriter(graphCommits);
+ writer.write(m, os);
+
+ assertEquals(5, graphCommits.size());
+ byte[] data = os.toByteArray();
+ assertTrue(data.length > 0);
+ byte[] headers = new byte[8];
+ System.arraycopy(data, 0, headers, 0, 8);
+ assertArrayEquals(new byte[] {'C', 'G', 'P', 'H', 1, 1, 4, 0}, headers);
+ assertEquals(CommitGraphConstants.CHUNK_ID_OID_FANOUT, NB.decodeInt32(data, 8));
+ assertEquals(CommitGraphConstants.CHUNK_ID_OID_LOOKUP, NB.decodeInt32(data, 20));
+ assertEquals(CommitGraphConstants.CHUNK_ID_COMMIT_DATA, NB.decodeInt32(data, 32));
+ assertEquals(CommitGraphConstants.CHUNK_ID_EXTRA_EDGE_LIST, NB.decodeInt32(data, 44));
+ }
+
+ @Test
+ public void testWriterWithoutExtraEdgeList() throws Exception {
+ RevCommit root = commit();
+ RevCommit a = commit(root);
+ RevCommit b = commit(root);
+ RevCommit tip = commit(a, b);
+
+ Set<ObjectId> wants = Collections.singleton(tip);
+ NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+ GraphCommits graphCommits = GraphCommits.fromWalk(m, wants, walk);
+ writer = new CommitGraphWriter(graphCommits);
+ writer.write(m, os);
+
+ assertEquals(4, graphCommits.size());
+ byte[] data = os.toByteArray();
+ assertTrue(data.length > 0);
+ byte[] headers = new byte[8];
+ System.arraycopy(data, 0, headers, 0, 8);
+ assertArrayEquals(new byte[] {'C', 'G', 'P', 'H', 1, 1, 3, 0}, headers);
+ assertEquals(CommitGraphConstants.CHUNK_ID_OID_FANOUT, NB.decodeInt32(data, 8));
+ assertEquals(CommitGraphConstants.CHUNK_ID_OID_LOOKUP, NB.decodeInt32(data, 20));
+ assertEquals(CommitGraphConstants.CHUNK_ID_COMMIT_DATA, NB.decodeInt32(data, 32));
+ }
+
+ RevCommit commit(RevCommit... parents) throws Exception {
+ return tr.commit(parents);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/GraphCommitsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/GraphCommitsTest.java
new file mode 100644
index 0000000000..fc05febed7
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/commitgraph/GraphCommitsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021, Tencent.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.commitgraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GraphCommitsTest extends RepositoryTestCase {
+
+ private TestRepository<FileRepository> tr;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ tr = new TestRepository<>(db, new RevWalk(db), mockSystemReader);
+ }
+
+ @Test
+ public void testEmptyRepo() throws Exception {
+ try (RevWalk rw = new RevWalk(db)) {
+ GraphCommits commits = GraphCommits.fromWalk(
+ NullProgressMonitor.INSTANCE, Collections.emptySet(), rw);
+ assertEquals(0, commits.size());
+ assertEquals(0, commits.getExtraEdgeCnt());
+ assertFalse(commits.iterator().hasNext());
+ }
+ }
+
+ @Test
+ public void testRepoWithoutExtraEdges() throws Exception {
+ try (RevWalk rw = new RevWalk(db)) {
+ RevCommit root = commit();
+ RevCommit a = commit(root);
+ RevCommit b = commit(root);
+ RevCommit tip = commit(a, b);
+ GraphCommits commits = GraphCommits.fromWalk(
+ NullProgressMonitor.INSTANCE, Collections.singleton(tip),
+ rw);
+ assertEquals(4, commits.size());
+ assertEquals(0, commits.getExtraEdgeCnt());
+ verifyOrderOfLookup(List.of(root, a, b, tip), commits);
+ }
+ }
+
+ @Test
+ public void testRepoWithExtraEdges() throws Exception {
+ try (RevWalk rw = new RevWalk(db)) {
+ RevCommit root = commit();
+ RevCommit a = commit(root);
+ RevCommit b = commit(root);
+ RevCommit c = commit(root);
+ RevCommit tip = commit(a, b, c);
+ GraphCommits commits = GraphCommits.fromWalk(
+ NullProgressMonitor.INSTANCE, Collections.singleton(tip),
+ rw);
+ assertEquals(5, commits.size());
+ assertEquals(2, commits.getExtraEdgeCnt());
+ verifyOrderOfLookup(List.of(root, a, b, c, tip), commits);
+ }
+ }
+
+ @Test
+ public void testCommitNotInLookup() throws Exception {
+ try (RevWalk rw = new RevWalk(db)) {
+ RevCommit a = commit();
+ RevCommit b = commit(a);
+ GraphCommits commits = GraphCommits.fromWalk(
+ NullProgressMonitor.INSTANCE, Collections.singleton(a), rw);
+ assertEquals(1, commits.size());
+ assertEquals(0, commits.getExtraEdgeCnt());
+ Iterator<RevCommit> iterator = commits.iterator();
+ assertEquals(a, iterator.next());
+ assertFalse(iterator.hasNext());
+ assertThrows(MissingObjectException.class,
+ () -> commits.getOidPosition(b));
+ }
+ }
+
+ private void verifyOrderOfLookup(List<RevCommit> commits,
+ GraphCommits graphCommits) {
+ commits = new ArrayList<>(commits);
+ Collections.sort(commits);
+ Iterator<RevCommit> expected = commits.iterator();
+ Iterator<RevCommit> actual = graphCommits.iterator();
+ while (expected.hasNext()) {
+ assertEquals(expected.next(), actual.next());
+ }
+ assertFalse(actual.hasNext());
+ }
+
+ RevCommit commit(RevCommit... parents) throws Exception {
+ return tr.commit(parents);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
index 3dd4190c83..fef0563f48 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTest.java
@@ -284,12 +284,14 @@ public class DfsBlockCacheTest {
asyncRun(() -> pack.getBitmapIndex(reader));
asyncRun(() -> pack.getPackIndex(reader));
asyncRun(() -> pack.getBitmapIndex(reader));
+ asyncRun(() -> pack.getCommitGraph(reader));
}
waitForExecutorPoolTermination();
assertEquals(1, cache.getMissCount()[PackExt.BITMAP_INDEX.ordinal()]);
assertEquals(1, cache.getMissCount()[PackExt.INDEX.ordinal()]);
assertEquals(1, cache.getMissCount()[PackExt.REVERSE_INDEX.ordinal()]);
+ assertEquals(1, cache.getMissCount()[PackExt.COMMIT_GRAPH.ordinal()]);
}
@SuppressWarnings("resource")
@@ -313,12 +315,15 @@ public class DfsBlockCacheTest {
}
asyncRun(() -> pack1.getBitmapIndex(reader));
asyncRun(() -> pack2.getBitmapIndex(reader));
+ asyncRun(() -> pack1.getCommitGraph(reader));
+ asyncRun(() -> pack2.getCommitGraph(reader));
}
waitForExecutorPoolTermination();
assertEquals(2, cache.getMissCount()[PackExt.BITMAP_INDEX.ordinal()]);
assertEquals(2, cache.getMissCount()[PackExt.INDEX.ordinal()]);
assertEquals(2, cache.getMissCount()[PackExt.REVERSE_INDEX.ordinal()]);
+ assertEquals(2, cache.getMissCount()[PackExt.COMMIT_GRAPH.ordinal()]);
}
@SuppressWarnings("resource")
@@ -342,12 +347,15 @@ public class DfsBlockCacheTest {
}
asyncRun(() -> pack1.getBitmapIndex(reader));
asyncRun(() -> pack2.getBitmapIndex(reader));
+ asyncRun(() -> pack1.getCommitGraph(reader));
+ asyncRun(() -> pack2.getCommitGraph(reader));
}
waitForExecutorPoolTermination();
assertEquals(2, cache.getMissCount()[PackExt.BITMAP_INDEX.ordinal()]);
assertEquals(2, cache.getMissCount()[PackExt.INDEX.ordinal()]);
assertEquals(2, cache.getMissCount()[PackExt.REVERSE_INDEX.ordinal()]);
+ assertEquals(2, cache.getMissCount()[PackExt.COMMIT_GRAPH.ordinal()]);
}
@SuppressWarnings("resource")
@@ -372,7 +380,9 @@ public class DfsBlockCacheTest {
}
asyncRun(() -> pack1.getBitmapIndex(reader));
asyncRun(() -> pack1.getPackIndex(reader));
+ asyncRun(() -> pack1.getCommitGraph(reader));
asyncRun(() -> pack2.getBitmapIndex(reader));
+ asyncRun(() -> pack2.getCommitGraph(reader));
}
waitForExecutorPoolTermination();
@@ -380,6 +390,7 @@ public class DfsBlockCacheTest {
// Index is loaded once for each repo.
assertEquals(2, cache.getMissCount()[PackExt.INDEX.ordinal()]);
assertEquals(2, cache.getMissCount()[PackExt.REVERSE_INDEX.ordinal()]);
+ assertEquals(2, cache.getMissCount()[PackExt.COMMIT_GRAPH.ordinal()]);
}
@Test
@@ -396,12 +407,14 @@ public class DfsBlockCacheTest {
asyncRun(() -> pack.getBitmapIndex(reader));
asyncRun(() -> pack.getPackIndex(reader));
asyncRun(() -> pack.getBitmapIndex(reader));
+ asyncRun(() -> pack.getCommitGraph(reader));
}
waitForExecutorPoolTermination();
assertEquals(1, cache.getMissCount()[PackExt.BITMAP_INDEX.ordinal()]);
assertEquals(1, cache.getMissCount()[PackExt.INDEX.ordinal()]);
assertEquals(1, cache.getMissCount()[PackExt.REVERSE_INDEX.ordinal()]);
+ assertEquals(1, cache.getMissCount()[PackExt.COMMIT_GRAPH.ordinal()]);
}
@Test
@@ -420,12 +433,14 @@ public class DfsBlockCacheTest {
asyncRun(() -> pack.getBitmapIndex(reader));
asyncRun(() -> pack.getPackIndex(reader));
asyncRun(() -> pack.getBitmapIndex(reader));
+ asyncRun(() -> pack.getCommitGraph(reader));
}
waitForExecutorPoolTermination();
assertEquals(1, cache.getMissCount()[PackExt.BITMAP_INDEX.ordinal()]);
assertEquals(1, cache.getMissCount()[PackExt.INDEX.ordinal()]);
assertEquals(1, cache.getMissCount()[PackExt.REVERSE_INDEX.ordinal()]);
+ assertEquals(1, cache.getMissCount()[PackExt.COMMIT_GRAPH.ordinal()]);
}
private void resetCache() {
@@ -450,7 +465,7 @@ public class DfsBlockCacheTest {
repository.branch("/refs/ref2" + repoName).commit()
.add("blob2", "blob2" + repoName).parent(commit).create();
}
- new DfsGarbageCollector(repo).pack(null);
+ new DfsGarbageCollector(repo).setWriteCommitGraph(true).pack(null);
return repo;
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
index f235b2eaa4..ab998951f3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java
@@ -18,6 +18,7 @@ import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.reftable.RefCursor;
import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
@@ -976,10 +977,139 @@ public class DfsGarbageCollectorTest {
assertNull(refdb.exactRef(NEXT));
}
+ @Test
+ public void produceCommitGraphAllRefsIncludedFromDisk() throws Exception {
+ String tag = "refs/tags/tag1";
+ String head = "refs/heads/head1";
+ String nonHead = "refs/something/nonHead";
+
+ RevCommit rootCommitTagged = git.branch(tag).commit().message("0")
+ .noParents().create();
+ RevCommit headTip = git.branch(head).commit().message("1")
+ .parent(rootCommitTagged).create();
+ RevCommit nonHeadTip = git.branch(nonHead).commit().message("2")
+ .parent(rootCommitTagged).create();
+
+ gcWithCommitGraph();
+
+ assertEquals(2, odb.getPacks().length);
+ DfsPackFile gcPack = odb.getPacks()[0];
+ assertEquals(GC, gcPack.getPackDescription().getPackSource());
+
+ DfsReader reader = odb.newReader();
+ CommitGraph cg = gcPack.getCommitGraph(reader);
+ assertNotNull(cg);
+
+ assertTrue("all commits in commit graph", cg.getCommitCnt() == 3);
+ // GC packed
+ assertTrue("tag referenced commit is in graph",
+ cg.findGraphPosition(rootCommitTagged) != -1);
+ assertTrue("head referenced commit is in graph",
+ cg.findGraphPosition(headTip) != -1);
+ // GC_REST packed
+ assertTrue("nonHead referenced commit is in graph",
+ cg.findGraphPosition(nonHeadTip) != -1);
+ }
+
+ @Test
+ public void produceCommitGraphAllRefsIncludedFromCache() throws Exception {
+ String tag = "refs/tags/tag1";
+ String head = "refs/heads/head1";
+ String nonHead = "refs/something/nonHead";
+
+ RevCommit rootCommitTagged = git.branch(tag).commit().message("0")
+ .noParents().create();
+ RevCommit headTip = git.branch(head).commit().message("1")
+ .parent(rootCommitTagged).create();
+ RevCommit nonHeadTip = git.branch(nonHead).commit().message("2")
+ .parent(rootCommitTagged).create();
+
+ gcWithCommitGraph();
+
+ assertEquals(2, odb.getPacks().length);
+ DfsPackFile gcPack = odb.getPacks()[0];
+ assertEquals(GC, gcPack.getPackDescription().getPackSource());
+
+ DfsReader reader = odb.newReader();
+ gcPack.getCommitGraph(reader);
+ // Invoke cache hit
+ CommitGraph cachedCG = gcPack.getCommitGraph(reader);
+ assertNotNull(cachedCG);
+ assertTrue("commit graph have been read from disk once",
+ reader.stats.readCommitGraph == 1);
+ assertTrue("commit graph read contains content",
+ reader.stats.readCommitGraphBytes > 0);
+ assertTrue("commit graph read time is recorded",
+ reader.stats.readCommitGraphMicros > 0);
+
+ assertTrue("all commits in commit graph", cachedCG.getCommitCnt() == 3);
+ // GC packed
+ assertTrue("tag referenced commit is in graph",
+ cachedCG.findGraphPosition(rootCommitTagged) != -1);
+ assertTrue("head referenced commit is in graph",
+ cachedCG.findGraphPosition(headTip) != -1);
+ // GC_REST packed
+ assertTrue("nonHead referenced commit is in graph",
+ cachedCG.findGraphPosition(nonHeadTip) != -1);
+ }
+
+ @Test
+ public void noCommitGraphWithoutGcPack() throws Exception {
+ String nonHead = "refs/something/nonHead";
+ RevCommit nonHeadCommit = git.branch(nonHead).commit()
+ .message("nonhead").noParents().create();
+ commit().message("unreachable").parent(nonHeadCommit).create();
+
+ gcWithCommitGraph();
+
+ assertEquals(2, odb.getPacks().length);
+ for (DfsPackFile pack : odb.getPacks()) {
+ assertNull(pack.getCommitGraph(odb.newReader()));
+ }
+ }
+
+ @Test
+ public void commitGraphWithoutGCrestPack() throws Exception {
+ String head = "refs/heads/head1";
+ RevCommit headCommit = git.branch(head).commit().message("head")
+ .noParents().create();
+ RevCommit unreachableCommit = commit().message("unreachable")
+ .parent(headCommit).create();
+
+ gcWithCommitGraph();
+
+ assertEquals(2, odb.getPacks().length);
+ for (DfsPackFile pack : odb.getPacks()) {
+ DfsPackDescription d = pack.getPackDescription();
+ if (d.getPackSource() == GC) {
+ CommitGraph cg = pack.getCommitGraph(odb.newReader());
+ assertNotNull(cg);
+ assertTrue("commit graph only contains 1 commit",
+ cg.getCommitCnt() == 1);
+ assertTrue("head exists in commit graph",
+ cg.findGraphPosition(headCommit) != -1);
+ assertTrue("unreachable commit does not exist in commit graph",
+ cg.findGraphPosition(unreachableCommit) == -1);
+ } else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
+ CommitGraph cg = pack.getCommitGraph(odb.newReader());
+ assertNull(cg);
+ } else {
+ fail("unexpected " + d.getPackSource());
+ break;
+ }
+ }
+ }
+
private TestRepository<InMemoryRepository>.CommitBuilder commit() {
return git.commit();
}
+ private void gcWithCommitGraph() throws IOException {
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setWriteCommitGraph(true);
+ run(gc);
+ }
+
private void gcNoTtl() throws IOException {
DfsGarbageCollector gc = new DfsGarbageCollector(repo);
gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java
new file mode 100644
index 0000000000..dbc9dba2c4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcCommitGraphTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022, Tencent.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.IO;
+import org.junit.Test;
+
+public class GcCommitGraphTest extends GcTestCase {
+
+ @Test
+ public void testCommitGraphConfig() {
+ StoredConfig config = repo.getConfig();
+ assertFalse(gc.shouldWriteCommitGraphWhenGc());
+
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+ assertTrue(gc.shouldWriteCommitGraphWhenGc());
+
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, false);
+ assertFalse(gc.shouldWriteCommitGraphWhenGc());
+ }
+
+ @Test
+ public void testWriteEmptyRepo() throws Exception {
+ StoredConfig config = repo.getConfig();
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+
+ assertTrue(gc.shouldWriteCommitGraphWhenGc());
+ gc.writeCommitGraph(Collections.emptySet());
+ File graphFile = new File(repo.getObjectsDirectory(),
+ Constants.INFO_COMMIT_GRAPH);
+ assertFalse(graphFile.exists());
+ }
+
+ @Test
+ public void testWriteShallowRepo() throws Exception {
+ StoredConfig config = repo.getConfig();
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+
+ RevCommit tip = commitChain(2);
+ TestRepository.BranchBuilder bb = tr.branch("refs/heads/master");
+ bb.update(tip);
+ repo.getObjectDatabase().setShallowCommits(Collections.singleton(tip));
+
+ gc.writeCommitGraph(Collections.singleton(tip));
+ File graphFile = new File(repo.getObjectsDirectory(),
+ Constants.INFO_COMMIT_GRAPH);
+ assertFalse(graphFile.exists());
+ }
+
+ @Test
+ public void testWriteWhenGc() throws Exception {
+ StoredConfig config = repo.getConfig();
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+
+ RevCommit tip = commitChain(10);
+ TestRepository.BranchBuilder bb = tr.branch("refs/heads/master");
+ bb.update(tip);
+
+ assertTrue(gc.shouldWriteCommitGraphWhenGc());
+ gc.gc().get();
+ File graphFile = new File(repo.getObjectsDirectory(),
+ Constants.INFO_COMMIT_GRAPH);
+ assertGraphFile(graphFile);
+ }
+
+ @Test
+ public void testDefaultWriteWhenGc() throws Exception {
+ RevCommit tip = commitChain(10);
+ TestRepository.BranchBuilder bb = tr.branch("refs/heads/master");
+ bb.update(tip);
+
+ assertFalse(gc.shouldWriteCommitGraphWhenGc());
+ gc.gc().get();
+ File graphFile = new File(repo.getObjectsDirectory(),
+ Constants.INFO_COMMIT_GRAPH);
+ assertFalse(graphFile.exists());
+ }
+
+ @Test
+ public void testDisableWriteWhenGc() throws Exception {
+ RevCommit tip = commitChain(10);
+ TestRepository.BranchBuilder bb = tr.branch("refs/heads/master");
+ bb.update(tip);
+ File graphFile = new File(repo.getObjectsDirectory(),
+ Constants.INFO_COMMIT_GRAPH);
+
+ StoredConfig config = repo.getConfig();
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, false);
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+
+ gc.gc().get();
+ assertFalse(graphFile.exists());
+
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, false);
+ gc.gc().get();
+ assertFalse(graphFile.exists());
+
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, false);
+ config.setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, false);
+ gc.gc().get();
+ assertFalse(graphFile.exists());
+ }
+
+ @Test
+ public void testWriteCommitGraphOnly() throws Exception {
+ RevCommit tip = commitChain(10);
+ TestRepository.BranchBuilder bb = tr.branch("refs/heads/master");
+ bb.update(tip);
+
+ StoredConfig config = repo.getConfig();
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, false);
+ gc.writeCommitGraph(Collections.singleton(tip));
+
+ File graphFile = new File(repo.getObjectsDirectory(),
+ Constants.INFO_COMMIT_GRAPH);
+ assertFalse(graphFile.exists());
+
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ gc.writeCommitGraph(Collections.singleton(tip));
+ assertGraphFile(graphFile);
+ }
+
+ private void assertGraphFile(File graphFile) throws Exception {
+ assertTrue(graphFile.exists());
+ try (InputStream os = new FileInputStream(graphFile)) {
+ byte[] magic = new byte[4];
+ IO.readFully(os, magic, 0, 4);
+ assertArrayEquals(new byte[] { 'C', 'G', 'P', 'H' }, magic);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
index 620aedf20f..342e559643 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcOrphanFilesTest.java
@@ -10,6 +10,7 @@
package org.eclipse.jgit.internal.storage.file;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REVERSE_INDEX;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -36,6 +37,14 @@ public class GcOrphanFilesTest extends GcTestCase {
private static final String PACK_File_3 = PACK + "-3.pack";
+ private static final String REVERSE_File_2 = PACK + "-2."
+ + REVERSE_INDEX.getExtension();
+
+ private static final String REVERSE_File_4 = PACK + "-4."
+ + REVERSE_INDEX.getExtension();
+
+ private static final String NONEXISTENT_EXT = PACK + "-4.xxxxx";
+
private File packDir;
@Override
@@ -46,14 +55,27 @@ public class GcOrphanFilesTest extends GcTestCase {
}
@Test
- public void bitmapAndIdxDeletedButPackNot() throws Exception {
+ public void indexesDeletedButPackNot() throws Exception {
createFileInPackFolder(BITMAP_File_1);
createFileInPackFolder(IDX_File_2);
createFileInPackFolder(PACK_File_3);
+ createFileInPackFolder(REVERSE_File_4);
gc.gc().get();
assertFalse(new File(packDir, BITMAP_File_1).exists());
assertFalse(new File(packDir, IDX_File_2).exists());
assertTrue(new File(packDir, PACK_File_3).exists());
+ assertFalse(new File(packDir, REVERSE_File_4).exists());
+ }
+
+ @Test
+ public void noPacks_allIndexesDeleted() throws Exception {
+ createFileInPackFolder(BITMAP_File_1);
+ createFileInPackFolder(IDX_File_2);
+ createFileInPackFolder(REVERSE_File_4);
+ gc.gc().get();
+ assertFalse(new File(packDir, BITMAP_File_1).exists());
+ assertFalse(new File(packDir, IDX_File_2).exists());
+ assertFalse(new File(packDir, REVERSE_File_4).exists());
}
@Test
@@ -77,18 +99,27 @@ public class GcOrphanFilesTest extends GcTestCase {
}
@Test
+ public void nonexistantExtensionNotDeleted() throws Exception {
+ createFileInPackFolder(NONEXISTENT_EXT);
+ gc.gc().get();
+ assertTrue(new File(packDir, NONEXISTENT_EXT).exists());
+ }
+
+ @Test
public void keepPreventsDeletionOfIndexFilesForMissingPackFile()
throws Exception {
createFileInPackFolder(BITMAP_File_1);
- createFileInPackFolder(IDX_File_2);
createFileInPackFolder(BITMAP_File_2);
+ createFileInPackFolder(IDX_File_2);
createFileInPackFolder(KEEP_File_2);
+ createFileInPackFolder(REVERSE_File_2);
createFileInPackFolder(PACK_File_3);
gc.gc().get();
assertFalse(new File(packDir, BITMAP_File_1).exists());
assertTrue(new File(packDir, BITMAP_File_2).exists());
assertTrue(new File(packDir, IDX_File_2).exists());
assertTrue(new File(packDir, KEEP_File_2).exists());
+ assertTrue(new File(packDir, REVERSE_File_2).exists());
assertTrue(new File(packDir, PACK_File_3).exists());
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
index 1a3b3787b1..746a0a1ff3 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ObjectDirectoryTest.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.internal.storage.file;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -236,6 +237,26 @@ public class ObjectDirectoryTest extends RepositoryTestCase {
}
@Test
+ public void testWindowCursorGetCommitGraph() throws Exception {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+
+ WindowCursor curs = new WindowCursor(db.getObjectDatabase());
+ assertTrue(curs.getCommitGraph().isEmpty());
+ commitFile("file.txt", "content", "master");
+ GC gc = new GC(db);
+ gc.gc().get();
+ assertTrue(curs.getCommitGraph().isPresent());
+
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, false);
+
+ assertTrue(curs.getCommitGraph().isEmpty());
+ }
+
+ @Test
public void testShallowFileCorrupt() throws Exception {
FileRepository repository = createBareRepository();
ObjectDirectory dir = repository.getObjectDatabase();
@@ -251,6 +272,58 @@ public class ObjectDirectoryTest extends RepositoryTestCase {
IOException.class, () -> dir.getShallowCommits());
}
+ @Test
+ public void testGetCommitGraph() throws Exception {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+ db.getConfig().save();
+
+ // no commit-graph
+ ObjectDirectory dir = db.getObjectDatabase();
+ assertTrue(dir.getCommitGraph().isEmpty());
+
+ // add commit-graph
+ commitFile("file.txt", "content", "master");
+ GC gc = new GC(db);
+ gc.gc().get();
+ File file = new File(db.getObjectsDirectory(),
+ Constants.INFO_COMMIT_GRAPH);
+ assertTrue(file.exists());
+ assertTrue(file.isFile());
+ assertTrue(dir.getCommitGraph().isPresent());
+ assertEquals(1, dir.getCommitGraph().get().getCommitCnt());
+
+ // get commit-graph in a newly created db
+ try (FileRepository repo2 = new FileRepository(db.getDirectory())) {
+ ObjectDirectory dir2 = repo2.getObjectDatabase();
+ assertTrue(dir2.getCommitGraph().isPresent());
+ assertEquals(1, dir2.getCommitGraph().get().getCommitCnt());
+ }
+
+ // update commit-graph
+ commitFile("file2.txt", "content", "master");
+ gc.gc().get();
+ assertEquals(2, dir.getCommitGraph().get().getCommitCnt());
+
+ // delete commit-graph
+ file.delete();
+ assertFalse(file.exists());
+ assertTrue(dir.getCommitGraph().isEmpty());
+
+ // commit-graph is corrupt
+ try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) {
+ writer.println("this is a corrupt commit-graph");
+ }
+ assertTrue(dir.getCommitGraph().isEmpty());
+
+ // add commit-graph again
+ gc.gc().get();
+ assertTrue(dir.getCommitGraph().isPresent());
+ assertEquals(2, dir.getCommitGraph().get().getCommitCnt());
+ }
+
private Collection<Callable<ObjectId>> blobInsertersForTheSameFanOutDir(
final ObjectDirectory dir) {
Callable<ObjectId> callable = () -> dir.newInserter()
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
index 910b928864..67bba18e2b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackIndexTestCase.java
@@ -25,6 +25,7 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
public abstract class PackIndexTestCase extends RepositoryTestCase {
@@ -122,6 +123,37 @@ public abstract class PackIndexTestCase extends RepositoryTestCase {
assertFalse(iter.hasNext());
}
+ @Test
+ public void testEntriesPositionsRamdomAccess() {
+ assertEquals(4, smallIdx.findPosition(ObjectId
+ .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")));
+ assertEquals(7, smallIdx.findPosition(ObjectId
+ .fromString("c59759f143fb1fe21c197981df75a7ee00290799")));
+ assertEquals(0, smallIdx.findPosition(ObjectId
+ .fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904")));
+ }
+
+ @Test
+ public void testEntriesPositionsWithIteratorOrder() {
+ int i = 0;
+ for (MutableEntry me : smallIdx) {
+ assertEquals(smallIdx.findPosition(me.toObjectId()), i);
+ i++;
+ }
+ i = 0;
+ for (MutableEntry me : denseIdx) {
+ assertEquals(denseIdx.findPosition(me.toObjectId()), i);
+ i++;
+ }
+ }
+
+ @Test
+ public void testEntriesPositionsObjectNotInPack() {
+ assertEquals(-1, smallIdx.findPosition(ObjectId.zeroId()));
+ assertEquals(-1, smallIdx.findPosition(ObjectId
+ .fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")));
+ }
+
/**
* Compare offset from iterator entries with output of findOffset() method.
*/
@@ -135,6 +167,13 @@ public abstract class PackIndexTestCase extends RepositoryTestCase {
}
}
+ @Test
+ public void testEntriesOffsetsObjectNotInPack() {
+ assertEquals(-1, smallIdx.findOffset(ObjectId.zeroId()));
+ assertEquals(-1, smallIdx.findOffset(ObjectId
+ .fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")));
+ }
+
/**
* Compare offset from iterator entries with output of getOffset() method.
*/
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1Test.java
new file mode 100644
index 0000000000..11e3746020
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1Test.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2022, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.eclipse.jgit.util.BlockList;
+import org.junit.Test;
+
+public class PackObjectSizeIndexV1Test {
+ private static final ObjectId OBJ_ID = ObjectId
+ .fromString("b8b1d53172fb3fb19647adce4b38fab4371c2454");
+
+ private static final long GB = 1 << 30;
+
+ private static final int MAX_24BITS_UINT = 0xffffff;
+
+ @Test
+ public void write_24bPositions_32bSizes() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ objs.add(blobWithSize(100));
+ objs.add(blobWithSize(400));
+ objs.add(blobWithSize(200));
+
+ writer.write(objs);
+ byte[] expected = new byte[] { -1, 's', 'i', 'z', // header
+ 0x01, // version
+ 0x00, 0x00, 0x00, 0x00, // minimum object size
+ 0x00, 0x00, 0x00, 0x03, // obj count
+ 0x18, // Unsigned 3 bytes
+ 0x00, 0x00, 0x00, 0x03, // 3 positions
+ 0x00, 0x00, 0x00, // positions
+ 0x00, 0x00, 0x01, //
+ 0x00, 0x00, 0x02, //
+ 0x00, // No more positions
+ 0x00, 0x00, 0x00, 0x64, // size 100
+ 0x00, 0x00, 0x01, (byte) 0x90, // size 400
+ 0x00, 0x00, 0x00, (byte) 0xc8, // size 200
+ 0x00, 0x00, 0x00, 0x00 // 64bit sizes counter
+ };
+ byte[] output = out.toByteArray();
+ assertArrayEquals(expected, output);
+ }
+
+ @Test
+ public void write_32bPositions_32bSizes() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new BlockList<>(9_000_000);
+ // The 24 bit range is full of commits and trees
+ PackedObjectInfo commit = objInfo(Constants.OBJ_COMMIT, 100);
+ for (int i = 0; i <= MAX_24BITS_UINT; i++) {
+ objs.add(commit);
+ }
+ objs.add(blobWithSize(100));
+ objs.add(blobWithSize(400));
+ objs.add(blobWithSize(200));
+
+ writer.write(objs);
+ byte[] expected = new byte[] { -1, 's', 'i', 'z', // header
+ 0x01, // version
+ 0x00, 0x00, 0x00, 0x00, // minimum object size
+ 0x00, 0x00, 0x00, 0x03, // obj count
+ (byte) 0x20, // Signed 4 bytes
+ 0x00, 0x00, 0x00, 0x03, // 3 positions
+ 0x01, 0x00, 0x00, 0x00, // positions
+ 0x01, 0x00, 0x00, 0x01, //
+ 0x01, 0x00, 0x00, 0x02, //
+ 0x00, // No more positions
+ 0x00, 0x00, 0x00, 0x64, // size 100
+ 0x00, 0x00, 0x01, (byte) 0x90, // size 400
+ 0x00, 0x00, 0x00, (byte) 0xc8, // size 200
+ 0x00, 0x00, 0x00, 0x00 // 64bit sizes counter
+ };
+ byte[] output = out.toByteArray();
+ assertArrayEquals(expected, output);
+ }
+
+ @Test
+ public void write_24b32bPositions_32bSizes() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new BlockList<>(9_000_000);
+ // The 24 bit range is full of commits and trees
+ PackedObjectInfo commit = objInfo(Constants.OBJ_COMMIT, 100);
+ for (int i = 0; i < MAX_24BITS_UINT; i++) {
+ objs.add(commit);
+ }
+ objs.add(blobWithSize(100));
+ objs.add(blobWithSize(400));
+ objs.add(blobWithSize(200));
+
+ writer.write(objs);
+ byte[] expected = new byte[] { -1, 's', 'i', 'z', // header
+ 0x01, // version
+ 0x00, 0x00, 0x00, 0x00, // minimum object size
+ 0x00, 0x00, 0x00, 0x03, // obj count
+ 0x18, // 3 bytes
+ 0x00, 0x00, 0x00, 0x01, // 1 position
+ (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0x20, // 4 bytes (32 bits)
+ 0x00, 0x00, 0x00, 0x02, // 2 positions
+ 0x01, 0x00, 0x00, 0x00, // positions
+ 0x01, 0x00, 0x00, 0x01, //
+ 0x00, // No more positions
+ 0x00, 0x00, 0x00, 0x64, // size 100
+ 0x00, 0x00, 0x01, (byte) 0x90, // size 400
+ 0x00, 0x00, 0x00, (byte) 0xc8, // size 200
+ 0x00, 0x00, 0x00, 0x00 // 64bit sizes counter
+ };
+ byte[] output = out.toByteArray();
+ assertArrayEquals(expected, output);
+ }
+
+ @Test
+ public void write_64bitsSize() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ objs.add(blobWithSize(100));
+ objs.add(blobWithSize(3 * GB));
+ objs.add(blobWithSize(4 * GB));
+ objs.add(blobWithSize(400));
+
+ writer.write(objs);
+ byte[] expected = new byte[] { -1, 's', 'i', 'z', // header
+ 0x01, // version
+ 0x00, 0x00, 0x00, 0x00, // minimum object size
+ 0x00, 0x00, 0x00, 0x04, // Object count
+ 0x18, // Unsigned 3 byte positions
+ 0x00, 0x00, 0x00, 0x04, // 4 positions
+ 0x00, 0x00, 0x00, // positions
+ 0x00, 0x00, 0x01, //
+ 0x00, 0x00, 0x02, //
+ 0x00, 0x00, 0x03, //
+ 0x00, // No more positions
+ 0x00, 0x00, 0x00, 0x64, // size 100
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // -1 (3GB)
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xfe, // -2 (4GB)
+ 0x00, 0x00, 0x01, (byte) 0x90, // size 400
+ 0x00, 0x00, 0x00, (byte) 0x02, // 64bit sizes counter (2)
+ 0x00, 0x00, 0x00, 0x00, // size 3Gb
+ (byte) 0xc0, 0x00, 0x00, 0x00, //
+ 0x00, 0x00, 0x00, 0x01, // size 4GB
+ (byte) 0x00, 0x00, 0x00, 0x00, //
+ 0x00, 0x00, 0x00, 0x00 // 128bit sizes counter
+ };
+ byte[] output = out.toByteArray();
+ assertArrayEquals(expected, output);
+ }
+
+ @Test
+ public void write_allObjectsTooSmall() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 1 << 10);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ // too small blobs
+ objs.add(blobWithSize(100));
+ objs.add(blobWithSize(200));
+ objs.add(blobWithSize(400));
+
+ writer.write(objs);
+ byte[] expected = new byte[] { -1, 's', 'i', 'z', // header
+ 0x01, // version
+ 0x00, 0x00, 0x04, 0x00, // minimum object size
+ 0x00, 0x00, 0x00, 0x00, // Object count
+ };
+ byte[] output = out.toByteArray();
+ assertArrayEquals(expected, output);
+ }
+
+ @Test
+ public void write_onlyBlobsIndexed() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ objs.add(objInfo(Constants.OBJ_COMMIT, 1000));
+ objs.add(blobWithSize(100));
+ objs.add(objInfo(Constants.OBJ_TAG, 1000));
+ objs.add(blobWithSize(400));
+ objs.add(blobWithSize(200));
+
+ writer.write(objs);
+ byte[] expected = new byte[] { -1, 's', 'i', 'z', // header
+ 0x01, // version
+ 0x00, 0x00, 0x00, 0x00, // minimum object size
+ 0x00, 0x00, 0x00, 0x03, // Object count
+ 0x18, // Positions in 3 bytes
+ 0x00, 0x00, 0x00, 0x03, // 3 entries
+ 0x00, 0x00, 0x01, // positions
+ 0x00, 0x00, 0x03, //
+ 0x00, 0x00, 0x04, //
+ 0x00, // No more positions
+ 0x00, 0x00, 0x00, 0x64, // size 100
+ 0x00, 0x00, 0x01, (byte) 0x90, // size 400
+ 0x00, 0x00, 0x00, (byte) 0xc8, // size 200
+ 0x00, 0x00, 0x00, 0x00 // 64bit sizes counter
+ };
+ byte[] output = out.toByteArray();
+ assertArrayEquals(expected, output);
+ }
+
+ @Test
+ public void write_noObjects() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+
+ writer.write(objs);
+ byte[] expected = new byte[] { -1, 's', 'i', 'z', // header
+ 0x01, // version
+ 0x00, 0x00, 0x00, 0x00, // minimum object size
+ 0x00, 0x00, 0x00, 0x00, // Object count
+ };
+ byte[] output = out.toByteArray();
+ assertArrayEquals(expected, output);
+ }
+
+ @Test
+ public void read_empty() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+
+ writer.write(objs);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ PackObjectSizeIndex index = PackObjectSizeIndexLoader.load(in);
+ assertEquals(-1, index.getSize(0));
+ assertEquals(-1, index.getSize(1));
+ assertEquals(-1, index.getSize(1 << 30));
+ assertEquals(0, index.getThreshold());
+ }
+
+ @Test
+ public void read_only24bitsPositions() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ objs.add(blobWithSize(100));
+ objs.add(blobWithSize(200));
+ objs.add(blobWithSize(400));
+ objs.add(blobWithSize(1500));
+
+ writer.write(objs);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ PackObjectSizeIndex index = PackObjectSizeIndexLoader.load(in);
+ assertEquals(100, index.getSize(0));
+ assertEquals(200, index.getSize(1));
+ assertEquals(400, index.getSize(2));
+ assertEquals(1500, index.getSize(3));
+ assertEquals(0, index.getThreshold());
+ }
+
+ @Test
+ public void read_only32bitsPositions() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 2000);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ PackedObjectInfo smallObj = blobWithSize(100);
+ for (int i = 0; i <= MAX_24BITS_UINT; i++) {
+ objs.add(smallObj);
+ }
+ objs.add(blobWithSize(1000));
+ objs.add(blobWithSize(3000));
+ objs.add(blobWithSize(2500));
+ objs.add(blobWithSize(1000));
+
+ writer.write(objs);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ PackObjectSizeIndex index = PackObjectSizeIndexLoader.load(in);
+ assertEquals(-1, index.getSize(5));
+ assertEquals(-1, index.getSize(MAX_24BITS_UINT+1));
+ assertEquals(3000, index.getSize(MAX_24BITS_UINT+2));
+ assertEquals(2500, index.getSize(MAX_24BITS_UINT+3));
+ assertEquals(-1, index.getSize(MAX_24BITS_UINT+4)); // Not indexed
+ assertEquals(2000, index.getThreshold());
+ }
+
+ @Test
+ public void read_24and32BitsPositions() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 2000);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ PackedObjectInfo smallObj = blobWithSize(100);
+ for (int i = 0; i <= MAX_24BITS_UINT; i++) {
+ if (i == 500 || i == 1000 || i == 1500) {
+ objs.add(blobWithSize(2500));
+ continue;
+ }
+ objs.add(smallObj);
+ }
+ objs.add(blobWithSize(3000));
+ objs.add(blobWithSize(1000));
+ objs.add(blobWithSize(2500));
+ objs.add(blobWithSize(1000));
+
+ writer.write(objs);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ PackObjectSizeIndex index = PackObjectSizeIndexLoader.load(in);
+ // 24 bit positions
+ assertEquals(-1, index.getSize(5));
+ assertEquals(2500, index.getSize(500));
+ // 32 bit positions
+ assertEquals(3000, index.getSize(MAX_24BITS_UINT+1));
+ assertEquals(-1, index.getSize(MAX_24BITS_UINT+2));
+ assertEquals(2500, index.getSize(MAX_24BITS_UINT+3));
+ assertEquals(-1, index.getSize(MAX_24BITS_UINT+4)); // Not indexed
+ assertEquals(2000, index.getThreshold());
+ }
+
+ @Test
+ public void read_only64bits() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ objs.add(blobWithSize(3 * GB));
+ objs.add(blobWithSize(8 * GB));
+
+ writer.write(objs);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ PackObjectSizeIndex index = PackObjectSizeIndexLoader.load(in);
+ assertEquals(3 * GB, index.getSize(0));
+ assertEquals(8 * GB, index.getSize(1));
+ assertEquals(0, index.getThreshold());
+ }
+
+ @Test
+ public void read_withMinSize() throws IOException {
+ int minSize = 1000;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, minSize);
+ List<PackedObjectInfo> objs = new ArrayList<>();
+ objs.add(blobWithSize(3 * GB));
+ objs.add(blobWithSize(1500));
+ objs.add(blobWithSize(500));
+ objs.add(blobWithSize(1000));
+ objs.add(blobWithSize(2000));
+
+ writer.write(objs);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ PackObjectSizeIndex index = PackObjectSizeIndexLoader.load(in);
+ assertEquals(3 * GB, index.getSize(0));
+ assertEquals(1500, index.getSize(1));
+ assertEquals(-1, index.getSize(2));
+ assertEquals(1000, index.getSize(3));
+ assertEquals(2000, index.getSize(4));
+ assertEquals(minSize, index.getThreshold());
+ }
+
+ private static PackedObjectInfo blobWithSize(long size) {
+ return objInfo(Constants.OBJ_BLOB, size);
+ }
+
+ private static PackedObjectInfo objInfo(int type, long size) {
+ PackedObjectInfo objectInfo = new PackedObjectInfo(OBJ_ID);
+ objectInfo.setType(type);
+ objectInfo.setFullSize(size);
+ return objectInfo;
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
index 3fe8f52fba..2a403c7699 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
@@ -505,6 +505,43 @@ public class PackWriterTest extends SampleDataRepositoryTestCase {
}
@Test
+ public void testWriteObjectSizeIndex_noDeltas() throws Exception {
+ config.setMinBytesForObjSizeIndex(0);
+ HashSet<ObjectId> interesting = new HashSet<>();
+ interesting.add(ObjectId
+ .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
+
+ NullProgressMonitor m1 = NullProgressMonitor.INSTANCE;
+ writer = new PackWriter(config, db.newObjectReader());
+ writer.setUseBitmaps(false);
+ writer.setThin(false);
+ writer.setIgnoreMissingUninteresting(false);
+ writer.preparePack(m1, interesting, NONE);
+ writer.writePack(m1, m1, os);
+
+ PackIndex idx;
+ try (ByteArrayOutputStream is = new ByteArrayOutputStream()) {
+ writer.writeIndex(is);
+ idx = PackIndex.read(new ByteArrayInputStream(is.toByteArray()));
+ }
+
+ PackObjectSizeIndex objSizeIdx;
+ try (ByteArrayOutputStream objSizeStream = new ByteArrayOutputStream()) {
+ writer.writeObjectSizeIndex(objSizeStream);
+ objSizeIdx = PackObjectSizeIndexLoader.load(
+ new ByteArrayInputStream(objSizeStream.toByteArray()));
+ }
+ writer.close();
+
+ ObjectId knownBlob1 = ObjectId
+ .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259");
+ ObjectId knownBlob2 = ObjectId
+ .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3");
+ assertEquals(18009, objSizeIdx.getSize(idx.findPosition(knownBlob1)));
+ assertEquals(18787, objSizeIdx.getSize(idx.findPosition(knownBlob2)));
+ }
+
+ @Test
public void testExclude() throws Exception {
// TestRepository closes repo
FileRepository repo = createBareRepository();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UInt24ArrayTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UInt24ArrayTest.java
new file mode 100644
index 0000000000..a96c217e4f
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/UInt24ArrayTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UInt24ArrayTest {
+
+ private static final byte[] DATA = { 0x00, 0x00, 0x00, // 0
+ 0x00, 0x00, 0x05, // 5
+ 0x00, 0x00, 0x0a, // 10
+ 0x00, 0x00, 0x0f, // 15
+ 0x00, 0x00, 0x14, // 20
+ 0x00, 0x00, 0x19, // 25
+ (byte) 0xff, 0x00, 0x00, // Uint with MSB=1
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, // MAX
+ };
+
+ private static final UInt24Array asArray = new UInt24Array(DATA);
+
+ @Test
+ public void uInt24Array_size() {
+ assertEquals(8, asArray.size());
+ }
+
+ @Test
+ public void uInt24Array_get() {
+ assertEquals(0, asArray.get(0));
+ assertEquals(5, asArray.get(1));
+ assertEquals(10, asArray.get(2));
+ assertEquals(15, asArray.get(3));
+ assertEquals(20, asArray.get(4));
+ assertEquals(25, asArray.get(5));
+ assertEquals(0xff0000, asArray.get(6));
+ assertEquals(0xffffff, asArray.get(7));
+ assertThrows(IndexOutOfBoundsException.class, () -> asArray.get(9));
+ }
+
+ @Test
+ public void uInt24Array_getLastValue() {
+ assertEquals(0xffffff, asArray.getLastValue());
+ }
+
+ @Test
+ public void uInt24Array_find() {
+ assertEquals(0, asArray.binarySearch(0));
+ assertEquals(1, asArray.binarySearch(5));
+ assertEquals(2, asArray.binarySearch(10));
+ assertEquals(3, asArray.binarySearch(15));
+ assertEquals(4, asArray.binarySearch(20));
+ assertEquals(5, asArray.binarySearch(25));
+ assertEquals(6, asArray.binarySearch(0xff0000));
+ assertEquals(7, asArray.binarySearch(0xffffff));
+ assertThrows(IllegalArgumentException.class,
+ () -> asArray.binarySearch(Integer.MAX_VALUE));
+ }
+
+ @Test
+ public void uInt24Array_empty() {
+ Assert.assertTrue(UInt24Array.EMPTY.isEmpty());
+ assertEquals(0, UInt24Array.EMPTY.size());
+ assertEquals(-1, UInt24Array.EMPTY.binarySearch(1));
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> UInt24Array.EMPTY.getLastValue());
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> UInt24Array.EMPTY.get(0));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java
index 09a7c0b28a..58ed7850b1 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStreamTest.java
@@ -54,6 +54,11 @@ public class CancellableDigestOutputStreamTest {
public boolean isCancelled() {
return cancelled;
}
+
+ @Override
+ public void showDuration(boolean enabled) {
+ // not implemented
+ }
}
@Test
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/memory/TernarySearchTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/memory/TernarySearchTreeTest.java
new file mode 100644
index 0000000000..623c92fc90
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/memory/TernarySearchTreeTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2021, Matthias Sohn <matthias.sohn@sap.com> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.memory;
+
+import static java.util.Map.entry;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class TernarySearchTreeTest {
+
+ private TernarySearchTree<String> tree;
+
+ @Before
+ public void setup() {
+ tree = new TernarySearchTree<>();
+ tree.insert("foo", "1");
+ tree.insert("bar", "2");
+ tree.insert("foobar", "3");
+ tree.insert("foobaz", "4");
+ tree.insert("johndoe", "5");
+ }
+
+ @Test
+ public void testInsert() {
+ int initialSize = tree.size();
+ tree.insert("foobarbaz", "6");
+ assertEquals(initialSize + 1, tree.size());
+ assertEquals("6", tree.get("foobarbaz"));
+ }
+
+ @Test
+ public void testBatchInsert() {
+ Map<String, String> m = Map.ofEntries(
+ entry("refs/heads/master", "master"),
+ entry("refs/heads/stable-1.0", "stable-1.0"),
+ entry("refs/heads/stable-2.1", "stable-2.1"),
+ entry("refs/heads/stable-2.0", "stable-2.0"),
+ entry("refs/heads/stable-1.1", "stable-1.1"),
+ entry("refs/heads/stable-2.2", "stable-2.2"),
+ entry("aaa", "aaa"), entry("refs/tags/xyz", "xyz"),
+ entry("refs/tags/v1.1", "v1.1"),
+ entry("refs/tags/v2.2", "v2.2"),
+ entry("refs/tags/v1.0", "v1.0"),
+ entry("refs/tags/v2.1", "v2.1"),
+ entry("refs/tags/v2.0", "v2.0"));
+ tree.insert(m);
+ assertArrayEquals(
+ new String[] { "refs/heads/master", "refs/heads/stable-1.0",
+ "refs/heads/stable-1.1", "refs/heads/stable-2.0",
+ "refs/heads/stable-2.1", "refs/heads/stable-2.2" },
+ toArray(tree.getKeysWithPrefix("refs/heads/")));
+ assertArrayEquals(
+ new String[] { "refs/tags/v1.0", "refs/tags/v1.1",
+ "refs/tags/v2.0", "refs/tags/v2.1", "refs/tags/v2.2",
+ "refs/tags/xyz" },
+ toArray(tree.getKeysWithPrefix("refs/tags/")));
+ assertEquals("aaa", tree.get("aaa"));
+ }
+
+ @Test
+ public void testInsertWithNullKey() {
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () -> {
+ tree.insert(null, "42");
+ });
+ assertTrue(exception.getMessage()
+ .contains("TernarySearchTree key must not be null or empty"));
+ }
+
+ @Test
+ public void testOverwriteValue() {
+ int initialSize = tree.size();
+ tree.insert("foo", "overwritten");
+ assertEquals(initialSize, tree.size());
+ assertEquals("overwritten", tree.get("foo"));
+ }
+
+ @Test
+ public void testInsertNullValue() {
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () -> {
+ tree.insert("xxx", null);
+ });
+ assertTrue(exception.getMessage()
+ .contains("cannot insert null value into TernarySearchTree"));
+ }
+
+ @Test
+ public void testReloadAll() {
+ Map<String, String> map = Map.of("foo", "foo-value", "bar",
+ "bar-value");
+ tree.replace(map.entrySet());
+ assertArrayEquals(new String[] { "bar", "foo" },
+ toArray(tree.getKeys()));
+ }
+
+ @Test
+ public void testReload() {
+ int initialSize = tree.size();
+ Map<String, String> map = Map.of("foo", "foo-value", "bar",
+ "bar-value");
+ tree.reload(map.entrySet());
+ assertEquals("foo-value", tree.get("foo"));
+ assertEquals("bar-value", tree.get("bar"));
+ assertEquals("3", tree.get("foobar"));
+ assertEquals("4", tree.get("foobaz"));
+ assertEquals("5", tree.get("johndoe"));
+ assertEquals(initialSize, tree.size());
+ }
+
+ @Test
+ public void testContains() {
+ assertTrue(tree.contains("foo"));
+ assertTrue(tree.contains("foobaz"));
+ assertFalse(tree.contains("ship"));
+ }
+
+ @Test
+ public void testContainsWithNullKey() {
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () -> {
+ tree.contains(null);
+ });
+ assertTrue(exception.getMessage()
+ .contains("TernarySearchTree key must not be null or empty"));
+ }
+
+ @Test
+ public void testGet() {
+ assertEquals("1", tree.get("foo"));
+ assertEquals("5", tree.get("johndoe"));
+ assertNull(tree.get("ship"));
+ }
+
+ @Test
+ public void testGetWithNullKey() {
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () -> {
+ tree.get(null);
+ });
+ assertTrue(exception.getMessage()
+ .contains("TernarySearchTree key must not be null or empty"));
+ }
+
+ @Test
+ public void testDeleteExisting() {
+ int initialSize = tree.size();
+ tree.delete("foo");
+ assertNull(tree.get("foo"));
+ assertEquals(initialSize - 1, tree.size());
+ tree.delete("cake");
+ assertEquals(4, tree.size());
+ }
+
+ @Test
+ public void testDeleteNonExisting() {
+ int initialSize = tree.size();
+ tree.delete("non-existent-key");
+ assertEquals(initialSize, tree.size());
+ }
+
+ @Test
+ public void testDeleteWithNullKey() {
+ Exception exception = assertThrows(IllegalArgumentException.class,
+ () -> {
+ tree.delete((String) null);
+ });
+ assertTrue(exception.getMessage()
+ .contains("TernarySearchTree key must not be null or empty"));
+ }
+
+ @Test
+ public void testDeleteMultiple() {
+ int initialSize = tree.size();
+ List<String> keys = toList(tree.getKeys());
+ keys.remove("foobar");
+ keys.remove("johndoe");
+ tree.delete(Arrays.asList(new String[] { "foobar", "johndoe" }));
+ assertEquals(initialSize - 2, tree.size());
+ assertArrayEquals(keys.toArray(), toArray(tree.getKeys()));
+ }
+
+ @Test
+ public void testClear() {
+ assertEquals(5, tree.size());
+ tree.clear();
+ assertEquals(0, tree.size());
+ tree.getKeys().forEach(new Consumer<String>() {
+
+ @Override
+ public void accept(String t) {
+ throw new IllegalStateException("should find no key");
+ }
+ });
+ }
+
+ @Test
+ public void testKeyLongestPrefix() {
+ assertEquals("foobar", tree.keyLongestPrefixOf("foobari"));
+ assertEquals("foo", tree.keyLongestPrefixOf("foocake"));
+ assertEquals("", tree.keyLongestPrefixOf("faabar"));
+ assertEquals("johndoe", tree.keyLongestPrefixOf("johndoea"));
+ assertEquals("", tree.keyLongestPrefixOf("wxy"));
+ assertNull(tree.keyLongestPrefixOf(""));
+ assertNull(tree.keyLongestPrefixOf(null));
+ }
+
+ @Test
+ public void testGetKeys() {
+ assertArrayEquals(
+ new String[] { "bar", "foo", "foobar", "foobaz", "johndoe" },
+ toArray(tree.getKeys()));
+ }
+
+ @Test
+ public void testGetKeysWithPrefix() {
+ assertArrayEquals(new String[] { "foo", "foobar", "foobaz" },
+ toArray(tree.getKeysWithPrefix("foo")));
+ assertArrayEquals(new String[] { "johndoe" },
+ toArray(tree.getKeysWithPrefix("john")));
+ assertArrayEquals(new String[0],
+ toArray(tree.getKeysWithPrefix("cake")));
+ assertArrayEquals(
+ new String[] { "bar", "foo", "foobar", "foobaz", "johndoe" },
+ toArray(tree.getKeysWithPrefix("")));
+ assertArrayEquals(new String[0], toArray(tree.getKeysWithPrefix(null)));
+ }
+
+ @Test
+ public void testGetWithPrefixFoo() {
+ Map<String, String> result = tree.getWithPrefix("foo");
+ assertEquals(3, result.size());
+ assertEquals("1", result.get("foo"));
+ assertEquals("3", result.get("foobar"));
+ assertEquals("4", result.get("foobaz"));
+ }
+
+ @Test
+ public void testGetWithPrefixNotFound() {
+ Map<String, String> result = tree.getWithPrefix("cheese");
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ public void testGetWithPrefixNull() {
+ Map<String, String> result = tree.getWithPrefix(null);
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ public void testGetWithPrefixEmptyPrefix() {
+ Map<String, String> result = tree.getWithPrefix("");
+ assertEquals(5, result.size());
+ assertEquals("1", result.get("foo"));
+ assertEquals("2", result.get("bar"));
+ assertEquals("3", result.get("foobar"));
+ assertEquals("4", result.get("foobaz"));
+ assertEquals("5", result.get("johndoe"));
+ }
+
+ @Test
+ public void testGetValuesWithPrefixFoo() {
+ List<String> result = tree.getValuesWithPrefix("foo");
+ assertEquals(3, result.size());
+ assertArrayEquals(new String[] { "1", "3", "4" },
+ result.toArray());
+ }
+
+ @Test
+ public void testGetValuesWithPrefixNotFound() {
+ List<String> result = tree.getValuesWithPrefix("cheese");
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ public void testGetValuesWithPrefixNull() {
+ List<String> result = tree.getValuesWithPrefix(null);
+ assertEquals(0, result.size());
+ }
+
+ @Test
+ public void testGetValuesWithPrefixEmptyPrefix() {
+ List<String> result = tree.getValuesWithPrefix("");
+ assertEquals(5, result.size());
+ assertArrayEquals(new String[] { "2", "1", "3", "4", "5" },
+ result.toArray());
+ }
+
+ @Test
+ public void testGetKeysMatching() {
+ assertArrayEquals(new String[] { "foobar" },
+ toArray(tree.getKeysMatching("fo?bar")));
+ assertArrayEquals(new String[] { "foobar", "foobaz" },
+ toArray(tree.getKeysMatching("fooba?")));
+ assertArrayEquals(new String[] { "foobar", "foobaz" },
+ toArray(tree.getKeysMatching("?o?ba?")));
+ assertArrayEquals(new String[0], toArray(tree.getKeysMatching("")));
+ assertArrayEquals(new String[0], toArray(tree.getKeysMatching(null)));
+ }
+
+ private static List<String> toList(Iterable<String> iter) {
+ return StreamSupport.stream(iter.spliterator(), false)
+ .collect(Collectors.toList());
+ }
+
+ private static String[] toArray(Iterable<String> iter) {
+ return toList(iter).toArray(new String[0]);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchConfigTest.java
index 1374ea2619..2c9097f377 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/BranchConfigTest.java
@@ -14,9 +14,11 @@ package org.eclipse.jgit.lib;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
import org.junit.Test;
public class BranchConfigTest {
@@ -114,17 +116,58 @@ public class BranchConfigTest {
}
@Test
+ public void testRebaseMode() {
+ Config c = parse("" //
+ + "[branch \"undefined\"]\n"
+ + "[branch \"false\"]\n"
+ + " rebase = false\n"
+ + "[branch \"true\"]\n"
+ + " rebase = true\n"
+ + "[branch \"interactive\"]\n"
+ + " rebase = interactive\n"
+ + "[branch \"merges\"]\n"
+ + " rebase = merges\n"
+ + "[branch \"preserve\"]\n"
+ + " rebase = preserve\n"
+ + "[branch \"illegal\"]\n"
+ + " rebase = illegal\n");
+ assertEquals(BranchRebaseMode.NONE,
+ new BranchConfig(c, " undefined").getRebaseMode());
+ assertEquals(BranchRebaseMode.NONE,
+ new BranchConfig(c, "false").getRebaseMode());
+ assertEquals(BranchRebaseMode.REBASE,
+ new BranchConfig(c, "true").getRebaseMode());
+ assertEquals(BranchRebaseMode.INTERACTIVE,
+ new BranchConfig(c, "interactive").getRebaseMode());
+ assertEquals(BranchRebaseMode.MERGES,
+ new BranchConfig(c, "merges").getRebaseMode());
+ assertEquals(BranchRebaseMode.MERGES,
+ new BranchConfig(c, "preserve").getRebaseMode());
+ assertThrows(IllegalArgumentException.class,
+ () -> new BranchConfig(c, "illegal").getRebaseMode());
+ }
+
+ @Test
public void isRebase() {
Config c = parse("" //
+ "[branch \"undefined\"]\n"
+ "[branch \"false\"]\n"
+ " rebase = false\n"
+ "[branch \"true\"]\n"
- + " rebase = true\n");
+ + " rebase = true\n"
+ + "[branch \"interactive\"]\n"
+ + " rebase = interactive\n"
+ + "[branch \"merges\"]\n"
+ + " rebase = merges\n"
+ + "[branch \"preserve\"]\n"
+ + " rebase = preserve\n");
assertFalse(new BranchConfig(c, "undefined").isRebase());
assertFalse(new BranchConfig(c, "false").isRebase());
assertTrue(new BranchConfig(c, "true").isRebase());
+ assertTrue(new BranchConfig(c, "interactive").isRebase());
+ assertTrue(new BranchConfig(c, "merges").isRebase());
+ assertTrue(new BranchConfig(c, "preserve").isRebase());
}
private static Config parse(String content) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
index a85a4f49b6..8f9d105319 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
@@ -1581,6 +1581,20 @@ public class ConfigTest {
config.get(CommitConfig.KEY).getCommitTemplateContent(repo);
}
+ @Test
+ public void testCoreCommitGraphConfig() {
+ Config config = new Config();
+ assertFalse(config.get(CoreConfig.KEY).enableCommitGraph());
+
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ assertTrue(config.get(CoreConfig.KEY).enableCommitGraph());
+
+ config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, false);
+ assertFalse(config.get(CoreConfig.KEY).enableCommitGraph());
+ }
+
private static void assertValueRoundTrip(String value)
throws ConfigInvalidException {
assertValueRoundTrip(value, value);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
index 8f84155dd3..e21ff580bd 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java
@@ -167,5 +167,10 @@ public class ThreadSafeProgressMonitorTest {
public boolean isCancelled() {
return false;
}
+
+ @Override
+ public void showDuration(boolean enabled) {
+ // not implemented
+ }
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java
index aeb3e6127a..893fd61556 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchApplierTest.java
@@ -24,6 +24,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.api.errors.PatchFormatException;
@@ -71,26 +72,21 @@ public class PatchApplierTest {
this.inCore = inCore;
}
+ void init(final String aName) throws Exception {
+ init(aName, true, true);
+ }
+
protected void init(String aName, boolean preExists, boolean postExists)
throws Exception {
- /* Patch and pre/postimage are read from data org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/ */
+ // Patch and pre/postimage are read from data
+ // org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/
this.name = aName;
if (postExists) {
- postImage = IO
- .readWholeStream(getTestResource(name + "_PostImage"), 0)
- .array();
- expectedText = new String(postImage, StandardCharsets.UTF_8);
+ expectedText = initPostImage(aName);
}
- File f = new File(db.getWorkTree(), name);
if (preExists) {
- preImage = IO
- .readWholeStream(getTestResource(name + "_PreImage"), 0)
- .array();
- try (Git git = new Git(db)) {
- Files.write(f.toPath(), preImage);
- git.add().addFilepattern(name).call();
- }
+ initPreImage(aName);
}
try (Git git = new Git(db)) {
RevCommit base = git.commit().setMessage("PreImage").call();
@@ -98,8 +94,22 @@ public class PatchApplierTest {
}
}
- void init(final String aName) throws Exception {
- init(aName, true, true);
+ protected void initPreImage(String aName) throws Exception {
+ File f = new File(db.getWorkTree(), aName);
+ preImage = IO
+ .readWholeStream(getTestResource(aName + "_PreImage"), 0)
+ .array();
+ try (Git git = new Git(db)) {
+ Files.write(f.toPath(), preImage);
+ git.add().addFilepattern(aName).call();
+ }
+ }
+
+ protected String initPostImage(String aName) throws Exception {
+ postImage = IO
+ .readWholeStream(getTestResource(aName + "_PostImage"), 0)
+ .array();
+ return new String(postImage, StandardCharsets.UTF_8);
}
protected Result applyPatch()
@@ -117,51 +127,64 @@ public class PatchApplierTest {
return PatchApplierTest.class.getClassLoader()
.getResourceAsStream("org/eclipse/jgit/diff/" + patchFile);
}
+
void verifyChange(Result result, String aName) throws Exception {
verifyChange(result, aName, true);
}
- protected void verifyContent(Result result, String path, boolean exists) throws Exception {
+ protected void verifyContent(Result result, String path, boolean exists)
+ throws Exception {
+ verifyContent(result, path, exists ? expectedText : null);
+ }
+
+ protected void verifyContent(Result result, String path,
+ @Nullable String expectedContent) throws Exception {
if (inCore) {
byte[] output = readBlob(result.getTreeId(), path);
- if (!exists)
+ if (expectedContent == null)
assertNull(output);
else {
assertNotNull(output);
- assertEquals(new String(output, StandardCharsets.UTF_8), expectedText);
+ assertEquals(expectedContent,
+ new String(output, StandardCharsets.UTF_8));
}
} else {
File f = new File(db.getWorkTree(), path);
- if (!exists)
+ if (expectedContent == null)
assertFalse(f.exists());
else
- checkFile(f, expectedText);
+ checkFile(f, expectedContent);
}
}
- void verifyChange(Result result, String aName, boolean exists) throws Exception {
+ void verifyChange(Result result, String aName, boolean exists)
+ throws Exception {
assertEquals(1, result.getPaths().size());
verifyContent(result, aName, exists);
}
- protected byte[] readBlob(ObjectId treeish, String path) throws Exception {
+ protected byte[] readBlob(ObjectId treeish, String path)
+ throws Exception {
try (TestRepository<?> tr = new TestRepository<>(db);
RevWalk rw = tr.getRevWalk()) {
db.incrementOpen();
RevTree tree = rw.parseTree(treeish);
- try (TreeWalk tw = TreeWalk.forPath(db,path,tree)){
+ try (TreeWalk tw = TreeWalk.forPath(db, path, tree)) {
if (tw == null) {
return null;
}
- return tw.getObjectReader().open(tw.getObjectId(0), OBJ_BLOB).getBytes();
+ return tw.getObjectReader()
+ .open(tw.getObjectId(0), OBJ_BLOB).getBytes();
}
}
}
- protected void checkBinary(Result result, int numberOfFiles) throws Exception {
+ protected void checkBinary(Result result, int numberOfFiles)
+ throws Exception {
assertEquals(numberOfFiles, result.getPaths().size());
if (inCore) {
- assertArrayEquals(postImage, readBlob(result.getTreeId(), result.getPaths().get(0)));
+ assertArrayEquals(postImage,
+ readBlob(result.getTreeId(), result.getPaths().get(0)));
} else {
File f = new File(db.getWorkTree(), name);
assertArrayEquals(postImage, Files.readAllBytes(f.toPath()));
@@ -292,7 +315,7 @@ public class PatchApplierTest {
assertTrue(result.getPaths().contains("RenameNoHunks"));
assertTrue(result.getPaths().contains("nested/subdir/Renamed"));
- verifyContent(result,"nested/subdir/Renamed", true);
+ verifyContent(result, "nested/subdir/Renamed", true);
}
@Test
@@ -304,7 +327,7 @@ public class PatchApplierTest {
assertTrue(result.getPaths().contains("RenameWithHunks"));
assertTrue(result.getPaths().contains("nested/subdir/Renamed"));
- verifyContent(result,"nested/subdir/Renamed", true);
+ verifyContent(result, "nested/subdir/Renamed", true);
}
@Test
@@ -346,6 +369,17 @@ public class PatchApplierTest {
Result result = applyPatch();
verifyChange(result, "ShiftDown2");
}
+
+ @Test
+ public void testDoesNotAffectUnrelatedFiles() throws Exception {
+ initPreImage("Unaffected");
+ String expectedUnaffectedText = initPostImage("Unaffected");
+ init("X");
+
+ Result result = applyPatch();
+ verifyChange(result, "X");
+ verifyContent(result, "Unaffected", expectedUnaffectedText);
+ }
}
public static class InCore extends Base {
@@ -353,10 +387,44 @@ public class PatchApplierTest {
public InCore() {
super(true);
}
+
+ @Test
+ public void testNoNewlineAtEnd() throws Exception {
+ init("x_d");
+
+ Result result = applyPatch();
+ verifyChange(result, "x_d");
+ }
+
+ @Test
+ public void testNoNewlineAtEndInHunk() throws Exception {
+ init("x_e");
+
+ Result result = applyPatch();
+ verifyChange(result, "x_e");
+ }
+
+ @Test
+ public void testAddNewlineAtEnd() throws Exception {
+ init("x_add_nl");
+
+ Result result = applyPatch();
+ verifyChange(result, "x_add_nl");
+ }
+
+ @Test
+ public void testRemoveNewlineAtEnd() throws Exception {
+ init("x_last_rm_nl");
+
+ Result result = applyPatch();
+ verifyChange(result, "x_last_rm_nl");
+ }
}
public static class WithWorktree extends Base {
- public WithWorktree() { super(false); }
+ public WithWorktree() {
+ super(false);
+ }
@Test
public void testModifyNL1() throws Exception {
@@ -369,8 +437,8 @@ public class PatchApplierTest {
@Test
public void testCrLf() throws Exception {
try {
- db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
init("crlf", true, true);
Result result = applyPatch();
@@ -385,8 +453,8 @@ public class PatchApplierTest {
@Test
public void testCrLfOff() throws Exception {
try {
- db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
init("crlf", true, true);
Result result = applyPatch();
@@ -401,8 +469,8 @@ public class PatchApplierTest {
@Test
public void testCrLfEmptyCommitted() throws Exception {
try {
- db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
init("crlf3", true, true);
Result result = applyPatch();
@@ -417,8 +485,8 @@ public class PatchApplierTest {
@Test
public void testCrLfNewFile() throws Exception {
try {
- db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
init("crlf4", false, true);
Result result = applyPatch();
@@ -433,8 +501,8 @@ public class PatchApplierTest {
@Test
public void testPatchWithCrLf() throws Exception {
try {
- db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
init("crlf2", true, true);
Result result = applyPatch();
@@ -450,11 +518,11 @@ public class PatchApplierTest {
public void testPatchWithCrLf2() throws Exception {
String aName = "crlf2";
try (Git git = new Git(db)) {
- db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
init(aName, true, true);
- db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
Result result = applyPatch();
@@ -465,10 +533,234 @@ public class PatchApplierTest {
}
}
- // Clean/smudge filter for testFiltering. The smudgetest test resources were
- // created with C git using a clean filter sed -e "s/A/E/g" and the smudge
- // filter sed -e "s/E/A/g". To keep the test independent of the presence of
- // sed, implement this with a built-in filter.
+ @Test
+ public void testNoNewlineAtEndAutoCRLF_true() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+
+ init("x_d_crlf", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_d_crlf");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testNoNewlineAtEndAutoCRLF_false() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+
+ init("x_d", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_d");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testNoNewlineAtEndAutoCRLF_input() throws Exception {
+ try {
+ db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, "input");
+
+ init("x_d", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_d");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testNoNewlineAtEndInHunkAutoCRLF_true() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+
+ init("x_e_crlf", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_e_crlf");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testNoNewlineAtEndInHunkAutoCRLF_false() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+
+ init("x_e", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_e");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testNoNewlineAtEndInHunkAutoCRLF_input() throws Exception {
+ try {
+ db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, "input");
+
+ init("x_e", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_e");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testAddNewlineAtEndAutoCRLF_true() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+
+ init("x_add_nl_crlf", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_add_nl_crlf");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testAddNewlineAtEndAutoCRLF_false() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+
+ init("x_add_nl", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_add_nl");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testAddNewlineAtEndAutoCRLF_input() throws Exception {
+ try {
+ db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, "input");
+
+ init("x_add_nl", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_add_nl");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testRemoveNewlineAtEndAutoCRLF_true() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, true);
+
+ init("x_last_rm_nl_crlf", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_last_rm_nl_crlf");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testRemoveNewlineAtEndAutoCRLF_false() throws Exception {
+ try {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false);
+
+ init("x_last_rm_nl", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_last_rm_nl");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testRemoveNewlineAtEndAutoCRLF_input() throws Exception {
+ try {
+ db.getConfig().setString(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_AUTOCRLF, "input");
+
+ init("x_last_rm_nl", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "x_last_rm_nl");
+ } finally {
+ db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF);
+ }
+ }
+
+ @Test
+ public void testEditExample() throws Exception {
+ init("z_e", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "z_e");
+ }
+
+ @Test
+ public void testEditNoNewline() throws Exception {
+ init("z_e_no_nl", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "z_e_no_nl");
+ }
+
+ @Test
+ public void testEditAddNewline() throws Exception {
+ init("z_e_add_nl", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "z_e_add_nl");
+ }
+
+ @Test
+ public void testEditRemoveNewline() throws Exception {
+ init("z_e_rm_nl", true, true);
+
+ Result result = applyPatch();
+ verifyChange(result, "z_e_rm_nl");
+ }
+
+ // Clean/smudge filter for testFiltering. The smudgetest test resources
+ // were created with C git using a clean filter sed -e "s/A/E/g" and the
+ // smudge filter sed -e "s/E/A/g". To keep the test independent of the
+ // presence of sed, implement this with a built-in filter.
private static class ReplaceFilter extends FilterCommand {
private final char toReplace;
@@ -501,8 +793,10 @@ public class PatchApplierTest {
@Test
public void testFiltering() throws Exception {
// Set up filter
- FilterCommandFactory clean = (repo, in, out) -> new ReplaceFilter(in, out, 'A', 'E');
- FilterCommandFactory smudge = (repo, in, out) -> new ReplaceFilter(in, out, 'E', 'A');
+ FilterCommandFactory clean =
+ (repo, in, out) -> new ReplaceFilter(in, out, 'A', 'E');
+ FilterCommandFactory smudge =
+ (repo, in, out) -> new ReplaceFilter(in, out, 'E', 'A');
FilterCommandRegistry.register("jgit://builtin/a2e/clean", clean);
FilterCommandRegistry.register("jgit://builtin/a2e/smudge", smudge);
Config config = db.getConfig();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
new file mode 100644
index 0000000000..97d3f81b9b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2023, Tencent.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.revwalk;
+
+import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraph.EMPTY;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.internal.storage.file.GC;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.filter.MessageRevFilter;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.junit.Test;
+
+public class RevWalkCommitGraphTest extends RevWalkTestCase {
+
+ private RevWalk rw;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ rw = new RevWalk(db);
+ }
+
+ @Test
+ public void testParseHeaders() throws Exception {
+ RevCommit c1 = commitFile("file1", "1", "master");
+
+ RevCommit notParseInGraph = rw.lookupCommit(c1);
+ rw.parseHeaders(notParseInGraph);
+ assertFalse(notParseInGraph instanceof RevCommitCG);
+ assertNotNull(notParseInGraph.getRawBuffer());
+ assertEquals(Constants.COMMIT_GENERATION_UNKNOWN,
+ notParseInGraph.getGeneration());
+
+ enableAndWriteCommitGraph();
+
+ reinitializeRevWalk();
+ RevCommit parseInGraph = rw.lookupCommit(c1);
+ parseInGraph.parseHeaders(rw);
+
+ assertTrue(parseInGraph instanceof RevCommitCG);
+ assertNotNull(parseInGraph.getRawBuffer());
+ assertEquals(1, parseInGraph.getGeneration());
+ assertEquals(notParseInGraph.getId(), parseInGraph.getId());
+ assertEquals(notParseInGraph.getTree(), parseInGraph.getTree());
+ assertEquals(notParseInGraph.getCommitTime(), parseInGraph.getCommitTime());
+ assertArrayEquals(notParseInGraph.getParents(), parseInGraph.getParents());
+
+ reinitializeRevWalk();
+ rw.setRetainBody(false);
+ RevCommit noBody = rw.lookupCommit(c1);
+ noBody.parseHeaders(rw);
+
+ assertTrue(noBody instanceof RevCommitCG);
+ assertNull(noBody.getRawBuffer());
+ assertEquals(1, noBody.getGeneration());
+ assertEquals(notParseInGraph.getId(), noBody.getId());
+ assertEquals(notParseInGraph.getTree(), noBody.getTree());
+ assertEquals(notParseInGraph.getCommitTime(), noBody.getCommitTime());
+ assertArrayEquals(notParseInGraph.getParents(), noBody.getParents());
+ }
+
+ @Test
+ public void testParseCanonical() throws Exception {
+ RevCommit c1 = commitFile("file1", "1", "master");
+ enableAndWriteCommitGraph();
+
+ RevCommit notParseInGraph = rw.lookupCommit(c1);
+ rw.parseHeaders(notParseInGraph);
+
+ reinitializeRevWalk();
+ RevCommit parseInGraph = rw.lookupCommit(c1);
+ parseInGraph.parseCanonical(rw, rw.getCachedBytes(c1));
+
+ assertTrue(parseInGraph instanceof RevCommitCG);
+ assertNotNull(parseInGraph.getRawBuffer());
+ assertEquals(1, parseInGraph.getGeneration());
+ assertEquals(notParseInGraph.getId(), parseInGraph.getId());
+ assertEquals(notParseInGraph.getTree(), parseInGraph.getTree());
+ assertEquals(notParseInGraph.getCommitTime(),
+ parseInGraph.getCommitTime());
+ assertArrayEquals(notParseInGraph.getParents(),
+ parseInGraph.getParents());
+
+ reinitializeRevWalk();
+ rw.setRetainBody(false);
+ RevCommit noBody = rw.lookupCommit(c1);
+ noBody.parseCanonical(rw, rw.getCachedBytes(c1));
+
+ assertTrue(noBody instanceof RevCommitCG);
+ assertNull(noBody.getRawBuffer());
+ assertEquals(1, noBody.getGeneration());
+ assertEquals(notParseInGraph.getId(), noBody.getId());
+ assertEquals(notParseInGraph.getTree(), noBody.getTree());
+ assertEquals(notParseInGraph.getCommitTime(), noBody.getCommitTime());
+ assertArrayEquals(notParseInGraph.getParents(), noBody.getParents());
+ }
+
+ @Test
+ public void testInitializeShallowCommits() throws Exception {
+ RevCommit c1 = commit(commit());
+ branch(c1, "master");
+ enableAndWriteCommitGraph();
+ assertCommitCntInGraph(2);
+
+ db.getObjectDatabase().setShallowCommits(Collections.singleton(c1));
+ RevCommit parseInGraph = rw.lookupCommit(c1);
+ parseInGraph.parseHeaders(rw);
+
+ assertTrue(parseInGraph instanceof RevCommitCG);
+ assertNotNull(parseInGraph.getRawBuffer());
+ assertEquals(2, parseInGraph.getGeneration());
+ assertEquals(0, parseInGraph.getParentCount());
+ }
+
+ @Test
+ public void testTreeFilter() throws Exception {
+ RevCommit c1 = commitFile("file1", "1", "master");
+ RevCommit c2 = commitFile("file2", "2", "master");
+ RevCommit c3 = commitFile("file1", "3", "master");
+ RevCommit c4 = commitFile("file2", "4", "master");
+
+ enableAndWriteCommitGraph();
+ assertCommitCntInGraph(4);
+
+ rw.markStart(rw.lookupCommit(c4));
+ rw.setTreeFilter(AndTreeFilter.create(PathFilter.create("file1"),
+ TreeFilter.ANY_DIFF));
+ assertEquals(c3, rw.next());
+ assertEquals(c1, rw.next());
+ assertNull(rw.next());
+
+ reinitializeRevWalk();
+ rw.markStart(rw.lookupCommit(c4));
+ rw.setTreeFilter(AndTreeFilter.create(PathFilter.create("file2"),
+ TreeFilter.ANY_DIFF));
+ assertEquals(c4, rw.next());
+ assertEquals(c2, rw.next());
+ assertNull(rw.next());
+ }
+
+ @Test
+ public void testWalkWithCommitMessageFilter() throws Exception {
+ RevCommit a = commit();
+ RevCommit b = commitBuilder().parent(a)
+ .message("The quick brown fox jumps over the lazy dog!")
+ .create();
+ RevCommit c = commitBuilder().parent(b).message("commit-c").create();
+ branch(c, "master");
+
+ enableAndWriteCommitGraph();
+ assertCommitCntInGraph(3);
+
+ rw.setRevFilter(MessageRevFilter.create("quick brown fox jumps"));
+ rw.markStart(rw.lookupCommit(c));
+ assertEquals(b, rw.next());
+ assertNull(rw.next());
+ }
+
+ @Test
+ public void testCommitsWalk() throws Exception {
+ RevCommit c1 = commit();
+ branch(c1, "commits/1");
+ RevCommit c2 = commit(c1);
+ branch(c2, "commits/2");
+ RevCommit c3 = commit(c2);
+ branch(c3, "commits/3");
+
+ enableAndWriteCommitGraph();
+ assertCommitCntInGraph(3);
+ testRevWalkBehavior("commits/1", "commits/3");
+
+ // add more commits
+ RevCommit c4 = commit(c1);
+ RevCommit c5 = commit(c4);
+ RevCommit c6 = commit(c1);
+ RevCommit c7 = commit(c6);
+
+ RevCommit m1 = commit(c2, c4);
+ branch(m1, "merge/1");
+ RevCommit m2 = commit(c4, c6);
+ branch(m2, "merge/2");
+ RevCommit m3 = commit(c3, c5, c7);
+ branch(m3, "merge/3");
+
+ /*
+ * <pre>
+ * current graph structure:
+ *
+ * __M3___
+ * / | \
+ * 3 M1 5 M2 7
+ * |/ \|/ \|
+ * 2 4 6
+ * |___/____/
+ * 1
+ * </pre>
+ */
+ enableAndWriteCommitGraph();
+ reinitializeRevWalk();
+ assertCommitCntInGraph(10);
+ testRevWalkBehavior("merge/1", "merge/2");
+ testRevWalkBehavior("merge/1", "merge/3");
+ testRevWalkBehavior("merge/2", "merge/3");
+
+ // add one more commit
+ RevCommit c8 = commit(m3);
+ branch(c8, "commits/8");
+
+ /*
+ * <pre>
+ * current graph structure:
+ * 8
+ * |
+ * __M3___
+ * / | \
+ * 3 M1 5 M2 7
+ * |/ \|/ \|
+ * 2 4 6
+ * |___/____/
+ * 1
+ * </pre>
+ */
+ testRevWalkBehavior("commits/8", "merge/1");
+ testRevWalkBehavior("commits/8", "merge/2");
+
+ enableAndWriteCommitGraph();
+ reinitializeRevWalk();
+ assertCommitCntInGraph(11);
+ testRevWalkBehavior("commits/8", "merge/1");
+ testRevWalkBehavior("commits/8", "merge/2");
+ }
+
+ void testRevWalkBehavior(String branch, String compare) throws Exception {
+ assertCommits(
+ travel(TreeFilter.ALL, RevFilter.MERGE_BASE, RevSort.NONE, true,
+ branch, compare),
+ travel(TreeFilter.ALL, RevFilter.MERGE_BASE, RevSort.NONE,
+ false, branch, compare));
+
+ assertCommits(
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, true,
+ branch),
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, false,
+ branch));
+
+ assertCommits(
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, true,
+ compare),
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, false,
+ compare));
+
+ assertCommits(
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC,
+ true, branch),
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC,
+ false, branch));
+
+ assertCommits(
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC,
+ true, compare),
+ travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC,
+ false, compare));
+ }
+
+ void assertCommitCntInGraph(int expect) {
+ assertEquals(expect, rw.commitGraph().getCommitCnt());
+ }
+
+ void assertCommits(List<RevCommit> expect, List<RevCommit> actual) {
+ assertEquals(expect.size(), actual.size());
+
+ for (int i = 0; i < expect.size(); i++) {
+ RevCommit c1 = expect.get(i);
+ RevCommit c2 = actual.get(i);
+
+ assertEquals(c1.getId(), c2.getId());
+ assertEquals(c1.getTree(), c2.getTree());
+ assertEquals(c1.getCommitTime(), c2.getCommitTime());
+ assertArrayEquals(c1.getParents(), c2.getParents());
+ assertArrayEquals(c1.getRawBuffer(), c2.getRawBuffer());
+ }
+ }
+
+ Ref branch(RevCommit commit, String name) throws Exception {
+ return Git.wrap(db).branchCreate().setName(name)
+ .setStartPoint(commit.name()).call();
+ }
+
+ List<RevCommit> travel(TreeFilter treeFilter, RevFilter revFilter,
+ RevSort revSort, boolean enableCommitGraph, String... starts)
+ throws Exception {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, enableCommitGraph);
+
+ try (RevWalk walk = new RevWalk(db)) {
+ walk.setTreeFilter(treeFilter);
+ walk.setRevFilter(revFilter);
+ walk.sort(revSort);
+ walk.setRetainBody(false);
+ for (String start : starts) {
+ walk.markStart(walk.lookupCommit(db.resolve(start)));
+ }
+ List<RevCommit> commits = new ArrayList<>();
+
+ if (enableCommitGraph) {
+ assertTrue(walk.commitGraph().getCommitCnt() > 0);
+ } else {
+ assertEquals(EMPTY, walk.commitGraph());
+ }
+
+ for (RevCommit commit : walk) {
+ commits.add(commit);
+ }
+ return commits;
+ }
+ }
+
+ void enableAndWriteCommitGraph() throws Exception {
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_COMMIT_GRAPH, true);
+ db.getConfig().setBoolean(ConfigConstants.CONFIG_GC_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true);
+ GC gc = new GC(db);
+ gc.gc().get();
+ }
+
+ private void reinitializeRevWalk() {
+ rw.close();
+ rw = new RevWalk(db);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
index 7e6d6f20fb..61458dddfb 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/UploadPackTest.java
@@ -2795,6 +2795,50 @@ public class UploadPackTest {
assertEquals(1, stats.getNotAdvertisedWants());
}
+ @Test
+ public void testAllowAnySha1InWantConfig() {
+ server.getConfig().setBoolean("uploadpack", null, "allowanysha1inwant",
+ true);
+
+ try (UploadPack uploadPack = new UploadPack(server)) {
+ assertEquals(RequestPolicy.ANY, uploadPack.getRequestPolicy());
+ }
+ }
+
+ @Test
+ public void testAllowReachableSha1InWantConfig() {
+ server.getConfig().setBoolean("uploadpack", null,
+ "allowreachablesha1inwant", true);
+
+ try (UploadPack uploadPack = new UploadPack(server)) {
+ assertEquals(RequestPolicy.REACHABLE_COMMIT,
+ uploadPack.getRequestPolicy());
+ }
+ }
+
+ @Test
+ public void testAllowTipSha1InWantConfig() {
+ server.getConfig().setBoolean("uploadpack", null, "allowtipsha1inwant",
+ true);
+
+ try (UploadPack uploadPack = new UploadPack(server)) {
+ assertEquals(RequestPolicy.TIP, uploadPack.getRequestPolicy());
+ }
+ }
+
+ @Test
+ public void testAllowReachableTipSha1InWantConfig() {
+ server.getConfig().setBoolean("uploadpack", null,
+ "allowreachablesha1inwant", true);
+ server.getConfig().setBoolean("uploadpack", null, "allowtipsha1inwant",
+ true);
+
+ try (UploadPack uploadPack = new UploadPack(server)) {
+ assertEquals(RequestPolicy.REACHABLE_COMMIT_TIP,
+ uploadPack.getRequestPolicy());
+ }
+ }
+
private class RefCallsCountingRepository extends InMemoryRepository {
private final InMemoryRepository.MemRefDatabase refdb;
private int numRefCalls;
@@ -2835,7 +2879,9 @@ public class UploadPackTest {
TestV2Hook hook = new TestV2Hook();
ByteArrayInputStream recvStream = uploadPackV2((UploadPack up) -> {
up.setProtocolV2Hook(hook);
- }, "command=object-info\n", "size",
+ }, "command=object-info\n",
+ PacketLineIn.delimiter(),
+ "size",
"oid " + ObjectId.toString(blob1.getId()),
"oid " + ObjectId.toString(blob2.getId()), PacketLineIn.end());
PacketLineIn pckIn = new PacketLineIn(recvStream);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java
index 648416925c..76bda6a35b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java
@@ -91,7 +91,7 @@ public class TimeoutInputStreamTest {
final byte[] exp = new byte[] { 'a', 'b', 'c' };
final byte[] act = new byte[exp.length];
out.write(exp);
- IO.readFully(is, act, 0, act.length);
+ IO.readFully(is, act);
assertArrayEquals(exp, act);
}
@@ -110,7 +110,7 @@ public class TimeoutInputStreamTest {
public void testTimeout_readBuffer_Timeout() throws IOException {
beginRead();
try {
- IO.readFully(is, new byte[512], 0, 512);
+ IO.readFully(is, new byte[512]);
fail("incorrectly read bytes");
} catch (InterruptedIOException e) {
// expected