diff options
Diffstat (limited to 'org.eclipse.jgit.test')
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 Binary files differnew file mode 100644 index 0000000000..941c0a7cec --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/commit-graph.v1 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 |