diff --git a/DEPENDENCIES b/DEPENDENCIES index bffe3d928..9dbca62b0 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -12,8 +12,8 @@ maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.9.0, Apache-2.0, approved, c maven/mavencentral/net.bytebuddy/byte-buddy/1.9.0, Apache-2.0, approved, clearlydefined maven/mavencentral/net.i2p.crypto/eddsa/0.3.0, CC0, approved, CQ17804 maven/mavencentral/net.sf.jopt-simple/jopt-simple/4.6, MIT, approved, clearlydefined -maven/mavencentral/org.apache.ant/ant-launcher/1.10.8, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560 -maven/mavencentral/org.apache.ant/ant/1.10.8, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560 +maven/mavencentral/org.apache.ant/ant-launcher/1.10.10, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560 +maven/mavencentral/org.apache.ant/ant/1.10.10, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560 maven/mavencentral/org.apache.commons/commons-compress/1.19, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.commons/commons-math3/3.2, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.13, Apache-2.0, approved, CQ22761 @@ -26,35 +26,35 @@ maven/mavencentral/org.assertj/assertj-core/3.14.0, Apache-2.0, approved, clearl maven/mavencentral/org.bouncycastle/bcpg-jdk15on/1.65, Apache-2.0, approved, CQ21975 maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.65, MIT AND LicenseRef-Public-Domain, approved, CQ21976 maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.65.01, MIT AND LicenseRef-Public-Domain, approved, CQ21977 -maven/mavencentral/org.eclipse.jetty/jetty-http/9.4.36.v20210114, , approved, eclipse -maven/mavencentral/org.eclipse.jetty/jetty-io/9.4.36.v20210114, , approved, eclipse -maven/mavencentral/org.eclipse.jetty/jetty-security/9.4.36.v20210114, , approved, eclipse -maven/mavencentral/org.eclipse.jetty/jetty-server/9.4.36.v20210114, , approved, eclipse -maven/mavencentral/org.eclipse.jetty/jetty-servlet/9.4.36.v20210114, , approved, eclipse -maven/mavencentral/org.eclipse.jetty/jetty-util-ajax/9.4.36.v20210114, , approved, eclipse -maven/mavencentral/org.eclipse.jetty/jetty-util/9.4.36.v20210114, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant.test/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.archive/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.gpg.bc/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.apache/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.server/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.test/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.http/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.ssh/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server.test/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.test/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm.test/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.test/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.jsch/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.test/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ui/5.11.0-SNAPSHOT, , approved, eclipse -maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit/5.11.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jetty/jetty-http/9.4.41.v20210516, , approved, eclipse +maven/mavencentral/org.eclipse.jetty/jetty-io/9.4.41.v20210516, , approved, eclipse +maven/mavencentral/org.eclipse.jetty/jetty-security/9.4.41.v20210516, , approved, eclipse +maven/mavencentral/org.eclipse.jetty/jetty-server/9.4.41.v20210516, , approved, eclipse +maven/mavencentral/org.eclipse.jetty/jetty-servlet/9.4.41.v20210516, , approved, eclipse +maven/mavencentral/org.eclipse.jetty/jetty-util-ajax/9.4.41.v20210516, , approved, eclipse +maven/mavencentral/org.eclipse.jetty/jetty-util/9.4.41.v20210516, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant.test/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.archive/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.gpg.bc/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.apache/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.server/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.test/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.http/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.ssh/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server.test/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.test/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm.test/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.test/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.jsch/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.test/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ui/5.12.0-SNAPSHOT, , approved, eclipse +maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit/5.12.0-SNAPSHOT, , approved, eclipse maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ7063 maven/mavencentral/org.mockito/mockito-core/2.23.0, MIT, approved, CQ17976 maven/mavencentral/org.objenesis/objenesis/2.6, Apache-2.0, approved, CQ15478 diff --git a/WORKSPACE b/WORKSPACE index 77c845168..d9c407ab3 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -237,55 +237,55 @@ maven_jar( sha1 = "9180733b7df8542621dc12e21e87557e8c99b8cb", ) -JETTY_VER = "9.4.40.v20210413" +JETTY_VER = "9.4.41.v20210516" maven_jar( name = "jetty-servlet", artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VER, - sha1 = "41abc058d311baae3fe5411223e4108af212a24a", - src_sha1 = "2e5b2319bce4c74d760106db05deed2a405041ce", + sha1 = "ea45368ea7fd04026038f89e6910f17f70939641", + src_sha1 = "4acf6b0d1449ccd39b195783e3639ab0da51f7bf", ) maven_jar( name = "jetty-security", artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VER, - sha1 = "0c2807eff66ca21b565276e69aa8502524beb204", - src_sha1 = "b4873ec0ab5acc8a383df4dc9046ad5361b5616f", + sha1 = "5ba69b1189a9d1f425ed03cbc2c901e0e6023c4d", + src_sha1 = "d46f8cb4dad66751d3a588309c6bbc15b80fbad4", ) maven_jar( name = "jetty-server", artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VER, - sha1 = "a6d22f20c863d2d7669dbc2399a1a3b25b0f8a20", - src_sha1 = "09f789d2959ea38813be6bd2b751bba9db3a4494", + sha1 = "25b1963b0a1c56202ec37046adc55861815107ce", + src_sha1 = "a7f82c9df737316cf0dfafe4a33ca4ae89d780db", ) maven_jar( name = "jetty-http", artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VER, - sha1 = "dea7e5fe28a6580d6900e77d836e650aeecfa9c8", - src_sha1 = "b161959fac6fd932031022ac3fb8b6c34a422feb", + sha1 = "0d460bece4dd9666b46cbd18f8d7fd31cf02ecd9", + src_sha1 = "6fa009d950b8fdab8e94003e6295c08d42ee85b7", ) maven_jar( name = "jetty-io", artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VER, - sha1 = "c420368a360c20b40a57897676d581462d0a54c0", - src_sha1 = "6ae54fba76b91f24ec5880202920f0a61b1b050b", + sha1 = "820eea368623939c2113902b1ca7a98186f64a73", + src_sha1 = "4373285dafb5f79210815d9c15de106cc3e9ac4d", ) maven_jar( name = "jetty-util", artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VER, - sha1 = "1ab1a4f33f293110fdfb3da1911b2a00da78019b", - src_sha1 = "9d537ad9d22c7edfac0e38ba5afc04632716dca5", + sha1 = "548c76ea00d7eb3e2bcea273174e5d030639d109", + src_sha1 = "ba188de552a0c310f69cf12bea887413ce8f0e78", ) maven_jar( name = "jetty-util-ajax", artifact = "org.eclipse.jetty:jetty-util-ajax:" + JETTY_VER, - sha1 = "62014fb386f1c3dce53029165fd76435bcb8bb6c", - src_sha1 = "99df1bf89bdd11c9caa0e56cc802b949f896e3d9", + sha1 = "d4c1d66fc62796a17548e6c344fbf89b5889f873", + src_sha1 = "b60cf77be68137eee4ee13d83c47d684d14b6d90", ) BOUNCYCASTLE_VER = "1.65" diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target index cb6dbeb6a..8cc851d65 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd index 55a09506f..6ba77b983 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.10.tpd @@ -1,7 +1,7 @@ target "jgit-4.10" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2018-12/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target index 21a6e1f18..164cd82ca 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd index 12b0f7584..f29a9fee2 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.11.tpd @@ -1,7 +1,7 @@ target "jgit-4.11" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2019-03/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target index 79cf33d40..ed8ad0b6b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd index 6fc744375..35bf3b081 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.12.tpd @@ -1,7 +1,7 @@ target "jgit-4.12" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2019-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target index bc2e3d305..993e1123a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd index 5dbcbb407..6da9b00c1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.13.tpd @@ -1,7 +1,7 @@ target "jgit-4.13" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2019-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target index 23591b12a..e8cfa6073 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd index 3da7ed46f..70dce0ce3 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.14.tpd @@ -1,7 +1,7 @@ target "jgit-4.14" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2019-12/201912181000/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target index 1a11dfff3..81c64502b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd index 49ae4eac6..edf5cec8d 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.15.tpd @@ -1,7 +1,7 @@ target "jgit-4.15" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2020-03/202003181000/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target index ae9511ac6..3afc3b8e6 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd index 4e6a74501..54e8a9649 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.16.tpd @@ -1,7 +1,7 @@ target "jgit-4.16" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2020-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target index 5578abb12..f61261fc6 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd index cad8a5c68..2b2af1a9f 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.17.tpd @@ -1,7 +1,7 @@ target "jgit-4.17" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2020-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target index 42bfa0cd2..c17c1c81a 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd index 6f0b0c359..0f42bb67f 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.18.tpd @@ -1,7 +1,7 @@ target "jgit-4.18" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2020-12/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target index 16af40036..6db7fe19f 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd index 6809f052a..5e4305c51 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.19.tpd @@ -1,7 +1,7 @@ target "jgit-4.19-staging" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/staging/2021-03/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.target index 28471a3e2..f256622f4 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.tpd index e7d4f257c..de929b56b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.20-staging.tpd @@ -1,7 +1,7 @@ target "jgit-4.20-staging" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/staging/2021-06/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target index 8aa7d0844..ca1696be3 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd index 4128209b5..96a6073e1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.6.tpd @@ -1,7 +1,7 @@ target "jgit-4.6" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/neon/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target index 178640abc..60983880e 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd index 68c6b7bea..bfb68b220 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.7.tpd @@ -1,7 +1,7 @@ target "jgit-4.7" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/oxygen/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target index e5d082998..180b81452 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd index f7ac2c0b9..e3e6b217b 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.8.tpd @@ -1,7 +1,7 @@ target "jgit-4.8" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/photon/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target index 46c2644b3..27b3096f2 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.target @@ -1,28 +1,28 @@ - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -41,8 +41,8 @@ - - + + @@ -86,7 +86,7 @@ - + diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd index c77376aae..938d80b87 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.9.tpd @@ -1,7 +1,7 @@ target "jgit-4.9" with source configurePhase include "projects/jetty-9.4.x.tpd" -include "orbit/S20210406213021.tpd" +include "orbit/R20210602031627-2021-06.tpd" location "https://download.eclipse.org/releases/2018-09/" { org.eclipse.osgi lazy diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210406213021.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210602031627-2021-06.tpd similarity index 94% rename from org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210406213021.tpd rename to org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210602031627-2021-06.tpd index 45d0fd161..83b5bb3fd 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/S20210406213021.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/orbit/R20210602031627-2021-06.tpd @@ -1,7 +1,7 @@ -target "S20210406213021" with source configurePhase +target "R20210602031627-2021-06" with source configurePhase // see https://download.eclipse.org/tools/orbit/downloads/ -location "https://download.eclipse.org/tools/orbit/downloads/drops/S20210406213021/repository" { +location "https://download.eclipse.org/tools/orbit/downloads/drops/R20210602031627/repository" { com.google.gson [2.8.6.v20201231-1626,2.8.6.v20201231-1626] com.google.gson.source [2.8.6.v20201231-1626,2.8.6.v20201231-1626] com.jcraft.jsch [0.1.55.v20190404-1902,0.1.55.v20190404-1902] @@ -18,8 +18,8 @@ location "https://download.eclipse.org/tools/orbit/downloads/drops/S202104062130 net.bytebuddy.byte-buddy.source [1.9.0.v20181107-1410,1.9.0.v20181107-1410] net.i2p.crypto.eddsa [0.3.0.v20181102-1323,0.3.0.v20181102-1323] net.i2p.crypto.eddsa.source [0.3.0.v20181102-1323,0.3.0.v20181102-1323] - org.apache.ant [1.10.9.v20201106-1946,1.10.9.v20201106-1946] - org.apache.ant.source [1.10.9.v20201106-1946,1.10.9.v20201106-1946] + org.apache.ant [1.10.10.v20210426-1926,1.10.10.v20210426-1926] + org.apache.ant.source [1.10.10.v20210426-1926,1.10.10.v20210426-1926] org.apache.commons.codec [1.14.0.v20200818-1422,1.14.0.v20200818-1422] org.apache.commons.codec.source [1.14.0.v20200818-1422,1.14.0.v20200818-1422] org.apache.commons.compress [1.19.0.v20200106-2343,1.19.0.v20200106-2343] diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd index 0f0b1005c..fb0df1e8f 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/projects/jetty-9.4.x.tpd @@ -1,22 +1,22 @@ target "jetty-9.4.x" with source configurePhase -location jetty-9.4.40 "https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.40.v20210413/" { - org.eclipse.jetty.client [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.client.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.continuation [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.continuation.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.http [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.http.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.io [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.io.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.security [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.security.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.server [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.server.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.servlet [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.servlet.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.util [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.util.source [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.util.ajax [9.4.40.v20210413,9.4.40.v20210413] - org.eclipse.jetty.util.ajax.source [9.4.40.v20210413,9.4.40.v20210413] +location jetty-9.4.40 "https://download.eclipse.org/jetty/updates/jetty-bundles-9.x/9.4.41.v20210516/" { + org.eclipse.jetty.client [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.client.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.continuation [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.continuation.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.http [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.http.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.io [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.io.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.security [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.security.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.server [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.server.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.servlet [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.servlet.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.util [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.util.source [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.util.ajax [9.4.41.v20210516,9.4.41.v20210516] + org.eclipse.jetty.util.ajax.source [9.4.41.v20210516,9.4.41.v20210516] } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RevListTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RevListTest.java new file mode 100644 index 000000000..06fddc29d --- /dev/null +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RevListTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021, kylezhao 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.pgm; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.CLIRepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Before; +import org.junit.Test; + +public class RevListTest extends CLIRepositoryTestCase { + + private Git git; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + } + + @Test + public void testWithParentsFlag() throws Exception { + List commits = createCommitsForParentsFlag(git); + String result = toString( + execute("git rev-list HEAD --parents -- Test.txt")); + + String expect = toString( + commits.get(3).name() + ' ' + commits.get(1).name(), + commits.get(1).name()); + + assertEquals(expect, result); + } + + @Test + public void testWithoutParentsFlag() throws Exception { + List commits = createCommitsForParentsFlag(git); + String result = toString(execute("git rev-list HEAD -- Test.txt")); + + String expect = toString(commits.get(3).name(), commits.get(1).name()); + + assertEquals(expect, result); + } + + private List createCommitsForParentsFlag(Git git) + throws Exception { + List commits = new ArrayList<>(); + writeTrashFile("Test1.txt", "Hello world"); + git.add().addFilepattern("Test1.txt").call(); + commits.add(git.commit().setMessage("commit#0").call()); + writeTrashFile("Test.txt", "Hello world!"); + git.add().addFilepattern("Test.txt").call(); + commits.add(git.commit().setMessage("commit#1").call()); + writeTrashFile("Test1.txt", "Hello world!!"); + git.add().addFilepattern("Test1.txt").call(); + commits.add(git.commit().setMessage("commit#2").call()); + writeTrashFile("Test.txt", "Hello world!!!"); + git.add().addFilepattern("Test.txt").call(); + commits.add(git.commit().setMessage("commit#3").call()); + return commits; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java index 822f8998b..696a924ec 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java @@ -129,6 +129,9 @@ protected void run() throws Exception { walk.setTreeFilter(AndTreeFilter.create(pathFilter, TreeFilter.ANY_DIFF)); } + if (parents) { + walk.setRewriteParents(true); + } if (revLimiter.size() == 1) walk.setRevFilter(revLimiter.get(0)); diff --git a/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java index 4be2271a8..93d85e2e9 100644 --- a/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java +++ b/org.eclipse.jgit.ssh.jsch.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2017 Google Inc. and others + * Copyright (C) 2008, 2021 Google Inc. 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 @@ -517,4 +517,76 @@ public void testEnVarSubstitution() throws Exception { assertEquals("/tmp/${TST_VAR/bar", c.getValue(SshConstants.IDENTITY_AGENT)); } + + @Test + public void testNegativeMatch() throws Exception { + config("Host foo.bar !foobar.baz *.baz\n" + "Port 29418\n"); + Host h = osc.lookup("foo.bar"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + h = osc.lookup("foobar.baz"); + assertNotNull(h); + assertEquals(22, h.getPort()); + h = osc.lookup("foo.baz"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + } + + @Test + public void testNegativeMatch2() throws Exception { + // Negative match after the positive match. + config("Host foo.bar *.baz !foobar.baz\n" + "Port 29418\n"); + Host h = osc.lookup("foo.bar"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + h = osc.lookup("foobar.baz"); + assertNotNull(h); + assertEquals(22, h.getPort()); + h = osc.lookup("foo.baz"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + } + + @Test + public void testNoMatch() throws Exception { + config("Host !host1 !host2\n" + "Port 29418\n"); + Host h = osc.lookup("host1"); + assertNotNull(h); + assertEquals(22, h.getPort()); + h = osc.lookup("host2"); + assertNotNull(h); + assertEquals(22, h.getPort()); + h = osc.lookup("host3"); + assertNotNull(h); + assertEquals(22, h.getPort()); + } + + @Test + public void testMultipleMatch() throws Exception { + config("Host foo.bar\nPort 29418\nIdentityFile /foo\n\n" + + "Host *.bar\nPort 22\nIdentityFile /bar\n" + + "Host foo.bar\nPort 47\nIdentityFile /baz\n"); + Host h = osc.lookup("foo.bar"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + assertArrayEquals(new Object[] { "/foo", "/bar", "/baz" }, + h.getConfig().getValues("IdentityFile")); + } + + @Test + public void testWhitespace() throws Exception { + config("Host foo \tbar baz\nPort 29418\n"); + Host h = osc.lookup("foo"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + h = osc.lookup("bar"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + h = osc.lookup("baz"); + assertNotNull(h); + assertEquals(29418, h.getPort()); + h = osc.lookup("\tbar"); + assertNotNull(h); + assertEquals(22, h.getPort()); + } } diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs index 6a9621db1..cddb99d1d 100644 --- a/org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs +++ b/org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs @@ -1,5 +1,6 @@ -#Sat Dec 20 21:21:24 CET 2008 eclipse.preferences.version=1 +encoding//tst-rsrc/org/eclipse/jgit/diff/umlaut.patch=ISO-8859-1 +encoding//tst-rsrc/org/eclipse/jgit/diff/umlaut_PostImage=ISO-8859-1 encoding//tst-rsrc/org/eclipse/jgit/patch/testGetText_BothISO88591.patch=ISO-8859-1 encoding//tst-rsrc/org/eclipse/jgit/patch/testGetText_Convert.patch=ISO-8859-1 encoding//tst-rsrc/org/eclipse/jgit/patch/testGetText_DiffCc.patch=ISO-8859-1 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/.gitattributes b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/.gitattributes index c5831d9a8..28caa2f82 100644 Binary files a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/.gitattributes and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/.gitattributes differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf.patch new file mode 100644 index 000000000..01eb0b951 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2.patch new file mode 100644 index 000000000..5a6210489 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PostImage new file mode 100644 index 000000000..91e246d06 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PreImage new file mode 100644 index 000000000..05c1c782e Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf2_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3.patch new file mode 100644 index 000000000..b155148b3 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PostImage new file mode 100644 index 000000000..05c1c782e Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf3_PreImage new file mode 100644 index 000000000..e69de29bb diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4.patch new file mode 100644 index 000000000..0cf606390 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4_PostImage new file mode 100644 index 000000000..05c1c782e Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf4_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PostImage new file mode 100644 index 000000000..91e246d06 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PreImage new file mode 100644 index 000000000..05c1c782e Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/crlf_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta.patch new file mode 100644 index 000000000..84855308a Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta_PostImage new file mode 100644 index 000000000..8b370bb5f Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta_PreImage new file mode 100644 index 000000000..b4527005b Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/delta_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine.patch new file mode 100644 index 000000000..18c80c4fe Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine_PostImage new file mode 100644 index 000000000..45c2c9ba5 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine_PreImage new file mode 100644 index 000000000..1fd3fa23b Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/emptyLine_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello.patch new file mode 100644 index 000000000..f015a3806 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello_PostImage new file mode 100644 index 000000000..0abaeaa99 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello_PreImage new file mode 100644 index 000000000..b6fc4c620 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/hello_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal.patch new file mode 100644 index 000000000..c8811d5bb Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_PostImage new file mode 100644 index 000000000..74e4201af Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_PreImage new file mode 100644 index 000000000..799df8578 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_add.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_add.patch new file mode 100644 index 000000000..bb6a9e42a Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_add.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_add_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_add_PostImage new file mode 100644 index 000000000..799df8578 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/literal_add_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest.patch new file mode 100644 index 000000000..ab4d4265a Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PostImage new file mode 100644 index 000000000..ad630893d Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PreImage new file mode 100644 index 000000000..9bbd8c763 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/smudgetest_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut.patch new file mode 100644 index 000000000..7380dbed8 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut.patch differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut_PostImage new file mode 100644 index 000000000..557f72f51 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut_PostImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut_PreImage new file mode 100644 index 000000000..003a05411 Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/umlaut_PreImage differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/io/delta1.forward b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/io/delta1.forward new file mode 100644 index 000000000..878b167ae Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/io/delta1.forward differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/io/delta1.reverse b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/io/delta1.reverse new file mode 100644 index 000000000..7ff7a08ad Binary files /dev/null and b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/util/io/delta1.reverse differ diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java index 055eba718..867310b60 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ApplyCommandTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2020 IBM Corporation and others + * Copyright (C) 2011, 2021 IBM Corporation 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 @@ -9,6 +9,7 @@ */ package org.eclipse.jgit.api; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -18,11 +19,20 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import org.eclipse.jgit.api.errors.PatchApplyException; import org.eclipse.jgit.api.errors.PatchFormatException; +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandFactory; +import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.util.IO; import org.junit.Test; public class ApplyCommandTest extends RepositoryTestCase { @@ -57,6 +67,260 @@ private ApplyResult init(final String name, final boolean preExists, } } + @Test + public void testCrLf() throws Exception { + try { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, true); + ApplyResult result = init("crlf", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "crlf"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "crlf"), + b.getString(0, b.size(), false)); + } finally { + db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF); + } + } + + @Test + public void testCrLfOff() throws Exception { + try { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, false); + ApplyResult result = init("crlf", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "crlf"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "crlf"), + b.getString(0, b.size(), false)); + } finally { + db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF); + } + } + + @Test + public void testCrLfEmptyCommitted() throws Exception { + try { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, true); + ApplyResult result = init("crlf3", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "crlf3"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "crlf3"), + b.getString(0, b.size(), false)); + } finally { + db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF); + } + } + + @Test + public void testCrLfNewFile() throws Exception { + try { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, true); + ApplyResult result = init("crlf4", false, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "crlf4"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "crlf4"), + b.getString(0, b.size(), false)); + } finally { + db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF); + } + } + + @Test + public void testPatchWithCrLf() throws Exception { + try { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, false); + ApplyResult result = init("crlf2", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "crlf2"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "crlf2"), + b.getString(0, b.size(), false)); + } finally { + db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF); + } + } + + @Test + public void testPatchWithCrLf2() throws Exception { + String name = "crlf2"; + try (Git git = new Git(db)) { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, false); + a = new RawText(readFile(name + "_PreImage")); + write(new File(db.getWorkTree(), name), + a.getString(0, a.size(), false)); + + git.add().addFilepattern(name).call(); + git.commit().setMessage("PreImage").call(); + + b = new RawText(readFile(name + "_PostImage")); + + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF, true); + ApplyResult result = git.apply() + .setPatch(getTestResource(name + ".patch")).call(); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), name), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), name), + b.getString(0, b.size(), false)); + } finally { + db.getConfig().unset(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOCRLF); + } + } + + // 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; + + private final char replacement; + + ReplaceFilter(InputStream in, OutputStream out, char toReplace, + char replacement) { + super(in, out); + this.toReplace = toReplace; + this.replacement = replacement; + } + + @Override + public int run() throws IOException { + int b = in.read(); + if (b < 0) { + in.close(); + out.close(); + return -1; + } + if ((b & 0xFF) == toReplace) { + b = replacement; + } + out.write(b); + return 1; + } + } + + @Test + public void testFiltering() throws Exception { + // Set up filter + FilterCommandFactory clean = (repo, in, out) -> { + return new ReplaceFilter(in, out, 'A', 'E'); + }; + FilterCommandFactory smudge = (repo, in, out) -> { + return new ReplaceFilter(in, out, 'E', 'A'); + }; + FilterCommandRegistry.register("jgit://builtin/a2e/clean", clean); + FilterCommandRegistry.register("jgit://builtin/a2e/smudge", smudge); + try (Git git = new Git(db)) { + Config config = db.getConfig(); + config.setString(ConfigConstants.CONFIG_FILTER_SECTION, "a2e", + "clean", "jgit://builtin/a2e/clean"); + config.setString(ConfigConstants.CONFIG_FILTER_SECTION, "a2e", + "smudge", "jgit://builtin/a2e/smudge"); + write(new File(db.getWorkTree(), ".gitattributes"), + "smudgetest filter=a2e"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("Attributes").call(); + ApplyResult result = init("smudgetest", true, true); + assertEquals(1, result.getUpdatedFiles().size()); + assertEquals(new File(db.getWorkTree(), "smudgetest"), + result.getUpdatedFiles().get(0)); + checkFile(new File(db.getWorkTree(), "smudgetest"), + b.getString(0, b.size(), false)); + + } finally { + // Tear down filter + FilterCommandRegistry.unregister("jgit://builtin/a2e/clean"); + FilterCommandRegistry.unregister("jgit://builtin/a2e/smudge"); + } + } + + private void checkBinary(String name, boolean hasPreImage) + throws Exception { + checkBinary(name, hasPreImage, 1); + } + + private void checkBinary(String name, boolean hasPreImage, + int numberOfFiles) throws Exception { + try (Git git = new Git(db)) { + byte[] post = IO + .readWholeStream(getTestResource(name + "_PostImage"), 0) + .array(); + File f = new File(db.getWorkTree(), name); + if (hasPreImage) { + byte[] pre = IO + .readWholeStream(getTestResource(name + "_PreImage"), 0) + .array(); + Files.write(f.toPath(), pre); + git.add().addFilepattern(name).call(); + git.commit().setMessage("PreImage").call(); + } + ApplyResult result = git.apply() + .setPatch(getTestResource(name + ".patch")).call(); + assertEquals(numberOfFiles, result.getUpdatedFiles().size()); + assertEquals(f, result.getUpdatedFiles().get(0)); + assertArrayEquals(post, Files.readAllBytes(f.toPath())); + } + } + + @Test + public void testBinaryDelta() throws Exception { + checkBinary("delta", true); + } + + @Test + public void testBinaryLiteral() throws Exception { + checkBinary("literal", true); + } + + @Test + public void testBinaryLiteralAdd() throws Exception { + checkBinary("literal_add", false); + } + + @Test + public void testEncodingChange() throws Exception { + // This is a text patch that changes a file containing ÄÖÜ in UTF-8 to + // the same characters in ISO-8859-1. The patch file itself uses mixed + // encoding. Since checkFile() works with strings use the binary check. + checkBinary("umlaut", true); + } + + @Test + public void testEmptyLine() throws Exception { + // C git accepts completely empty lines as empty context lines. + // According to comments in the C git sources (apply.c), newer GNU diff + // may produce such diffs. + checkBinary("emptyLine", true); + } + + @Test + public void testMultiFileNoNewline() throws Exception { + // This test needs two files. One is in the test resources. + try (Git git = new Git(db)) { + Files.write(db.getWorkTree().toPath().resolve("yello"), + "yello".getBytes(StandardCharsets.US_ASCII)); + git.add().addFilepattern("yello").call(); + git.commit().setMessage("yello").call(); + } + checkBinary("hello", true, 2); + } + @Test public void testAddA1() throws Exception { ApplyResult result = init("A1", false, true); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java index 2ea3cd7eb..5edb60ce3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java @@ -579,6 +579,57 @@ public void testInexactRename_LargeFile() throws Exception { assertDelete(PATH_Q, bId, FileMode.REGULAR_FILE, entries.get(1)); } + @Test + public void testExactRenameForBinaryFile_isIdentified() throws Exception { + ObjectId aId = blob("a\nb\nc\n\0\0\0\0d\n"); + + DiffEntry a = DiffEntry.add(PATH_A, aId); + DiffEntry b = DiffEntry.delete(PATH_Q, aId); + + rd.add(a); + rd.add(b); + + List entries = rd.compute(); + assertEquals(1, entries.size()); + assertRename(b, a, 100, entries.get(0)); + } + + @Test + public void testInexactRenameForBinaryFile_identifiedByDefault() throws Exception { + ObjectId aId = blob("a\nb\nc\n\0\0\0\0d\n"); + ObjectId bId = blob("a\nb\nc\n\0\0\0d\n"); + + DiffEntry a = DiffEntry.add(PATH_A, aId); + DiffEntry b = DiffEntry.delete(PATH_Q, bId); + + rd.add(a); + rd.add(b); + rd.setRenameScore(40); + + List entries = rd.compute(); + assertEquals(1, entries.size()); + assertRename(b, a, 50, entries.get(0)); + } + + @Test + public void testInexactRenameForBinaryFile_notIdentifiedIfSkipParameterSet() throws Exception { + ObjectId aId = blob("a\nb\nc\n\0\0\0\0d\n"); + ObjectId bId = blob("a\nb\nc\n\0\0\0d\n"); + + DiffEntry a = DiffEntry.add(PATH_A, aId); + DiffEntry b = DiffEntry.delete(PATH_Q, bId); + + rd.add(a); + rd.add(b); + rd.setRenameScore(40); + rd.setSkipContentRenamesForBinaryFiles(true); + + List entries = rd.compute(); + assertEquals(2, entries.size()); + assertAdd(PATH_A, aId, FileMode.REGULAR_FILE, entries.get(0)); + assertDelete(PATH_Q, bId, FileMode.REGULAR_FILE, entries.get(1)); + } + @Test public void testSetRenameScore_IllegalArgs() throws Exception { try { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java index ae4db0b7d..509adc2cb 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java @@ -46,6 +46,8 @@ import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; import org.junit.Test; public class RepoCommandTest extends RepositoryTestCase { @@ -749,9 +751,55 @@ public void testRevisionBare() throws Exception { String gitlink = localDb.resolve(Constants.HEAD + ":foo").name(); assertEquals("The gitlink is same as remote head", oldCommitId.name(), gitlink); + + File dotmodules = new File(localDb.getWorkTree(), + Constants.DOT_GIT_MODULES); + assertTrue(dotmodules.exists()); + // The .gitmodules file should have "branch" lines + String gitModulesContents = RawParseUtils + .decode(IO.readFully(dotmodules)); + assertTrue(gitModulesContents.contains("branch = branch")); } } + @Test + public void testRevisionBare_ignoreTags() throws Exception { + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testReplaceManifestBare"); + File dotmodules; + try (Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository()) { + dotmodules = new File(localDb.getWorkTree(), + Constants.DOT_GIT_MODULES); + assertTrue(dotmodules.exists()); + } + + // The .gitmodules file should not have "branch" lines + String gitModulesContents = RawParseUtils + .decode(IO.readFully(dotmodules)); + assertFalse(gitModulesContents.contains("branch")); + assertTrue(gitModulesContents.contains("ref = refs/tags/" + TAG)); + } + @Test public void testCopyFileBare() throws Exception { Repository remoteDb = createBareRepository(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java index 25cd227f0..6357a0b9a 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BatchRefUpdateTest.java @@ -190,14 +190,27 @@ public void simpleNoForce() throws IOException { if (atomic) { assertResults(cmds, TRANSACTION_ABORTED, REJECTED_NONFASTFORWARD); assertRefs("refs/heads/master", A, "refs/heads/masters", B); - assertEquals(1, refsChangedEvents); } else { assertResults(cmds, OK, REJECTED_NONFASTFORWARD); assertRefs("refs/heads/master", B, "refs/heads/masters", B); - assertEquals(2, refsChangedEvents); } } + @Test + public void simpleNoForceRefsChangedEvents() throws IOException { + writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(B, A, "refs/heads/masters", + UPDATE_NONFASTFORWARD)); + execute(newBatchUpdate(cmds)); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 1, refsChangedEvents); + } + @Test public void simpleForce() throws IOException { writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B); @@ -210,7 +223,21 @@ public void simpleForce() throws IOException { assertResults(cmds, OK, OK); assertRefs("refs/heads/master", B, "refs/heads/masters", A); - assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents); + } + + @Test + public void simpleForceRefsChangedEvents() throws IOException { + writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(B, A, "refs/heads/masters", + UPDATE_NONFASTFORWARD)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); } @Test @@ -232,7 +259,27 @@ public boolean isMergedInto(RevCommit base, RevCommit tip) { assertResults(cmds, OK); assertRefs("refs/heads/master", A); - assertEquals(2, refsChangedEvents); + } + + @Test + public void nonFastForwardDoesNotDoExpensiveMergeCheckRefsChangedEvents() + throws IOException { + writeLooseRef("refs/heads/master", B); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList(new ReceiveCommand(B, A, + "refs/heads/master", UPDATE_NONFASTFORWARD)); + try (RevWalk rw = new RevWalk(diskRepo) { + @Override + public boolean isMergedInto(RevCommit base, RevCommit tip) { + throw new AssertionError("isMergedInto() should not be called"); + } + }) { + newBatchUpdate(cmds).setAllowNonFastForwards(true).execute(rw, + new StrictWorkMonitor()); + } + + assertEquals(initialRefsChangedEvents + 1, refsChangedEvents); } @Test @@ -251,16 +298,29 @@ public void fileDirectoryConflict() throws IOException { assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED, TRANSACTION_ABORTED); assertRefs("refs/heads/master", A, "refs/heads/masters", B); - assertEquals(1, refsChangedEvents); } else { // Non-atomic updates are applied in order: master succeeds, then // master/x fails due to conflict. assertResults(cmds, OK, LOCK_FAILURE, LOCK_FAILURE); assertRefs("refs/heads/master", B, "refs/heads/masters", B); - assertEquals(2, refsChangedEvents); } } + @Test + public void fileDirectoryConflictRefsChangedEvents() throws IOException { + writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), A, "refs/heads/master/x", CREATE), + new ReceiveCommand(zeroId(), A, "refs/heads", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 1, refsChangedEvents); + } + @Test public void conflictThanksToDelete() throws IOException { writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B); @@ -273,15 +333,21 @@ public void conflictThanksToDelete() throws IOException { assertResults(cmds, OK, OK, OK); assertRefs("refs/heads/master", B, "refs/heads/masters/x", A); - if (atomic) { - assertEquals(2, refsChangedEvents); - } else if (!useReftable) { - // The non-atomic case actually produces 5 events, but that's an - // implementation detail. We expect at least 4 events, one for the - // initial read due to writeLooseRef(), and then one for each - // successful ref update. - assertTrue(refsChangedEvents >= 4); - } + } + + @Test + public void conflictThanksToDeleteRefsChangedEvents() throws IOException { + writeLooseRefs("refs/heads/master", A, "refs/heads/masters", B); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), A, "refs/heads/masters/x", CREATE), + new ReceiveCommand(B, zeroId(), "refs/heads/masters", DELETE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 3, refsChangedEvents); } @Test @@ -298,14 +364,28 @@ public void updateToMissingObject() throws IOException { if (atomic) { assertResults(cmds, REJECTED_MISSING_OBJECT, TRANSACTION_ABORTED); assertRefs("refs/heads/master", A); - assertEquals(1, refsChangedEvents); } else { assertResults(cmds, REJECTED_MISSING_OBJECT, OK); assertRefs("refs/heads/master", A, "refs/heads/foo2", B); - assertEquals(2, refsChangedEvents); } } + @Test + public void updateToMissingObjectRefsChangedEvents() throws IOException { + writeLooseRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; + + ObjectId bad = ObjectId + .fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + List cmds = Arrays.asList( + new ReceiveCommand(A, bad, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 1, refsChangedEvents); + } + @Test public void addMissingObject() throws IOException { writeLooseRef("refs/heads/master", A); @@ -320,14 +400,28 @@ public void addMissingObject() throws IOException { if (atomic) { assertResults(cmds, TRANSACTION_ABORTED, REJECTED_MISSING_OBJECT); assertRefs("refs/heads/master", A); - assertEquals(1, refsChangedEvents); } else { assertResults(cmds, OK, REJECTED_MISSING_OBJECT); assertRefs("refs/heads/master", B); - assertEquals(2, refsChangedEvents); } } + @Test + public void addMissingObjectRefsChangedEvents() throws IOException { + writeLooseRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; + + ObjectId bad = ObjectId + .fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), bad, "refs/heads/foo2", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true), false); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 1, refsChangedEvents); + } + @Test public void oneNonExistentRef() throws IOException { List cmds = Arrays.asList( @@ -358,14 +452,26 @@ public void oneRefWrongOldValue() throws IOException { if (atomic) { assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED); assertRefs("refs/heads/master", A); - assertEquals(1, refsChangedEvents); } else { assertResults(cmds, LOCK_FAILURE, OK); assertRefs("refs/heads/master", A, "refs/heads/foo2", B); - assertEquals(2, refsChangedEvents); } } + @Test + public void oneRefWrongOldValueRefsChangedEvents() throws IOException { + writeLooseRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(B, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/foo2", CREATE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 1, refsChangedEvents); + } + @Test public void nonExistentRef() throws IOException { writeLooseRef("refs/heads/master", A); @@ -378,17 +484,31 @@ public void nonExistentRef() throws IOException { if (atomic) { assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE); assertRefs("refs/heads/master", A); - assertEquals(1, refsChangedEvents); } else { assertResults(cmds, OK, LOCK_FAILURE); assertRefs("refs/heads/master", B); - assertEquals(2, refsChangedEvents); } } + @Test + public void nonExistentRefRefsChangedEvents() throws IOException { + writeLooseRef("refs/heads/master", A); + + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(A, zeroId(), "refs/heads/foo2", DELETE)); + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 1, refsChangedEvents); + } + @Test public void noRefLog() throws IOException { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; Map oldLogs = getLastReflogs("refs/heads/master", "refs/heads/branch"); @@ -402,7 +522,8 @@ public void noRefLog() throws IOException { assertResults(cmds, OK, OK); assertRefs("refs/heads/master", B, "refs/heads/branch", B); - assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); assertReflogUnchanged(oldLogs, "refs/heads/master"); assertReflogUnchanged(oldLogs, "refs/heads/branch"); } @@ -411,6 +532,7 @@ public void noRefLog() throws IOException { public void reflogDefaultIdent() throws IOException { writeRef("refs/heads/master", A); writeRef("refs/heads/branch2", A); + int initialRefsChangedEvents = refsChangedEvents; Map oldLogs = getLastReflogs("refs/heads/master", "refs/heads/branch1", "refs/heads/branch2"); @@ -423,7 +545,8 @@ public void reflogDefaultIdent() throws IOException { assertResults(cmds, OK, OK); assertRefs("refs/heads/master", B, "refs/heads/branch1", B, "refs/heads/branch2", A); - assertEquals(batchesRefUpdates() ? 3 : 4, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); assertReflogEquals(reflog(A, B, new PersonIdent(diskRepo), "a reflog"), getLastReflog("refs/heads/master")); assertReflogEquals( @@ -436,6 +559,7 @@ public void reflogDefaultIdent() throws IOException { public void reflogAppendStatusNoMessage() throws IOException { writeRef("refs/heads/master", A); writeRef("refs/heads/branch1", B); + int initialRefsChangedEvents = refsChangedEvents; List cmds = Arrays.asList( new ReceiveCommand(A, B, "refs/heads/master", UPDATE), @@ -448,7 +572,8 @@ public void reflogAppendStatusNoMessage() throws IOException { assertResults(cmds, OK, OK, OK); assertRefs("refs/heads/master", B, "refs/heads/branch1", A, "refs/heads/branch2", A); - assertEquals(batchesRefUpdates() ? 3 : 5, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 3, refsChangedEvents); assertReflogEquals( // Always forced; setAllowNonFastForwards(true) bypasses the // check. @@ -465,6 +590,7 @@ public void reflogAppendStatusNoMessage() throws IOException { @Test public void reflogAppendStatusFastForward() throws IOException { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; List cmds = Arrays .asList(new ReceiveCommand(A, B, "refs/heads/master", UPDATE)); @@ -472,7 +598,7 @@ public void reflogAppendStatusFastForward() throws IOException { assertResults(cmds, OK); assertRefs("refs/heads/master", B); - assertEquals(2, refsChangedEvents); + assertEquals(initialRefsChangedEvents + 1, refsChangedEvents); assertReflogEquals( reflog(A, B, new PersonIdent(diskRepo), "fast-forward"), getLastReflog("refs/heads/master")); @@ -481,6 +607,7 @@ public void reflogAppendStatusFastForward() throws IOException { @Test public void reflogAppendStatusWithMessage() throws IOException { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; List cmds = Arrays.asList( new ReceiveCommand(A, B, "refs/heads/master", UPDATE), @@ -489,7 +616,8 @@ public void reflogAppendStatusWithMessage() throws IOException { assertResults(cmds, OK, OK); assertRefs("refs/heads/master", B, "refs/heads/branch", A); - assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); assertReflogEquals( reflog(A, B, new PersonIdent(diskRepo), "a reflog: fast-forward"), @@ -503,6 +631,7 @@ public void reflogAppendStatusWithMessage() throws IOException { @Test public void reflogCustomIdent() throws IOException { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; List cmds = Arrays.asList( new ReceiveCommand(A, B, "refs/heads/master", UPDATE), @@ -513,7 +642,8 @@ public void reflogCustomIdent() throws IOException { .setRefLogIdent(ident)); assertResults(cmds, OK, OK); - assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); assertRefs("refs/heads/master", B, "refs/heads/branch", B); assertReflogEquals(reflog(A, B, ident, "a reflog"), getLastReflog("refs/heads/master"), true); @@ -525,6 +655,7 @@ public void reflogCustomIdent() throws IOException { public void reflogDelete() throws IOException { writeRef("refs/heads/master", A); writeRef("refs/heads/branch", A); + int initialRefsChangedEvents = refsChangedEvents; assertEquals(2, getLastReflogs("refs/heads/master", "refs/heads/branch") .size()); @@ -535,7 +666,8 @@ public void reflogDelete() throws IOException { assertResults(cmds, OK, OK); assertRefs("refs/heads/branch", B); - assertEquals(batchesRefUpdates() ? 3 : 4, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); if (useReftable) { // reftable retains reflog entries for deleted branches. assertReflogEquals( @@ -551,6 +683,7 @@ public void reflogDelete() throws IOException { @Test public void reflogFileDirectoryConflict() throws IOException { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; List cmds = Arrays.asList( new ReceiveCommand(A, zeroId(), "refs/heads/master", DELETE), @@ -559,7 +692,8 @@ public void reflogFileDirectoryConflict() throws IOException { assertResults(cmds, OK, OK); assertRefs("refs/heads/master/x", A); - assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); if (!useReftable) { // reftable retains reflog entries for deleted branches. assertNull(getLastReflog("refs/heads/master")); @@ -572,6 +706,7 @@ public void reflogFileDirectoryConflict() throws IOException { @Test public void reflogOnLockFailure() throws IOException { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; Map oldLogs = getLastReflogs("refs/heads/master", "refs/heads/branch"); @@ -583,12 +718,12 @@ public void reflogOnLockFailure() throws IOException { if (atomic) { assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE); - assertEquals(1, refsChangedEvents); + assertEquals(initialRefsChangedEvents, refsChangedEvents); assertReflogUnchanged(oldLogs, "refs/heads/master"); assertReflogUnchanged(oldLogs, "refs/heads/branch"); } else { assertResults(cmds, OK, LOCK_FAILURE); - assertEquals(2, refsChangedEvents); + assertEquals(initialRefsChangedEvents + 1, refsChangedEvents); assertReflogEquals( reflog(A, B, new PersonIdent(diskRepo), "a reflog"), getLastReflog("refs/heads/master")); @@ -599,6 +734,7 @@ public void reflogOnLockFailure() throws IOException { @Test public void overrideRefLogMessage() throws Exception { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; List cmds = Arrays.asList( new ReceiveCommand(A, B, "refs/heads/master", UPDATE), @@ -609,7 +745,8 @@ public void overrideRefLogMessage() throws Exception { .setRefLogMessage("a reflog", true)); assertResults(cmds, OK, OK); - assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); assertReflogEquals(reflog(A, B, ident, "custom log"), getLastReflog("refs/heads/master"), true); assertReflogEquals(reflog(zeroId(), B, ident, "a reflog: created"), @@ -619,6 +756,7 @@ public void overrideRefLogMessage() throws Exception { @Test public void overrideDisableRefLog() throws Exception { writeRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; Map oldLogs = getLastReflogs("refs/heads/master", "refs/heads/branch"); @@ -630,7 +768,8 @@ public void overrideDisableRefLog() throws Exception { execute(newBatchUpdate(cmds).setRefLogMessage("a reflog", true)); assertResults(cmds, OK, OK); - assertEquals(batchesRefUpdates() ? 2 : 3, refsChangedEvents); + assertEquals(batchesRefUpdates() ? initialRefsChangedEvents + 1 + : initialRefsChangedEvents + 2, refsChangedEvents); assertReflogUnchanged(oldLogs, "refs/heads/master"); assertReflogEquals( reflog(zeroId(), B, new PersonIdent(diskRepo), @@ -641,6 +780,7 @@ public void overrideDisableRefLog() throws Exception { @Test public void refLogNotWrittenWithoutConfigOption() throws Exception { assumeFalse(useReftable); + setLogAllRefUpdates(false); writeRef("refs/heads/master", A); @@ -661,6 +801,7 @@ public void refLogNotWrittenWithoutConfigOption() throws Exception { @Test public void forceRefLogInUpdate() throws Exception { assumeFalse(useReftable); + setLogAllRefUpdates(false); writeRef("refs/heads/master", A); assertTrue(getLastReflogs("refs/heads/master", "refs/heads/branch") @@ -683,6 +824,7 @@ public void forceRefLogInUpdate() throws Exception { @Test public void forceRefLogInCommand() throws Exception { assumeFalse(useReftable); + setLogAllRefUpdates(false); writeRef("refs/heads/master", A); @@ -723,19 +865,39 @@ public void packedRefsLockFailure() throws Exception { if (atomic) { assertResults(cmds, LOCK_FAILURE, TRANSACTION_ABORTED); assertRefs("refs/heads/master", A); - assertEquals(1, refsChangedEvents); } else { // Only operates on loose refs, doesn't care that packed-refs is // locked. assertResults(cmds, OK, OK); assertRefs("refs/heads/master", B, "refs/heads/branch", B); - assertEquals(3, refsChangedEvents); } } finally { myLock.unlock(); } } + @Test + public void packedRefsLockFailureRefsChangedEvents() throws Exception { + assumeFalse(useReftable); + + writeLooseRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + + LockFile myLock = refdir.lockPackedRefs(); + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 2, refsChangedEvents); + } finally { + myLock.unlock(); + } + } + @Test public void oneRefLockFailure() throws Exception { assumeFalse(useReftable); @@ -757,20 +919,42 @@ public void oneRefLockFailure() throws Exception { if (atomic) { assertResults(cmds, TRANSACTION_ABORTED, LOCK_FAILURE); assertRefs("refs/heads/master", A); - assertEquals(1, refsChangedEvents); } else { assertResults(cmds, OK, LOCK_FAILURE); assertRefs("refs/heads/branch", B, "refs/heads/master", A); - assertEquals(2, refsChangedEvents); } } finally { myLock.unlock(); } } + @Test + public void oneRefLockFailureRefsChangedEvents() throws Exception { + assumeFalse(useReftable); + + writeLooseRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE), + new ReceiveCommand(A, B, "refs/heads/master", UPDATE)); + + LockFile myLock = new LockFile(refdir.fileFor("refs/heads/master")); + assertTrue(myLock.lock()); + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertEquals(atomic ? initialRefsChangedEvents + : initialRefsChangedEvents + 1, refsChangedEvents); + } finally { + myLock.unlock(); + } + } + @Test public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception { assumeFalse(useReftable); + writeLooseRef("refs/heads/master", A); List cmds = Arrays @@ -782,13 +966,33 @@ public void singleRefUpdateDoesNotRequirePackedRefsLock() throws Exception { assertFalse(getLockFile("refs/heads/master").exists()); assertResults(cmds, OK); - assertEquals(2, refsChangedEvents); assertRefs("refs/heads/master", B); } finally { myLock.unlock(); } } + @Test + public void singleRefUpdateDoesNotRequirePackedRefsLockRefsChangedEvents() + throws Exception { + assumeFalse(useReftable); + + writeLooseRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays + .asList(new ReceiveCommand(A, B, "refs/heads/master", UPDATE)); + + LockFile myLock = refdir.lockPackedRefs(); + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + + assertEquals(initialRefsChangedEvents + 1, refsChangedEvents); + } finally { + myLock.unlock(); + } + } + @Test public void atomicUpdateRespectsInProcessLock() throws Exception { assumeTrue(atomic); @@ -838,10 +1042,56 @@ public void atomicUpdateRespectsInProcessLock() throws Exception { } assertResults(cmds, OK, OK); - assertEquals(2, refsChangedEvents); assertRefs("refs/heads/master", B, "refs/heads/branch", B); } + @Test + public void atomicUpdateRespectsInProcessLockRefsChangedEvents() + throws Exception { + assumeTrue(atomic); + assumeFalse(useReftable); + + writeLooseRef("refs/heads/master", A); + int initialRefsChangedEvents = refsChangedEvents; + + List cmds = Arrays.asList( + new ReceiveCommand(A, B, "refs/heads/master", UPDATE), + new ReceiveCommand(zeroId(), B, "refs/heads/branch", CREATE)); + + Thread t = new Thread(() -> { + try { + execute(newBatchUpdate(cmds).setAllowNonFastForwards(true)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + ReentrantLock l = refdir.inProcessPackedRefsLock; + l.lock(); + try { + t.start(); + long timeoutSecs = 10; + + // Hold onto the lock until we observe the worker thread has + // attempted to + // acquire it. + while (l.getQueueLength() == 0) { + Thread.sleep(3); + } + + // Once we unlock, the worker thread should finish the update + // promptly. + l.unlock(); + t.join(SECONDS.toMillis(timeoutSecs)); + } finally { + if (l.isHeldByCurrentThread()) { + l.unlock(); + } + } + + assertEquals(initialRefsChangedEvents + 1, refsChangedEvents); + } + private void setLogAllRefUpdates(boolean enable) throws Exception { StoredConfig cfg = diskRepo.getConfig(); cfg.load(); @@ -855,6 +1105,11 @@ private void writeLooseRef(String name, AnyObjectId id) throws IOException { writeRef(name, id); } else { write(new File(diskRepo.getDirectory(), name), id.name() + "\n"); + // force the refs-changed event to be fired for the loose ref that + // was created. We do this to get the events fired during the test + // 'setup' out of the way and this allows us to now accurately + // assert only for the new events fired during the BatchRefUpdate. + refdir.exactRef(name); } } @@ -957,11 +1212,11 @@ private void assertRefs(Object... args) throws IOException { } enum Result { - OK(ReceiveCommand.Result.OK), LOCK_FAILURE( - ReceiveCommand.Result.LOCK_FAILURE), REJECTED_NONFASTFORWARD( - ReceiveCommand.Result.REJECTED_NONFASTFORWARD), REJECTED_MISSING_OBJECT( - ReceiveCommand.Result.REJECTED_MISSING_OBJECT), TRANSACTION_ABORTED( - ReceiveCommand::isTransactionAborted); + OK(ReceiveCommand.Result.OK), + LOCK_FAILURE(ReceiveCommand.Result.LOCK_FAILURE), + REJECTED_NONFASTFORWARD(ReceiveCommand.Result.REJECTED_NONFASTFORWARD), + REJECTED_MISSING_OBJECT(ReceiveCommand.Result.REJECTED_MISSING_OBJECT), + TRANSACTION_ABORTED(ReceiveCommand::isTransactionAborted); @SuppressWarnings("ImmutableEnumChecker") final Predicate p; 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 5045e9464..b0b5f68ef 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 @@ -998,6 +998,108 @@ public void testV2FetchClientStopsNegotiation() throws Exception { assertTrue(client.getObjectDatabase().has(barChild.toObjectId())); } + @Test + public void testV2FetchWithoutWaitForDoneReceivesPackfile() + throws Exception { + String commonInBlob = "abcdefghijklmnopqrstuvwxyz"; + + RevBlob parentBlob = remote.blob(commonInBlob + "a"); + RevCommit parent = remote + .commit(remote.tree(remote.file("foo", parentBlob))); + remote.update("branch1", parent); + + RevCommit localParent = null; + RevCommit localChild = null; + try (TestRepository local = new TestRepository<>( + client)) { + RevBlob localParentBlob = local.blob(commonInBlob + "a"); + localParent = local + .commit(local.tree(local.file("foo", localParentBlob))); + RevBlob localChildBlob = local.blob(commonInBlob + "b"); + localChild = local.commit( + local.tree(local.file("foo", localChildBlob)), localParent); + local.update("branch1", localChild); + } + + ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n", + PacketLineIn.delimiter(), + "have " + localParent.toObjectId().getName() + "\n", + "have " + localChild.toObjectId().getName() + "\n", + PacketLineIn.end()); + PacketLineIn pckIn = new PacketLineIn(recvStream); + assertThat(pckIn.readString(), is("acknowledgments")); + assertThat(Arrays.asList(pckIn.readString()), + hasItems("ACK " + parent.toObjectId().getName())); + assertThat(pckIn.readString(), is("ready")); + assertTrue(PacketLineIn.isDelimiter(pckIn.readString())); + assertThat(pckIn.readString(), is("packfile")); + parsePack(recvStream); + } + + @Test + public void testV2FetchWithWaitForDoneOnlyDoesNegotiation() + throws Exception { + String commonInBlob = "abcdefghijklmnopqrstuvwxyz"; + + RevBlob parentBlob = remote.blob(commonInBlob + "a"); + RevCommit parent = remote + .commit(remote.tree(remote.file("foo", parentBlob))); + remote.update("branch1", parent); + + RevCommit localParent = null; + RevCommit localChild = null; + try (TestRepository local = new TestRepository<>( + client)) { + RevBlob localParentBlob = local.blob(commonInBlob + "a"); + localParent = local + .commit(local.tree(local.file("foo", localParentBlob))); + RevBlob localChildBlob = local.blob(commonInBlob + "b"); + localChild = local.commit( + local.tree(local.file("foo", localChildBlob)), localParent); + local.update("branch1", localChild); + } + + ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n", + PacketLineIn.delimiter(), "wait-for-done\n", + "have " + localParent.toObjectId().getName() + "\n", + "have " + localChild.toObjectId().getName() + "\n", + PacketLineIn.end()); + PacketLineIn pckIn = new PacketLineIn(recvStream); + assertThat(pckIn.readString(), is("acknowledgments")); + assertThat(Arrays.asList(pckIn.readString()), + hasItems("ACK " + parent.toObjectId().getName())); + assertTrue(PacketLineIn.isEnd(pckIn.readString())); + } + + @Test + public void testV2FetchWithWaitForDoneOnlyDoesNegotiationAndNothingToAck() + throws Exception { + String commonInBlob = "abcdefghijklmnopqrstuvwxyz"; + + RevCommit localParent = null; + RevCommit localChild = null; + try (TestRepository local = new TestRepository<>( + client)) { + RevBlob localParentBlob = local.blob(commonInBlob + "a"); + localParent = local + .commit(local.tree(local.file("foo", localParentBlob))); + RevBlob localChildBlob = local.blob(commonInBlob + "b"); + localChild = local.commit( + local.tree(local.file("foo", localChildBlob)), localParent); + local.update("branch1", localChild); + } + + ByteArrayInputStream recvStream = uploadPackV2("command=fetch\n", + PacketLineIn.delimiter(), "wait-for-done\n", + "have " + localParent.toObjectId().getName() + "\n", + "have " + localChild.toObjectId().getName() + "\n", + PacketLineIn.end()); + PacketLineIn pckIn = new PacketLineIn(recvStream); + assertThat(pckIn.readString(), is("acknowledgments")); + assertThat(pckIn.readString(), is("NAK")); + assertTrue(PacketLineIn.isEnd(pckIn.readString())); + } + @Test public void testV2FetchThinPack() throws Exception { String commonInBlob = "abcdefghijklmnopqrstuvwxyz"; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/Base85Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/Base85Test.java new file mode 100644 index 000000000..a49878cc7 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/Base85Test.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.util; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.StandardCharsets; + +import org.junit.Test; + +/** + * Tests for {@link Base85}. + */ +public class Base85Test { + + private static final String VALID_CHARS = "0123456789" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + "!#$%&()*+-;<=>?@^_`{|}~"; + + @Test + public void testChars() { + for (int i = 0; i < 256; i++) { + byte[] testData = { '1', '2', '3', '4', (byte) i }; + if (VALID_CHARS.indexOf(i) >= 0) { + byte[] decoded = Base85.decode(testData, 4); + assertNotNull(decoded); + } else { + assertThrows(IllegalArgumentException.class, + () -> Base85.decode(testData, 4)); + } + } + } + + private void roundtrip(byte[] data, int expectedLength) { + byte[] encoded = Base85.encode(data); + assertEquals(expectedLength, encoded.length); + assertArrayEquals(data, Base85.decode(encoded, data.length)); + } + + private void roundtrip(String data, int expectedLength) { + roundtrip(data.getBytes(StandardCharsets.US_ASCII), expectedLength); + } + + @Test + public void testPadding() { + roundtrip("", 0); + roundtrip("a", 5); + roundtrip("ab", 5); + roundtrip("abc", 5); + roundtrip("abcd", 5); + roundtrip("abcde", 10); + roundtrip("abcdef", 10); + roundtrip("abcdefg", 10); + roundtrip("abcdefgh", 10); + roundtrip("abcdefghi", 15); + } + + @Test + public void testBinary() { + roundtrip(new byte[] { 1 }, 5); + roundtrip(new byte[] { 1, 2 }, 5); + roundtrip(new byte[] { 1, 2, 3 }, 5); + roundtrip(new byte[] { 1, 2, 3, 4 }, 5); + roundtrip(new byte[] { 1, 2, 3, 4, 5 }, 10); + roundtrip(new byte[] { 1, 2, 3, 4, 5, 0, 0, 0 }, 10); + roundtrip(new byte[] { 1, 2, 3, 4, 0, 0, 0, 5 }, 10); + } + + @Test + public void testOverflow() { + IllegalArgumentException e = assertThrows( + IllegalArgumentException.class, + () -> Base85.decode(new byte[] { '~', '~', '~', '~', '~' }, 4)); + assertTrue(e.getMessage().contains("overflow")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/BinaryDeltaInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/BinaryDeltaInputStreamTest.java new file mode 100644 index 000000000..d9297fcd9 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/BinaryDeltaInputStreamTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.util.io; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.zip.InflaterInputStream; + +import org.junit.Test; + +/** + * Crude tests for the {@link BinaryDeltaInputStream} using delta diffs + * generated by C git. + */ +public class BinaryDeltaInputStreamTest { + + private InputStream getBinaryHunk(String name) { + return this.getClass().getResourceAsStream(name); + } + + @Test + public void testBinaryDelta() throws Exception { + // Prepare our test data + byte[] data = new byte[8192]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (255 - (i % 256)); + } + // Same, but with five 'x' inserted in the middle. + int middle = data.length / 2; + byte[] newData = new byte[data.length + 5]; + System.arraycopy(data, 0, newData, 0, middle); + for (int i = 0; i < 5; i++) { + newData[middle + i] = 'x'; + } + System.arraycopy(data, middle, newData, middle + 5, middle); + // delta1.forward has the instructions + // @formatter:off + // COPY 0 4096 + // INSERT 5 xxxxx + // COPY 0 4096 + // @formatter:on + // Note that the way we built newData could be expressed as + // @formatter:off + // COPY 0 4096 + // INSERT 5 xxxxx + // COPY 4096 4096 + // @formatter:on + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + BinaryDeltaInputStream input = new BinaryDeltaInputStream(data, + new InflaterInputStream(new BinaryHunkInputStream( + getBinaryHunk("delta1.forward"))))) { + byte[] buf = new byte[1024]; + int n; + while ((n = input.read(buf)) >= 0) { + out.write(buf, 0, n); + } + assertArrayEquals(newData, out.toByteArray()); + assertTrue(input.isFullyConsumed()); + } + // delta1.reverse has the instructions + // @formatter:off + // COPY 0 4096 + // COPY 256 3840 + // COPY 256 256 + // @formatter:on + // Note that there are alternatives, for instance + // @formatter:off + // COPY 0 4096 + // COPY 4101 4096 + // @formatter:on + // or + // @formatter:off + // COPY 0 4096 + // COPY 0 4096 + // @formatter:on + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + BinaryDeltaInputStream input = new BinaryDeltaInputStream( + newData, + new InflaterInputStream(new BinaryHunkInputStream( + getBinaryHunk("delta1.reverse"))))) { + long expectedSize = input.getExpectedResultSize(); + assertEquals(data.length, expectedSize); + byte[] buf = new byte[1024]; + int n; + while ((n = input.read(buf)) >= 0) { + out.write(buf, 0, n); + } + assertArrayEquals(data, out.toByteArray()); + assertTrue(input.isFullyConsumed()); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/BinaryHunkStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/BinaryHunkStreamTest.java new file mode 100644 index 000000000..b198c32a7 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/BinaryHunkStreamTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.util.io; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Test; + +/** + * Tests for {@link BinaryHunkInputStream} and {@link BinaryHunkOutputStream}. + */ +public class BinaryHunkStreamTest { + + @Test + public void testRoundtripWholeBuffer() throws IOException { + for (int length = 1; length < 520 + 52; length++) { + byte[] data = new byte[length]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (255 - (i % 256)); + } + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + BinaryHunkOutputStream out = new BinaryHunkOutputStream( + bos)) { + out.write(data); + out.flush(); + byte[] encoded = bos.toByteArray(); + assertFalse(Arrays.equals(data, encoded)); + try (BinaryHunkInputStream in = new BinaryHunkInputStream( + new ByteArrayInputStream(encoded))) { + byte[] decoded = new byte[data.length]; + int newLength = in.read(decoded); + assertEquals(newLength, decoded.length); + assertEquals(-1, in.read()); + assertArrayEquals(data, decoded); + } + } + } + } + + @Test + public void testRoundtripChunks() throws IOException { + for (int length = 1; length < 520 + 52; length++) { + byte[] data = new byte[length]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (255 - (i % 256)); + } + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + BinaryHunkOutputStream out = new BinaryHunkOutputStream( + bos)) { + out.write(data, 0, data.length / 2); + out.write(data, data.length / 2, data.length - data.length / 2); + out.flush(); + byte[] encoded = bos.toByteArray(); + assertFalse(Arrays.equals(data, encoded)); + try (BinaryHunkInputStream in = new BinaryHunkInputStream( + new ByteArrayInputStream(encoded))) { + byte[] decoded = new byte[data.length]; + int p = 0; + int n; + while ((n = in.read(decoded, p, + Math.min(decoded.length - p, 57))) >= 0) { + p += n; + if (p == decoded.length) { + break; + } + } + assertEquals(p, decoded.length); + assertEquals(-1, in.read()); + assertArrayEquals(data, decoded); + } + } + } + } + + @Test + public void testRoundtripBytes() throws IOException { + for (int length = 1; length < 520 + 52; length++) { + byte[] data = new byte[length]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (255 - (i % 256)); + } + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + BinaryHunkOutputStream out = new BinaryHunkOutputStream( + bos)) { + for (int i = 0; i < data.length; i++) { + out.write(data[i]); + } + out.flush(); + byte[] encoded = bos.toByteArray(); + assertFalse(Arrays.equals(data, encoded)); + try (BinaryHunkInputStream in = new BinaryHunkInputStream( + new ByteArrayInputStream(encoded))) { + byte[] decoded = new byte[data.length]; + for (int i = 0; i < decoded.length; i++) { + int val = in.read(); + assertTrue(0 <= val && val <= 255); + decoded[i] = (byte) val; + } + assertEquals(-1, in.read()); + assertArrayEquals(data, decoded); + } + } + } + } + + @Test + public void testRoundtripWithClose() throws IOException { + for (int length = 1; length < 520 + 52; length++) { + byte[] data = new byte[length]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (255 - (i % 256)); + } + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + try (BinaryHunkOutputStream out = new BinaryHunkOutputStream( + bos)) { + out.write(data); + } + byte[] encoded = bos.toByteArray(); + assertFalse(Arrays.equals(data, encoded)); + try (BinaryHunkInputStream in = new BinaryHunkInputStream( + new ByteArrayInputStream(encoded))) { + byte[] decoded = new byte[data.length]; + int newLength = in.read(decoded); + assertEquals(newLength, decoded.length); + assertEquals(-1, in.read()); + assertArrayEquals(data, decoded); + } + } + } + } +} diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters new file mode 100644 index 000000000..33331fbab --- /dev/null +++ b/org.eclipse.jgit/.settings/.api_filters @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 2fa8713da..962324e0f 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -13,6 +13,9 @@ ambiguousObjectAbbreviation=Object abbreviation {0} is ambiguous aNewObjectIdIsRequired=A NewObjectId is required. anExceptionOccurredWhileTryingToAddTheIdOfHEAD=An exception occurred while trying to add the Id of HEAD anSSHSessionHasBeenAlreadyCreated=An SSH session has been already created +applyBinaryBaseOidWrong=Cannot apply binary patch; OID for file {0} does not match +applyBinaryOidTooShort=Binary patch for file {0} does not have full IDs +applyBinaryResultOidWrong=Result of binary patch for file {0} has wrong OID. applyingCommit=Applying {0} archiveFormatAlreadyAbsent=Archive format already absent: {0} archiveFormatAlreadyRegistered=Archive format already registered with different implementation: {0} @@ -37,7 +40,19 @@ badRef=Bad ref: {0}: {1} badSectionEntry=Bad section entry: {0} badShallowLine=Bad shallow line: {0} bareRepositoryNoWorkdirAndIndex=Bare Repository has neither a working tree, nor an index +base85invalidChar=Invalid base-85 character: 0x{0} +base85length=Base-85 encoded data must have a length that is a multiple of 5 +base85overflow=Base-85 value overflow, does not fit into 32 bits: 0x{0} +base85tooLong=Extra base-85 encoded data for output size of {0} bytes +base85tooShort=Base-85 data decoded into less than {0} bytes baseLengthIncorrect=base length incorrect +binaryDeltaBaseLengthMismatch=Binary delta base length does not match, expected {0}, got {1} +binaryDeltaInvalidOffset=Binary delta offset + length too large: {0} + {1} +binaryDeltaInvalidResultLength=Binary delta expected result length is negative +binaryHunkDecodeError=Binary hunk, line {0}: invalid input +binaryHunkInvalidLength=Binary hunk, line {0}: input corrupt; expected length byte, got 0x{1} +binaryHunkLineTooShort=Binary hunk, line {0}: input ended prematurely +binaryHunkMissingNewline=Binary hunk, line {0}: input line not terminated by newline bitmapMissingObject=Bitmap at {0} is missing {1}. bitmapsMustBePrepared=Bitmaps must be prepared before they may be written. blameNotCommittedYet=Not Committed Yet diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java index e228e8276..583767af3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2020 IBM Corporation and others + * Copyright (C) 2011, 2021 IBM Corporation 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 @@ -9,29 +9,68 @@ */ package org.eclipse.jgit.api; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.Writer; +import java.io.OutputStream; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.zip.InflaterInputStream; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.PatchApplyException; import org.eclipse.jgit.api.errors.PatchFormatException; +import org.eclipse.jgit.attributes.FilterCommand; +import org.eclipse.jgit.attributes.FilterCommandRegistry; import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.diff.RawText; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.LargeObjectException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectStream; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.patch.BinaryHunk; import org.eclipse.jgit.patch.FileHeader; +import org.eclipse.jgit.patch.FileHeader.PatchType; import org.eclipse.jgit.patch.HunkHeader; import org.eclipse.jgit.patch.Patch; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.eclipse.jgit.util.TemporaryBuffer.LocalFile; +import org.eclipse.jgit.util.io.BinaryDeltaInputStream; +import org.eclipse.jgit.util.io.BinaryHunkInputStream; +import org.eclipse.jgit.util.io.EolStreamTypeUtil; +import org.eclipse.jgit.util.sha1.SHA1; /** * Apply a patch to files and/or to the index. @@ -45,7 +84,7 @@ public class ApplyCommand extends GitCommand { private InputStream in; /** - * Constructs the command if the patch is to be applied to the index. + * Constructs the command. * * @param repo */ @@ -79,6 +118,7 @@ public ApplyCommand setPatch(InputStream in) { public ApplyResult call() throws GitAPIException, PatchFormatException, PatchApplyException { checkCallable(); + setCallable(false); ApplyResult r = new ApplyResult(); try { final Patch p = new Patch(); @@ -87,19 +127,22 @@ public ApplyResult call() throws GitAPIException, PatchFormatException, } finally { in.close(); } - if (!p.getErrors().isEmpty()) + if (!p.getErrors().isEmpty()) { throw new PatchFormatException(p.getErrors()); + } + Repository repository = getRepository(); + DirCache cache = repository.readDirCache(); for (FileHeader fh : p.getFiles()) { ChangeType type = fh.getChangeType(); File f = null; switch (type) { case ADD: f = getFile(fh.getNewPath(), true); - apply(f, fh); + apply(repository, fh.getNewPath(), cache, f, fh); break; case MODIFY: f = getFile(fh.getOldPath(), false); - apply(f, fh); + apply(repository, fh.getOldPath(), cache, f, fh); break; case DELETE: f = getFile(fh.getOldPath(), false); @@ -118,14 +161,14 @@ public ApplyResult call() throws GitAPIException, PatchFormatException, throw new PatchApplyException(MessageFormat.format( JGitText.get().renameFileFailed, f, dest), e); } - apply(dest, fh); + apply(repository, fh.getOldPath(), cache, dest, fh); break; case COPY: f = getFile(fh.getOldPath(), false); File target = getFile(fh.getNewPath(), false); FileUtils.mkdirs(target.getParentFile(), true); Files.copy(f.toPath(), target.toPath()); - apply(target, fh); + apply(repository, fh.getOldPath(), cache, target, fh); } r.addUpdatedFile(f); } @@ -133,14 +176,13 @@ public ApplyResult call() throws GitAPIException, PatchFormatException, throw new PatchApplyException(MessageFormat.format( JGitText.get().patchApplyException, e.getMessage()), e); } - setCallable(false); return r; } private File getFile(String path, boolean create) throws PatchApplyException { File f = new File(getRepository().getWorkTree(), path); - if (create) + if (create) { try { File parent = f.getParentFile(); FileUtils.mkdirs(parent, true); @@ -149,22 +191,366 @@ private File getFile(String path, boolean create) throw new PatchApplyException(MessageFormat.format( JGitText.get().createNewFileFailed, f), e); } + } return f; } + private void apply(Repository repository, String path, DirCache cache, + File f, FileHeader fh) throws IOException, PatchApplyException { + if (PatchType.BINARY.equals(fh.getPatchType())) { + return; + } + boolean convertCrLf = needsCrLfConversion(f, fh); + // Use a TreeWalk with a DirCacheIterator to pick up the correct + // clean/smudge filters. CR-LF handling is completely determined by + // whether the file or the patch have CR-LF line endings. + try (TreeWalk walk = new TreeWalk(repository)) { + walk.setOperationType(OperationType.CHECKIN_OP); + FileTreeIterator files = new FileTreeIterator(repository); + int fileIdx = walk.addTree(files); + int cacheIdx = walk.addTree(new DirCacheIterator(cache)); + files.setDirCacheIterator(walk, cacheIdx); + walk.setFilter(AndTreeFilter.create( + PathFilterGroup.createFromStrings(path), + new NotIgnoredFilter(fileIdx))); + walk.setRecursive(true); + if (walk.next()) { + // If the file on disk has no newline characters, convertCrLf + // will be false. In that case we want to honor the normal git + // settings. + EolStreamType streamType = convertCrLf ? EolStreamType.TEXT_CRLF + : walk.getEolStreamType(OperationType.CHECKOUT_OP); + String command = walk.getFilterCommand( + Constants.ATTR_FILTER_TYPE_SMUDGE); + CheckoutMetadata checkOut = new CheckoutMetadata(streamType, command); + FileTreeIterator file = walk.getTree(fileIdx, + FileTreeIterator.class); + if (file != null) { + if (PatchType.GIT_BINARY.equals(fh.getPatchType())) { + applyBinary(repository, path, f, fh, + file::openEntryStream, file.getEntryObjectId(), + checkOut); + } else { + command = walk.getFilterCommand( + Constants.ATTR_FILTER_TYPE_CLEAN); + RawText raw; + // Can't use file.openEntryStream() as it would do CR-LF + // conversion as usual, not as wanted by us. + try (InputStream input = filterClean(repository, path, + new FileInputStream(f), convertCrLf, command)) { + raw = new RawText( + IO.readWholeStream(input, 0).array()); + } + applyText(repository, path, raw, f, fh, checkOut); + } + return; + } + } + } + // File ignored? + RawText raw; + CheckoutMetadata checkOut; + if (PatchType.GIT_BINARY.equals(fh.getPatchType())) { + checkOut = new CheckoutMetadata(EolStreamType.DIRECT, null); + applyBinary(repository, path, f, fh, () -> new FileInputStream(f), + null, checkOut); + } else { + if (convertCrLf) { + try (InputStream input = EolStreamTypeUtil.wrapInputStream( + new FileInputStream(f), EolStreamType.TEXT_LF)) { + raw = new RawText(IO.readWholeStream(input, 0).array()); + } + checkOut = new CheckoutMetadata(EolStreamType.TEXT_CRLF, null); + } else { + raw = new RawText(f); + checkOut = new CheckoutMetadata(EolStreamType.DIRECT, null); + } + applyText(repository, path, raw, f, fh, checkOut); + } + } + + private boolean needsCrLfConversion(File f, FileHeader fileHeader) + throws IOException { + if (PatchType.GIT_BINARY.equals(fileHeader.getPatchType())) { + return false; + } + if (!hasCrLf(fileHeader)) { + try (InputStream input = new FileInputStream(f)) { + return RawText.isCrLfText(input); + } + } + return false; + } + + private static boolean hasCrLf(FileHeader fileHeader) { + if (PatchType.GIT_BINARY.equals(fileHeader.getPatchType())) { + return false; + } + for (HunkHeader header : fileHeader.getHunks()) { + byte[] buf = header.getBuffer(); + int hunkEnd = header.getEndOffset(); + int lineStart = header.getStartOffset(); + while (lineStart < hunkEnd) { + int nextLineStart = RawParseUtils.nextLF(buf, lineStart); + if (nextLineStart > hunkEnd) { + nextLineStart = hunkEnd; + } + if (nextLineStart <= lineStart) { + break; + } + if (nextLineStart - lineStart > 1) { + char first = (char) (buf[lineStart] & 0xFF); + if (first == ' ' || first == '-') { + // It's an old line. Does it end in CR-LF? + if (buf[nextLineStart - 2] == '\r') { + return true; + } + } + } + lineStart = nextLineStart; + } + } + return false; + } + + private InputStream filterClean(Repository repository, String path, + InputStream fromFile, boolean convertCrLf, String filterCommand) + throws IOException { + InputStream input = fromFile; + if (convertCrLf) { + input = EolStreamTypeUtil.wrapInputStream(input, + EolStreamType.TEXT_LF); + } + if (StringUtils.isEmptyOrNull(filterCommand)) { + return input; + } + if (FilterCommandRegistry.isRegistered(filterCommand)) { + LocalFile buffer = new TemporaryBuffer.LocalFile(null); + FilterCommand command = FilterCommandRegistry.createFilterCommand( + filterCommand, repository, input, buffer); + while (command.run() != -1) { + // loop as long as command.run() tells there is work to do + } + return buffer.openInputStreamWithAutoDestroy(); + } + FS fs = repository.getFS(); + ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand, + new String[0]); + filterProcessBuilder.directory(repository.getWorkTree()); + filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY, + repository.getDirectory().getAbsolutePath()); + ExecutionResult result; + try { + result = fs.execute(filterProcessBuilder, in); + } catch (IOException | InterruptedException e) { + throw new IOException( + new FilterFailedException(e, filterCommand, path)); + } + int rc = result.getRc(); + if (rc != 0) { + throw new IOException(new FilterFailedException(rc, filterCommand, + path, result.getStdout().toByteArray(4096), RawParseUtils + .decode(result.getStderr().toByteArray(4096)))); + } + return result.getStdout().openInputStreamWithAutoDestroy(); + } + /** - * @param f - * @param fh - * @throws IOException - * @throws PatchApplyException + * Something that can supply an {@link InputStream}. */ - private void apply(File f, FileHeader fh) + private interface StreamSupplier { + InputStream load() throws IOException; + } + + /** + * We write the patch result to a {@link TemporaryBuffer} and then use + * {@link DirCacheCheckout}.getContent() to run the result through the CR-LF + * and smudge filters. DirCacheCheckout needs an ObjectLoader, not a + * TemporaryBuffer, so this class bridges between the two, making any Stream + * provided by a {@link StreamSupplier} look like an ordinary git blob to + * DirCacheCheckout. + */ + private static class StreamLoader extends ObjectLoader { + + private StreamSupplier data; + + private long size; + + StreamLoader(StreamSupplier data, long length) { + this.data = data; + this.size = length; + } + + @Override + public int getType() { + return Constants.OBJ_BLOB; + } + + @Override + public long getSize() { + return size; + } + + @Override + public boolean isLarge() { + return true; + } + + @Override + public byte[] getCachedBytes() throws LargeObjectException { + throw new LargeObjectException(); + } + + @Override + public ObjectStream openStream() + throws MissingObjectException, IOException { + return new ObjectStream.Filter(getType(), getSize(), + new BufferedInputStream(data.load())); + } + } + + private void initHash(SHA1 hash, long size) { + hash.update(Constants.encodedTypeString(Constants.OBJ_BLOB)); + hash.update((byte) ' '); + hash.update(Constants.encodeASCII(size)); + hash.update((byte) 0); + } + + private ObjectId hash(File f) throws IOException { + SHA1 hash = SHA1.newInstance(); + initHash(hash, f.length()); + try (InputStream input = new FileInputStream(f)) { + byte[] buf = new byte[8192]; + int n; + while ((n = input.read(buf)) >= 0) { + hash.update(buf, 0, n); + } + } + return hash.toObjectId(); + } + + private void checkOid(ObjectId baseId, ObjectId id, ChangeType type, File f, + String path) + throws PatchApplyException, IOException { + boolean hashOk = false; + if (id != null) { + hashOk = baseId.equals(id); + if (!hashOk && ChangeType.ADD.equals(type) + && ObjectId.zeroId().equals(baseId)) { + // We create the file first. The OID of an empty file is not the + // zero id! + hashOk = Constants.EMPTY_BLOB_ID.equals(id); + } + } else { + if (ObjectId.zeroId().equals(baseId)) { + // File empty is OK. + hashOk = !f.exists() || f.length() == 0; + } else { + hashOk = baseId.equals(hash(f)); + } + } + if (!hashOk) { + throw new PatchApplyException(MessageFormat + .format(JGitText.get().applyBinaryBaseOidWrong, path)); + } + } + + private void applyBinary(Repository repository, String path, File f, + FileHeader fh, StreamSupplier loader, ObjectId id, + CheckoutMetadata checkOut) + throws PatchApplyException, IOException { + if (!fh.getOldId().isComplete() || !fh.getNewId().isComplete()) { + throw new PatchApplyException(MessageFormat + .format(JGitText.get().applyBinaryOidTooShort, path)); + } + BinaryHunk hunk = fh.getForwardBinaryHunk(); + // A BinaryHunk has the start at the "literal" or "delta" token. Data + // starts on the next line. + int start = RawParseUtils.nextLF(hunk.getBuffer(), + hunk.getStartOffset()); + int length = hunk.getEndOffset() - start; + SHA1 hash = SHA1.newInstance(); + // Write to a buffer and copy to the file only if everything was fine + TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(null); + try { + switch (hunk.getType()) { + case LITERAL_DEFLATED: + // This just overwrites the file. We need to check the hash of + // the base. + checkOid(fh.getOldId().toObjectId(), id, fh.getChangeType(), f, + path); + initHash(hash, hunk.getSize()); + try (OutputStream out = buffer; + InputStream inflated = new SHA1InputStream(hash, + new InflaterInputStream( + new BinaryHunkInputStream( + new ByteArrayInputStream( + hunk.getBuffer(), start, + length))))) { + DirCacheCheckout.getContent(repository, path, checkOut, + new StreamLoader(() -> inflated, hunk.getSize()), + null, out); + if (!fh.getNewId().toObjectId().equals(hash.toObjectId())) { + throw new PatchApplyException(MessageFormat.format( + JGitText.get().applyBinaryResultOidWrong, + path)); + } + } + try (InputStream bufIn = buffer.openInputStream()) { + Files.copy(bufIn, f.toPath(), + StandardCopyOption.REPLACE_EXISTING); + } + break; + case DELTA_DEFLATED: + // Unfortunately delta application needs random access to the + // base to construct the result. + byte[] base; + try (InputStream input = loader.load()) { + base = IO.readWholeStream(input, 0).array(); + } + // At least stream the result! + try (BinaryDeltaInputStream input = new BinaryDeltaInputStream( + base, + new InflaterInputStream(new BinaryHunkInputStream( + new ByteArrayInputStream(hunk.getBuffer(), + start, length))))) { + long finalSize = input.getExpectedResultSize(); + initHash(hash, finalSize); + try (OutputStream out = buffer; + SHA1InputStream hashed = new SHA1InputStream(hash, + input)) { + DirCacheCheckout.getContent(repository, path, checkOut, + new StreamLoader(() -> hashed, finalSize), null, + out); + if (!fh.getNewId().toObjectId() + .equals(hash.toObjectId())) { + throw new PatchApplyException(MessageFormat.format( + JGitText.get().applyBinaryResultOidWrong, + path)); + } + } + } + try (InputStream bufIn = buffer.openInputStream()) { + Files.copy(bufIn, f.toPath(), + StandardCopyOption.REPLACE_EXISTING); + } + break; + default: + break; + } + } finally { + buffer.destroy(); + } + } + + private void applyText(Repository repository, String path, RawText rt, + File f, FileHeader fh, CheckoutMetadata checkOut) throws IOException, PatchApplyException { - RawText rt = new RawText(f); - List oldLines = new ArrayList<>(rt.size()); - for (int i = 0; i < rt.size(); i++) - oldLines.add(rt.getString(i)); - List newLines = new ArrayList<>(oldLines); + List oldLines = new ArrayList<>(rt.size()); + for (int i = 0; i < rt.size(); i++) { + oldLines.add(rt.getRawString(i)); + } + List newLines = new ArrayList<>(oldLines); int afterLastHunk = 0; int lineNumberShift = 0; int lastHunkNewLine = -1; @@ -182,9 +568,9 @@ private void apply(File f, FileHeader fh) b.length); RawText hrt = new RawText(b); - List hunkLines = new ArrayList<>(hrt.size()); + List hunkLines = new ArrayList<>(hrt.size()); for (int i = 0; i < hrt.size(); i++) { - hunkLines.add(hrt.getString(i)); + hunkLines.add(hrt.getRawString(i)); } if (hh.getNewStartLine() == 0) { @@ -253,8 +639,13 @@ && canApplyAt(hunkLines, newLines, 0)) { lineNumberShift = applyAt - hh.getNewStartLine() + 1; int sz = hunkLines.size(); for (int j = 1; j < sz; j++) { - String hunkLine = hunkLines.get(j); - switch (hunkLine.charAt(0)) { + ByteBuffer hunkLine = hunkLines.get(j); + if (!hunkLine.hasRemaining()) { + // Completely empty line; accept as empty context line + applyAt++; + continue; + } + switch (hunkLine.array()[hunkLine.position()]) { case ' ': applyAt++; break; @@ -262,7 +653,7 @@ && canApplyAt(hunkLines, newLines, 0)) { newLines.remove(applyAt); break; case '+': - newLines.add(applyAt++, hunkLine.substring(1)); + newLines.add(applyAt++, slice(hunkLine, 1)); break; default: break; @@ -271,39 +662,64 @@ && canApplyAt(hunkLines, newLines, 0)) { afterLastHunk = applyAt; } if (!isNoNewlineAtEndOfFile(fh)) { - newLines.add(""); //$NON-NLS-1$ + newLines.add(null); } if (!rt.isMissingNewlineAtEnd()) { - oldLines.add(""); //$NON-NLS-1$ + oldLines.add(null); } - if (!isChanged(oldLines, newLines)) { - return; // Don't touch the file + if (oldLines.equals(newLines)) { + return; // Unchanged; don't touch the file } - try (Writer fw = Files.newBufferedWriter(f.toPath())) { - for (Iterator l = newLines.iterator(); l.hasNext();) { - fw.write(l.next()); - if (l.hasNext()) { - // Don't bother handling line endings - if it was Windows, - // the \r is still there! - fw.write('\n'); + + TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(null); + try { + try (OutputStream out = buffer) { + for (Iterator l = newLines.iterator(); l + .hasNext();) { + ByteBuffer line = l.next(); + if (line == null) { + // Must be the marker for the final newline + break; + } + out.write(line.array(), line.position(), line.remaining()); + if (l.hasNext()) { + out.write('\n'); + } } } + try (OutputStream output = new FileOutputStream(f)) { + DirCacheCheckout.getContent(repository, path, checkOut, + new StreamLoader(buffer::openInputStream, + buffer.length()), + null, output); + } + } finally { + buffer.destroy(); } - getRepository().getFS().setExecute(f, fh.getNewMode() == FileMode.EXECUTABLE_FILE); + repository.getFS().setExecute(f, + fh.getNewMode() == FileMode.EXECUTABLE_FILE); } - private boolean canApplyAt(List hunkLines, List newLines, - int line) { + private boolean canApplyAt(List hunkLines, + List newLines, int line) { int sz = hunkLines.size(); int limit = newLines.size(); int pos = line; for (int j = 1; j < sz; j++) { - String hunkLine = hunkLines.get(j); - switch (hunkLine.charAt(0)) { + ByteBuffer hunkLine = hunkLines.get(j); + if (!hunkLine.hasRemaining()) { + // Empty line. Accept as empty context line. + if (pos >= limit || newLines.get(pos).hasRemaining()) { + return false; + } + pos++; + continue; + } + switch (hunkLine.array()[hunkLine.position()]) { case ' ': case '-': if (pos >= limit - || !newLines.get(pos).equals(hunkLine.substring(1))) { + || !newLines.get(pos).equals(slice(hunkLine, 1))) { return false; } pos++; @@ -315,13 +731,9 @@ private boolean canApplyAt(List hunkLines, List newLines, return true; } - private static boolean isChanged(List ol, List nl) { - if (ol.size() != nl.size()) - return true; - for (int i = 0; i < ol.size(); i++) - if (!ol.get(i).equals(nl.get(i))) - return true; - return false; + private ByteBuffer slice(ByteBuffer b, int off) { + int newOffset = b.position() + off; + return ByteBuffer.wrap(b.array(), newOffset, b.limit() - newOffset); } private boolean isNoNewlineAtEndOfFile(FileHeader fh) { @@ -330,8 +742,51 @@ private boolean isNoNewlineAtEndOfFile(FileHeader fh) { return false; } HunkHeader lastHunk = hunks.get(hunks.size() - 1); - RawText lhrt = new RawText(lastHunk.getBuffer()); + byte[] buf = new byte[lastHunk.getEndOffset() + - lastHunk.getStartOffset()]; + System.arraycopy(lastHunk.getBuffer(), lastHunk.getStartOffset(), buf, + 0, buf.length); + RawText lhrt = new RawText(buf); return lhrt.getString(lhrt.size() - 1) .equals("\\ No newline at end of file"); //$NON-NLS-1$ } + + /** + * An {@link InputStream} that updates a {@link SHA1} on every byte read. + * The hash is supposed to have been initialized before reading starts. + */ + private static class SHA1InputStream extends InputStream { + + private final SHA1 hash; + + private final InputStream in; + + SHA1InputStream(SHA1 hash, InputStream in) { + this.hash = hash; + this.in = in; + } + + @Override + public int read() throws IOException { + int b = in.read(); + if (b >= 0) { + hash.update((byte) b); + } + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int n = in.read(b, off, len); + if (n > 0) { + hash.update(b, off, n); + } + return n; + } + + @Override + public void close() throws IOException { + in.close(); + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index c611f915a..ef56d802c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -86,6 +86,20 @@ public class MergeCommand extends GitCommand { private ProgressMonitor monitor = NullProgressMonitor.INSTANCE; + /** + * Values for the "merge.conflictStyle" git config. + * + * @since 5.12 + */ + public enum ConflictStyle { + + /** "merge" style: only ours/theirs. This is the default. */ + MERGE, + + /** "diff3" style: ours/base/theirs. */ + DIFF3 + } + /** * The modes available for fast forward merges corresponding to the * --ff, --no-ff and --ff-only diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java index 9f4b1fa49..d09da019d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2009, Google Inc. - * Copyright (C) 2008-2009, Johannes E. Schindelin and others + * Copyright (C) 2008-2021, Johannes E. Schindelin 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 @@ -16,6 +16,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import org.eclipse.jgit.errors.BinaryBlobException; import org.eclipse.jgit.errors.LargeObjectException; @@ -164,6 +165,27 @@ public String getString(int i) { return getString(i, i + 1, true); } + /** + * Get the raw text for a single line. + * + * @param i + * index of the line to extract. Note this is 0-based, so line + * number 1 is actually index 0. + * @return the text for the line, without a trailing LF, as a + * {@link ByteBuffer} that is backed by a slice of the + * {@link #getRawContent() raw content}, with the buffer's position + * on the start of the line and the limit at the end. + * @since 5.12 + */ + public ByteBuffer getRawString(int i) { + int s = getStart(i); + int e = getEnd(i); + if (e > 0 && content[e - 1] == '\n') { + e--; + } + return ByteBuffer.wrap(content, s, e - s); + } + /** * Get the text for a region of lines. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java index 75784c255..ba1f63b68 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java @@ -104,6 +104,13 @@ private int sortOf(ChangeType changeType) { */ private int bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; + /** + * Skip detecting content renames for binary files. Content renames are + * those that are not exact, that is with a slight content modification + * between the two files. + */ + private boolean skipContentRenamesForBinaryFiles = false; + /** Set if the number of adds or deletes was over the limit. */ private boolean overRenameLimit; @@ -235,6 +242,26 @@ public void setBigFileThreshold(int threshold) { this.bigFileThreshold = threshold; } + /** + * Get skipping detecting content renames for binary files. + * + * @return true if content renames should be skipped for binary files, false otherwise. + * @since 5.12 + */ + public boolean getSkipContentRenamesForBinaryFiles() { + return skipContentRenamesForBinaryFiles; + } + + /** + * Sets skipping detecting content renames for binary files. + * + * @param value true if content renames should be skipped for binary files, false otherwise. + * @since 5.12 + */ + public void setSkipContentRenamesForBinaryFiles(boolean value) { + this.skipContentRenamesForBinaryFiles = value; + } + /** * Check if the detector is over the rename limit. *

@@ -521,6 +548,7 @@ private void findContentRenames(ContentSource.Pair reader, d = new SimilarityRenameDetector(reader, deleted, added); d.setRenameScore(getRenameScore()); d.setBigFileThreshold(getBigFileThreshold()); + d.setSkipBinaryFiles(getSkipContentRenamesForBinaryFiles()); d.compute(pm); overRenameLimit |= d.isTableOverflow(); deleted = d.getLeftOverSources(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java index fb6e5df58..661369b86 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityIndex.java @@ -102,6 +102,15 @@ public static SimilarityIndex create(ObjectLoader obj) throws IOException, idGrowAt = growAt(idHashBits); } + static boolean isBinary(ObjectLoader obj) throws IOException { + if (obj.isLarge()) { + try (ObjectStream in1 = obj.openStream()) { + return RawText.isBinary(in1); + } + } + return RawText.isBinary(obj.getCachedBytes()); + } + void hash(ObjectLoader obj) throws MissingObjectException, IOException, TableFullException { if (obj.isLarge()) { @@ -115,9 +124,7 @@ void hash(ObjectLoader obj) throws MissingObjectException, IOException, private void hashLargeObject(ObjectLoader obj) throws IOException, TableFullException { boolean text; - try (ObjectStream in1 = obj.openStream()) { - text = !RawText.isBinary(in1); - } + text = !isBinary(obj); try (ObjectStream in2 = obj.openStream()) { hash(in2, in2.getSize(), text); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java index 082f31d17..5871b4aee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java @@ -26,6 +26,7 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ProgressMonitor; class SimilarityRenameDetector { @@ -87,6 +88,9 @@ class SimilarityRenameDetector { */ private int bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; + /** Skip content renames for binary files. */ + private boolean skipBinaryFiles = false; + /** Set if any {@link SimilarityIndex.TableFullException} occurs. */ private boolean tableOverflow; @@ -107,6 +111,10 @@ void setBigFileThreshold(int threshold) { bigFileThreshold = threshold; } + void setSkipBinaryFiles(boolean value) { + skipBinaryFiles = value; + } + void compute(ProgressMonitor pm) throws IOException, CancelledException { if (pm == null) pm = NullProgressMonitor.INSTANCE; @@ -271,7 +279,12 @@ private int buildMatrix(ProgressMonitor pm) if (s == null) { try { - s = hash(OLD, srcEnt); + ObjectLoader loader = reader.open(OLD, srcEnt); + if (skipBinaryFiles && SimilarityIndex.isBinary(loader)) { + pm.update(1); + continue SRC; + } + s = hash(loader); } catch (TableFullException tableFull) { tableOverflow = true; continue SRC; @@ -280,7 +293,12 @@ private int buildMatrix(ProgressMonitor pm) SimilarityIndex d; try { - d = hash(NEW, dstEnt); + ObjectLoader loader = reader.open(NEW, dstEnt); + if (skipBinaryFiles && SimilarityIndex.isBinary(loader)) { + pm.update(1); + continue; + } + d = hash(loader); } catch (TableFullException tableFull) { if (dstTooLarge == null) dstTooLarge = new BitSet(dsts.size()); @@ -364,10 +382,10 @@ static int nameScore(String a, String b) { return (((dirScoreLtr + dirScoreRtl) * 25) + (fileScore * 50)) / 100; } - private SimilarityIndex hash(DiffEntry.Side side, DiffEntry ent) + private SimilarityIndex hash(ObjectLoader objectLoader) throws IOException, TableFullException { SimilarityIndex r = new SimilarityIndex(); - r.hash(reader.open(side, ent)); + r.hash(objectLoader); r.sort(); return r; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java index c039aaffa..552315d43 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME; import static org.eclipse.jgit.lib.Constants.R_REMOTES; +import static org.eclipse.jgit.lib.Constants.R_TAGS; import java.io.File; import java.io.FileInputStream; @@ -79,6 +80,13 @@ * @since 3.4 */ public class RepoCommand extends GitCommand { + private static final int LOCK_FAILURE_MAX_RETRIES = 5; + + // Retry exponentially with delays in this range + private static final int LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS = 50; + + private static final int LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS = 5000; + private String manifestPath; private String baseUri; private URI targetUri; @@ -587,8 +595,11 @@ public RevCommit call() throws GitAPIException { throw new RemoteUnavailableException(url); } if (recordRemoteBranch) { - // can be branch or tag - cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$ + // "branch" field is only for non-tag references. + // Keep tags in "ref" field as hint for other tools. + String field = proj.getRevision().startsWith( + R_TAGS) ? "ref" : "branch"; //$NON-NLS-1$ //$NON-NLS-2$ + cfg.setString("submodule", name, field, //$NON-NLS-1$ proj.getRevision()); } @@ -682,50 +693,22 @@ public RevCommit call() throws GitAPIException { builder.finish(); ObjectId treeId = index.writeTree(inserter); - // Create a Commit object, populate it and write it - ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$ - if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) { - // No change. Do nothing. - return rw.parseCommit(headId); + long prevDelay = 0; + for (int i = 0; i < LOCK_FAILURE_MAX_RETRIES - 1; i++) { + try { + return commitTreeOnCurrentTip( + inserter, rw, treeId); + } catch (ConcurrentRefUpdateException e) { + prevDelay = FileUtils.delay(prevDelay, + LOCK_FAILURE_MIN_RETRY_DELAY_MILLIS, + LOCK_FAILURE_MAX_RETRY_DELAY_MILLIS); + Thread.sleep(prevDelay); + repo.getRefDatabase().refresh(); + } } - - CommitBuilder commit = new CommitBuilder(); - commit.setTreeId(treeId); - if (headId != null) - commit.setParentIds(headId); - commit.setAuthor(author); - commit.setCommitter(author); - commit.setMessage(RepoText.get().repoCommitMessage); - - ObjectId commitId = inserter.insert(commit); - inserter.flush(); - - RefUpdate ru = repo.updateRef(targetBranch); - ru.setNewObjectId(commitId); - ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId()); - Result rc = ru.update(rw); - - switch (rc) { - case NEW: - case FORCED: - case FAST_FORWARD: - // Successful. Do nothing. - break; - case REJECTED: - case LOCK_FAILURE: - throw new ConcurrentRefUpdateException( - MessageFormat.format( - JGitText.get().cannotLock, targetBranch), - ru.getRef(), - rc); - default: - throw new JGitInternalException(MessageFormat.format( - JGitText.get().updatingRefFailed, - targetBranch, commitId.name(), rc)); - } - - return rw.parseCommit(commitId); - } catch (GitAPIException | IOException e) { + // In the last try, just propagate the exceptions + return commitTreeOnCurrentTip(inserter, rw, treeId); + } catch (GitAPIException | IOException | InterruptedException e) { throw new ManifestErrorException(e); } } @@ -742,6 +725,51 @@ public RevCommit call() throws GitAPIException { } } + + private RevCommit commitTreeOnCurrentTip(ObjectInserter inserter, + RevWalk rw, ObjectId treeId) + throws IOException, ConcurrentRefUpdateException { + ObjectId headId = repo.resolve(targetBranch + "^{commit}"); //$NON-NLS-1$ + if (headId != null && rw.parseCommit(headId).getTree().getId().equals(treeId)) { + // No change. Do nothing. + return rw.parseCommit(headId); + } + + CommitBuilder commit = new CommitBuilder(); + commit.setTreeId(treeId); + if (headId != null) + commit.setParentIds(headId); + commit.setAuthor(author); + commit.setCommitter(author); + commit.setMessage(RepoText.get().repoCommitMessage); + + ObjectId commitId = inserter.insert(commit); + inserter.flush(); + + RefUpdate ru = repo.updateRef(targetBranch); + ru.setNewObjectId(commitId); + ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId()); + Result rc = ru.update(rw); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + // Successful. Do nothing. + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(MessageFormat + .format(JGitText.get().cannotLock, targetBranch), + ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, + targetBranch, commitId.name(), rc)); + } + + return rw.parseCommit(commitId); + } + private void addSubmodule(String name, String url, String path, String revision, List copyfiles, List linkfiles, Git git) throws GitAPIException, IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index ab9fc5c9b..fd54986f9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -41,6 +41,9 @@ public static JGitText get() { /***/ public String aNewObjectIdIsRequired; /***/ public String anExceptionOccurredWhileTryingToAddTheIdOfHEAD; /***/ public String anSSHSessionHasBeenAlreadyCreated; + /***/ public String applyBinaryBaseOidWrong; + /***/ public String applyBinaryOidTooShort; + /***/ public String applyBinaryResultOidWrong; /***/ public String applyingCommit; /***/ public String archiveFormatAlreadyAbsent; /***/ public String archiveFormatAlreadyRegistered; @@ -65,7 +68,19 @@ public static JGitText get() { /***/ public String badSectionEntry; /***/ public String badShallowLine; /***/ public String bareRepositoryNoWorkdirAndIndex; + /***/ public String base85invalidChar; + /***/ public String base85length; + /***/ public String base85overflow; + /***/ public String base85tooLong; + /***/ public String base85tooShort; /***/ public String baseLengthIncorrect; + /***/ public String binaryDeltaBaseLengthMismatch; + /***/ public String binaryDeltaInvalidOffset; + /***/ public String binaryDeltaInvalidResultLength; + /***/ public String binaryHunkDecodeError; + /***/ public String binaryHunkInvalidLength; + /***/ public String binaryHunkLineTooShort; + /***/ public String binaryHunkMissingNewline; /***/ public String bitmapMissingObject; /***/ public String bitmapsMustBePrepared; /***/ public String blameNotCommittedYet; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java index 7d108feae..17a910008 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java @@ -244,47 +244,18 @@ public void refresh() { /** {@inheritDoc} */ @Override public boolean isNameConflicting(String name) throws IOException { - RefList packed = getPackedRefs(); - RefList loose = getLooseRefs(); - // Cannot be nested within an existing reference. int lastSlash = name.lastIndexOf('/'); while (0 < lastSlash) { String needle = name.substring(0, lastSlash); - if (loose.contains(needle) || packed.contains(needle)) + if (exactRef(needle) != null) { return true; + } lastSlash = name.lastIndexOf('/', lastSlash - 1); } // Cannot be the container of an existing reference. - String prefix = name + '/'; - int idx; - - idx = -(packed.find(prefix) + 1); - if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix)) - return true; - - idx = -(loose.find(prefix) + 1); - if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix)) - return true; - - return false; - } - - private RefList getLooseRefs() { - final RefList oldLoose = looseRefs.get(); - - LooseScanner scan = new LooseScanner(oldLoose); - scan.scan(ALL); - - RefList loose; - if (scan.newLoose != null) { - loose = scan.newLoose.toRefList(); - if (looseRefs.compareAndSet(oldLoose, loose)) - modCnt.incrementAndGet(); - } else - loose = oldLoose; - return loose; + return !getRefsByPrefix(name + '/').isEmpty(); } @Nullable diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java index de6a346cb..228c25f0a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2008, 2017, Google Inc. - * Copyright (C) 2017, 2018, Thomas Wolf and others + * Copyright (C) 2017, 2021, Thomas Wolf 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 @@ -21,7 +21,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -82,12 +83,6 @@ */ public class OpenSshConfigFile implements SshConfigStore { - /** - * "Host" name of the HostEntry for the default options before the first - * host block in a config file. - */ - private static final String DEFAULT_NAME = ""; //$NON-NLS-1$ - /** The user's home directory, as key files may be relative to here. */ private final File home; @@ -105,11 +100,9 @@ public class OpenSshConfigFile implements SshConfigStore { * fully resolved entries created from that. */ private static class State { - // Keyed by pattern; if a "Host" line has multiple patterns, we generate - // duplicate HostEntry objects - Map entries = new LinkedHashMap<>(); + List entries = new LinkedList<>(); - // Keyed by user@hostname:port + // Previous lookups, keyed by user@hostname:port Map hosts = new HashMap<>(); @Override @@ -165,14 +158,16 @@ public HostEntry lookup(@NonNull String hostName, int port, return h; } HostEntry fullConfig = new HostEntry(); - // Initialize with default entries at the top of the file, before the - // first Host block. - fullConfig.merge(cache.entries.get(DEFAULT_NAME)); - for (Map.Entry e : cache.entries.entrySet()) { - String pattern = e.getKey(); - if (isHostMatch(pattern, hostName)) { - fullConfig.merge(e.getValue()); - } + Iterator entries = cache.entries.iterator(); + if (entries.hasNext()) { + // Should always have at least the first top entry containing + // key-value pairs before the first Host block + fullConfig.merge(entries.next()); + entries.forEachRemaining(entry -> { + if (entry.matches(hostName)) { + fullConfig.merge(entry); + } + }); } fullConfig.substitute(hostName, port, userName, localUserName, home); cache.hosts.put(cacheKey, fullConfig); @@ -208,20 +203,19 @@ private synchronized State refresh() { return state; } - private Map parse(BufferedReader reader) + private List parse(BufferedReader reader) throws IOException { - final Map entries = new LinkedHashMap<>(); - final List current = new ArrayList<>(4); - String line; + final List entries = new LinkedList<>(); // The man page doesn't say so, but the openssh parser (readconf.c) // starts out in active mode and thus always applies any lines that // occur before the first host block. We gather those options in a // HostEntry for DEFAULT_NAME. HostEntry defaults = new HostEntry(); - current.add(defaults); - entries.put(DEFAULT_NAME, defaults); + HostEntry current = defaults; + entries.add(defaults); + String line; while ((line = reader.readLine()) != null) { // OpenSsh ignores trailing comments on a line. Anything after the // first # on a line is trimmed away (yes, even if the hash is @@ -246,38 +240,17 @@ private Map parse(BufferedReader reader) String argValue = parts.length > 1 ? parts[1].trim() : ""; //$NON-NLS-1$ if (StringUtils.equalsIgnoreCase(SshConstants.HOST, keyword)) { - current.clear(); - for (String name : parseList(argValue)) { - if (name == null || name.isEmpty()) { - // null should not occur, but better be safe than sorry. - continue; - } - HostEntry c = entries.get(name); - if (c == null) { - c = new HostEntry(); - entries.put(name, c); - } - current.add(c); - } - continue; - } - - if (current.isEmpty()) { - // We received an option outside of a Host block. We - // don't know who this should match against, so skip. + current = new HostEntry(parseList(argValue)); + entries.add(current); continue; } if (HostEntry.isListKey(keyword)) { List args = validate(keyword, parseList(argValue)); - for (HostEntry entry : current) { - entry.setValue(keyword, args); - } + current.setValue(keyword, args); } else if (!argValue.isEmpty()) { argValue = validate(keyword, dequote(argValue)); - for (HostEntry entry : current) { - entry.setValue(keyword, argValue); - } + current.setValue(keyword, argValue); } } @@ -300,7 +273,7 @@ private List parseList(String argument) { int length = argument.length(); while (start < length) { // Skip whitespace - if (Character.isSpaceChar(argument.charAt(start))) { + if (Character.isWhitespace(argument.charAt(start))) { start++; continue; } @@ -315,7 +288,7 @@ private List parseList(String argument) { } else { int stop = start + 1; while (stop < length - && !Character.isSpaceChar(argument.charAt(stop))) { + && !Character.isWhitespace(argument.charAt(stop))) { stop++; } result.add(argument.substring(start, stop)); @@ -358,13 +331,6 @@ protected List validate(String key, List value) { return value; } - private static boolean isHostMatch(String pattern, String name) { - if (pattern.startsWith("!")) { //$NON-NLS-1$ - return !patternMatchesHost(pattern.substring(1), name); - } - return patternMatchesHost(pattern, name); - } - private static boolean patternMatchesHost(String pattern, String name) { if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) { final FileNameMatcher fn; @@ -389,9 +355,12 @@ private static String dequote(String value) { private static String stripWhitespace(String value) { final StringBuilder b = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - if (!Character.isSpaceChar(value.charAt(i))) - b.append(value.charAt(i)); + int length = value.length(); + for (int i = 0; i < length; i++) { + char ch = value.charAt(i); + if (!Character.isWhitespace(ch)) { + b.append(ch); + } } return b.toString(); } @@ -511,6 +480,38 @@ public static class HostEntry implements SshConfigStore.HostConfig { private Map> listOptions; + private final List patterns; + + /** + * Constructor used to build the merged entry; never matches anything + */ + public HostEntry() { + this.patterns = Collections.emptyList(); + } + + /** + * @param patterns + * to be used in matching against host name. + */ + public HostEntry(List patterns) { + this.patterns = patterns; + } + + boolean matches(String hostName) { + boolean doesMatch = false; + for (String pattern : patterns) { + if (pattern.startsWith("!")) { //$NON-NLS-1$ + if (patternMatchesHost(pattern.substring(1), hostName)) { + return false; + } + } else if (!doesMatch + && patternMatchesHost(pattern, hostName)) { + doesMatch = true; + } + } + return doesMatch; + } + private static String toKey(String key) { String k = ALIASES.get(key); return k != null ? k : key; @@ -886,8 +887,8 @@ public void update(char key, String value) { public String substitute(String input, String allowed, boolean withEnv) { if (input == null || input.length() <= 1 - || input.indexOf('%') < 0 - && (!withEnv || input.indexOf("${") < 0)) { //$NON-NLS-1$ + || (input.indexOf('%') < 0 + && (!withEnv || input.indexOf("${") < 0))) { //$NON-NLS-1$ return input; } StringBuilder builder = new StringBuilder(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java index 06009f885..ef1379a23 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java @@ -13,7 +13,6 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; -import static java.util.stream.Collectors.toCollection; import java.io.IOException; import java.text.MessageFormat; @@ -29,7 +28,6 @@ import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.PushCertificate; import org.eclipse.jgit.transport.ReceiveCommand; @@ -495,42 +493,24 @@ public void execute(RevWalk walk, ProgressMonitor monitor, } } if (!commands2.isEmpty()) { - // What part of the name space is already taken - Collection takenNames = refdb.getRefs().stream() - .map(Ref::getName) - .collect(toCollection(HashSet::new)); - Collection takenPrefixes = getTakenPrefixes(takenNames); - - // Now to the update that may require more room in the name space + // Perform updates that may require more room in the name space for (ReceiveCommand cmd : commands2) { try { if (cmd.getResult() == NOT_ATTEMPTED) { cmd.updateType(walk); RefUpdate ru = newUpdate(cmd); - SWITCH: switch (cmd.getType()) { - case DELETE: - // Performed in the first phase - break; - case UPDATE: - case UPDATE_NONFASTFORWARD: - RefUpdate ruu = newUpdate(cmd); - cmd.setResult(ruu.update(walk)); - break; - case CREATE: - for (String prefix : getPrefixes(cmd.getRefName())) { - if (takenNames.contains(prefix)) { - cmd.setResult(Result.LOCK_FAILURE); - break SWITCH; - } - } - if (takenPrefixes.contains(cmd.getRefName())) { - cmd.setResult(Result.LOCK_FAILURE); - break SWITCH; - } - ru.setCheckConflicting(false); - takenPrefixes.addAll(getPrefixes(cmd.getRefName())); - takenNames.add(cmd.getRefName()); - cmd.setResult(ru.update(walk)); + switch (cmd.getType()) { + case DELETE: + // Performed in the first phase + break; + case UPDATE: + case UPDATE_NONFASTFORWARD: + RefUpdate ruu = newUpdate(cmd); + cmd.setResult(ruu.update(walk)); + break; + case CREATE: + cmd.setResult(ru.update(walk)); + break; } } } catch (IOException err) { @@ -602,14 +582,6 @@ public void execute(RevWalk walk, ProgressMonitor monitor) execute(walk, monitor, null); } - private static Collection getTakenPrefixes(Collection names) { - Collection ref = new HashSet<>(); - for (String name : names) { - addPrefixesTo(name, ref); - } - return ref; - } - /** * Get all path prefixes of a ref name. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 03c1ef904..3e3d9b569 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -397,8 +397,16 @@ public final class ConfigConstants { /** The "ff" key */ public static final String CONFIG_KEY_FF = "ff"; + /** + * The "conflictStyle" key. + * + * @since 5.12 + */ + public static final String CONFIG_KEY_CONFLICTSTYLE = "conflictStyle"; + /** * The "checkstat" key + * * @since 3.0 */ public static final String CONFIG_KEY_CHECKSTAT = "checkstat"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java index ea639332e..50fb9d226 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java @@ -36,6 +36,8 @@ public final class FetchV2Request extends FetchRequest { private final boolean doneReceived; + private final boolean waitForDone; + @NonNull private final List serverOptions; @@ -50,7 +52,8 @@ public final class FetchV2Request extends FetchRequest { @NonNull Set clientShallowCommits, int deepenSince, @NonNull List deepenNotRefs, int depth, @NonNull FilterSpec filterSpec, - boolean doneReceived, @NonNull Set clientCapabilities, + boolean doneReceived, boolean waitForDone, + @NonNull Set clientCapabilities, @Nullable String agent, @NonNull List serverOptions, boolean sidebandAll, @NonNull List packfileUriProtocols) { super(wantIds, depth, clientShallowCommits, filterSpec, @@ -59,6 +62,7 @@ public final class FetchV2Request extends FetchRequest { this.peerHas = requireNonNull(peerHas); this.wantedRefs = requireNonNull(wantedRefs); this.doneReceived = doneReceived; + this.waitForDone = waitForDone; this.serverOptions = requireNonNull(serverOptions); this.sidebandAll = sidebandAll; this.packfileUriProtocols = packfileUriProtocols; @@ -90,7 +94,14 @@ boolean wasDoneReceived() { } /** - * Options received in server-option lines. The caller can choose to act on + * @return true if the request had a "wait-for-done" line + */ + boolean wasWaitForDoneReceived() { + return waitForDone; + } + + /** + * Options received in server-option lines. The caller can choose to act on * these in an application-specific way * * @return Immutable list of server options received in the request @@ -141,6 +152,8 @@ static final class Builder { boolean doneReceived; + boolean waitForDone; + @Nullable String agent; @@ -279,6 +292,16 @@ Builder setDoneReceived() { return this; } + /** + * Mark that the "wait-for-done" line has been received. + * + * @return this builder + */ + Builder setWaitForDone() { + waitForDone = true; + return this; + } + /** * Value of an agent line received after the command and before the * arguments. E.g. "agent=a.b.c/1.0" should set "a.b.c/1.0". @@ -328,7 +351,7 @@ Builder addPackfileUriProtocol(@NonNull String value) { FetchV2Request build() { return new FetchV2Request(peerHas, wantedRefs, wantIds, clientShallowCommits, deepenSince, deepenNotRefs, - depth, filterSpec, doneReceived, clientCapabilities, + depth, filterSpec, doneReceived, waitForDone, clientCapabilities, agent, Collections.unmodifiableList(serverOptions), sidebandAll, Collections.unmodifiableList(packfileUriProtocols)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java index 36fce7a3f..c5e52bef9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java @@ -148,6 +148,13 @@ public final class GitProtocolConstants { */ public static final String OPTION_SIDEBAND_ALL = "sideband-all"; //$NON-NLS-1$ + /** + * The server waits for client to send "done" before sending any packs back. + * + * @since 5.13 + */ + public static final String OPTION_WAIT_FOR_DONE = "wait-for-done"; //$NON-NLS-1$ + /** * The client supports atomic pushes. If this option is used, the server * will update all refs within one atomic transaction. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java index faccc2518..92f0133f5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java @@ -19,6 +19,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF; import java.io.IOException; @@ -123,6 +124,8 @@ FetchV2Request parseFetchRequest(PacketLineIn pckIn) reqBuilder.addPeerHas(ObjectId.fromString(line2.substring(5))); } else if (line2.equals("done")) { //$NON-NLS-1$ reqBuilder.setDoneReceived(); + } else if (line2.equals(OPTION_WAIT_FOR_DONE)) { + reqBuilder.setWaitForDone(); } else if (line2.equals(OPTION_THIN_PACK)) { reqBuilder.addClientCapability(OPTION_THIN_PACK); } else if (line2.equals(OPTION_NO_PROGRESS)) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java index be55cd1b8..5cd5b334a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java @@ -118,7 +118,7 @@ private SshConstants() { * Key in an ssh config file; defines signature algorithms for public key * authentication as a comma-separated list. * - * @since 5.11 + * @since 5.11.1 */ public static final String PUBKEY_ACCEPTED_ALGORITHMS = "PubkeyAcceptedAlgorithms"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java index 83ffd4123..da97f1e58 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -120,7 +120,10 @@ static ProtocolVersion parse(@Nullable String name) { private final boolean allowReachableSha1InWant; private final boolean allowFilter; private final boolean allowSidebandAll; + private final boolean advertiseSidebandAll; + private final boolean advertiseWaitForDone; + final @Nullable ProtocolVersion protocolVersion; final String[] hideRefs; @@ -206,6 +209,8 @@ public TransferConfig(Config rc) { "uploadpack", "allowsidebandall", false); advertiseSidebandAll = rc.getBoolean("uploadpack", "advertisesidebandall", false); + advertiseWaitForDone = rc.getBoolean("uploadpack", + "advertisewaitfordone", false); } /** @@ -304,6 +309,14 @@ public boolean isAdvertiseSidebandAll() { return advertiseSidebandAll && allowSidebandAll; } + /** + * @return true to advertise wait-for-done all to the clients + * @since 5.13 + */ + public boolean isAdvertiseWaitForDone() { + return advertiseWaitForDone; + } + /** * Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured * hidden refs. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 7f1ddaab2..ecf175193 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -33,6 +33,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE; import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2_REQUEST; import static org.eclipse.jgit.util.RefMap.toRefMap; @@ -1192,9 +1193,10 @@ private void fetchV2(PacketLineOut pckOut) throws IOException { walk.assumeShallow(req.getClientShallowCommits()); if (req.wasDoneReceived()) { - processHaveLines(req.getPeerHas(), ObjectId.zeroId(), + processHaveLines( + req.getPeerHas(), ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE, false), - accumulator); + accumulator, req.wasWaitForDoneReceived() ? Option.WAIT_FOR_DONE : Option.NONE); } else { pckOut.writeString( GitProtocolConstants.SECTION_ACKNOWLEDGMENTS + '\n'); @@ -1205,8 +1207,8 @@ private void fetchV2(PacketLineOut pckOut) throws IOException { } processHaveLines(req.getPeerHas(), ObjectId.zeroId(), new PacketLineOut(NullOutputStream.INSTANCE, false), - accumulator); - if (okToGiveUp()) { + accumulator, Option.NONE); + if (!req.wasWaitForDoneReceived() && okToGiveUp()) { pckOut.writeString("ready\n"); //$NON-NLS-1$ } else if (commonBase.isEmpty()) { pckOut.writeString("NAK\n"); //$NON-NLS-1$ @@ -1214,7 +1216,7 @@ private void fetchV2(PacketLineOut pckOut) throws IOException { sectionSent = true; } - if (req.wasDoneReceived() || okToGiveUp()) { + if (req.wasDoneReceived() || (!req.wasWaitForDoneReceived() && okToGiveUp())) { if (mayHaveShallow) { if (sectionSent) pckOut.writeDelim(); @@ -1312,6 +1314,9 @@ private List getV2CapabilityAdvertisement() { ? OPTION_SIDEBAND_ALL + ' ' : "") + (cachedPackUriProvider != null ? "packfile-uris " : "") + + (transferConfig.isAdvertiseWaitForDone() + ? OPTION_WAIT_FOR_DONE + ' ' + : "") + OPTION_SHALLOW); caps.add(CAPABILITY_SERVER_OPTION); return caps; @@ -1656,7 +1661,7 @@ private boolean negotiate(FetchRequest req, } if (PacketLineIn.isEnd(line)) { - last = processHaveLines(peerHas, last, pckOut, accumulator); + last = processHaveLines(peerHas, last, pckOut, accumulator, Option.NONE); if (commonBase.isEmpty() || multiAck != MultiAck.OFF) pckOut.writeString("NAK\n"); //$NON-NLS-1$ if (noDone && sentReady) { @@ -1671,7 +1676,7 @@ private boolean negotiate(FetchRequest req, peerHas.add(ObjectId.fromString(line.substring(5))); accumulator.haves++; } else if (line.equals("done")) { //$NON-NLS-1$ - last = processHaveLines(peerHas, last, pckOut, accumulator); + last = processHaveLines(peerHas, last, pckOut, accumulator, Option.NONE); if (commonBase.isEmpty()) pckOut.writeString("NAK\n"); //$NON-NLS-1$ @@ -1687,8 +1692,14 @@ else if (multiAck != MultiAck.OFF) } } + private enum Option { + WAIT_FOR_DONE, + NONE; + } + private ObjectId processHaveLines(List peerHas, ObjectId last, - PacketLineOut out, PackStatistics.Accumulator accumulator) + PacketLineOut out, PackStatistics.Accumulator accumulator, + Option option) throws IOException { preUploadHook.onBeginNegotiateRound(this, wantIds, peerHas.size()); if (wantAll.isEmpty() && !wantIds.isEmpty()) @@ -1754,6 +1765,18 @@ private ObjectId processHaveLines(List peerHas, ObjectId last, // create a pack at this point, let the client know so it stops // telling us about its history. // + if (option != Option.WAIT_FOR_DONE) { + sentReady = shouldGiveUp(peerHas, out, missCnt); + } + + preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady); + peerHas.clear(); + return last; + } + + private boolean shouldGiveUp(List peerHas, PacketLineOut out, int missCnt) + throws IOException { + boolean sentReady = false; boolean didOkToGiveUp = false; if (0 < missCnt) { for (int i = peerHas.size() - 1; i >= 0; i--) { @@ -1765,10 +1788,12 @@ private ObjectId processHaveLines(List peerHas, ObjectId last, case OFF: break; case CONTINUE: - out.writeString("ACK " + id.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$ + out.writeString( + "ACK " + id.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$ break; case DETAILED: - out.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$ + out.writeString( + "ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$ sentReady = true; break; } @@ -1778,15 +1803,14 @@ private ObjectId processHaveLines(List peerHas, ObjectId last, } } - if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) { + if (multiAck == MultiAck.DETAILED && !didOkToGiveUp + && okToGiveUp()) { ObjectId id = peerHas.get(peerHas.size() - 1); out.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$ sentReady = true; } - preUploadHook.onEndNegotiateRound(this, wantAll, haveCnt, missCnt, sentReady); - peerHas.clear(); - return last; + return sentReady; } private void parseWants(PackStatistics.Accumulator accumulator) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base85.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base85.java new file mode 100644 index 000000000..54b7cfcaa --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base85.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.util; + +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.eclipse.jgit.internal.JGitText; + +/** + * Base-85 encoder/decoder. + * + * @since 5.12 + */ +public final class Base85 { + + private static final byte[] ENCODE = ("0123456789" //$NON-NLS-1$ + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" //$NON-NLS-1$ + + "abcdefghijklmnopqrstuvwxyz" //$NON-NLS-1$ + + "!#$%&()*+-;<=>?@^_`{|}~") //$NON-NLS-1$ + .getBytes(StandardCharsets.US_ASCII); + + private static final int[] DECODE = new int[256]; + + static { + Arrays.fill(DECODE, -1); + for (int i = 0; i < ENCODE.length; i++) { + DECODE[ENCODE[i]] = i; + } + } + + private Base85() { + // No instantiation + } + + /** + * Determines the length of the base-85 encoding for {@code rawLength} + * bytes. + * + * @param rawLength + * number of bytes to encode + * @return number of bytes needed for the base-85 encoding of + * {@code rawLength} bytes + */ + public static int encodedLength(int rawLength) { + return (rawLength + 3) / 4 * 5; + } + + /** + * Encodes the given {@code data} in Base-85. + * + * @param data + * to encode + * @return encoded data + */ + public static byte[] encode(byte[] data) { + return encode(data, 0, data.length); + } + + /** + * Encodes {@code length} bytes of {@code data} in Base-85, beginning at the + * {@code start} index. + * + * @param data + * to encode + * @param start + * index of the first byte to encode + * @param length + * number of bytes to encode + * @return encoded data + */ + public static byte[] encode(byte[] data, int start, int length) { + byte[] result = new byte[encodedLength(length)]; + int end = start + length; + int in = start; + int out = 0; + while (in < end) { + // Accumulate remaining bytes MSB first as a 32bit value + long accumulator = ((long) (data[in++] & 0xFF)) << 24; + if (in < end) { + accumulator |= (data[in++] & 0xFF) << 16; + if (in < end) { + accumulator |= (data[in++] & 0xFF) << 8; + if (in < end) { + accumulator |= (data[in++] & 0xFF); + } + } + } + // Write the 32bit value in base-85 encoding, also MSB first + for (int i = 4; i >= 0; i--) { + result[out + i] = ENCODE[(int) (accumulator % 85)]; + accumulator /= 85; + } + out += 5; + } + return result; + } + + /** + * Decodes the Base-85 {@code encoded} data into a byte array of + * {@code expectedSize} bytes. + * + * @param encoded + * Base-85 encoded data + * @param expectedSize + * of the result + * @return the decoded bytes + * @throws IllegalArgumentException + * if expectedSize doesn't match, the encoded data has a length + * that is not a multiple of 5, or there are invalid characters + * in the encoded data + */ + public static byte[] decode(byte[] encoded, int expectedSize) { + return decode(encoded, 0, encoded.length, expectedSize); + } + + /** + * Decodes {@code length} bytes of Base-85 {@code encoded} data, beginning + * at the {@code start} index, into a byte array of {@code expectedSize} + * bytes. + * + * @param encoded + * Base-85 encoded data + * @param start + * index at which the data to decode starts in {@code encoded} + * @param length + * of the Base-85 encoded data + * @param expectedSize + * of the result + * @return the decoded bytes + * @throws IllegalArgumentException + * if expectedSize doesn't match, {@code length} is not a + * multiple of 5, or there are invalid characters in the encoded + * data + */ + public static byte[] decode(byte[] encoded, int start, int length, + int expectedSize) { + if (length % 5 != 0) { + throw new IllegalArgumentException(JGitText.get().base85length); + } + byte[] result = new byte[expectedSize]; + int end = start + length; + int in = start; + int out = 0; + while (in < end && out < expectedSize) { + // Accumulate 5 bytes, "MSB" first + long accumulator = 0; + for (int i = 4; i >= 0; i--) { + int val = DECODE[encoded[in++] & 0xFF]; + if (val < 0) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().base85invalidChar, + Integer.toHexString(encoded[in - 1] & 0xFF))); + } + accumulator = accumulator * 85 + val; + } + if (accumulator > 0xFFFF_FFFFL) { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().base85overflow, + Long.toHexString(accumulator))); + } + // Write remaining bytes, MSB first + result[out++] = (byte) (accumulator >>> 24); + if (out < expectedSize) { + result[out++] = (byte) (accumulator >>> 16); + if (out < expectedSize) { + result[out++] = (byte) (accumulator >>> 8); + if (out < expectedSize) { + result[out++] = (byte) accumulator; + } + } + } + } + // Should have exhausted 'in' and filled 'out' completely + if (in < end) { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().base85tooLong, + Integer.valueOf(expectedSize))); + } + if (out < expectedSize) { + throw new IllegalArgumentException( + MessageFormat.format(JGitText.get().base85tooShort, + Integer.valueOf(expectedSize))); + } + return result; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryDeltaInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryDeltaInputStream.java new file mode 100644 index 000000000..9eceeb811 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryDeltaInputStream.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.util.io; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; + +/** + * An {@link InputStream} that applies a binary delta to a base on the fly. + *

+ * Delta application to a base needs random access to the base data. The delta + * is expressed as a sequence of copy and insert instructions. A copy + * instruction has the form "COPY fromOffset length" and says "copy length bytes + * from the base, starting at offset fromOffset, to the result". An insert + * instruction has the form "INSERT length" followed by length bytes and says + * "copy the next length bytes from the delta to the result". + *

+ *

+ * These instructions are generated using a content-defined chunking algorithm + * (currently C git uses the standard Rabin variant; but there are others that + * could be used) that identifies equal chunks. It is entirely possible that a + * later copy instruction has a fromOffset that is before the fromOffset of an + * earlier copy instruction. + *

+ *

+ * This makes it impossible to stream the base. + *

+ *

+ * JGit is limited to 2GB maximum size for the base since array indices are + * signed 32bit values. + * + * @since 5.12 + */ +public class BinaryDeltaInputStream extends InputStream { + + private final byte[] base; + + private final InputStream delta; + + private long resultLength; + + private long toDeliver = -1; + + private int fromBase; + + private int fromDelta; + + private int baseOffset = -1; + + /** + * Creates a new {@link BinaryDeltaInputStream} that applies {@code delta} + * to {@code base}. + * + * @param base + * data to apply the delta to + * @param delta + * {@link InputStream} delivering the delta to apply + */ + public BinaryDeltaInputStream(byte[] base, InputStream delta) { + this.base = base; + this.delta = delta; + } + + @Override + public int read() throws IOException { + int b = readNext(); + if (b >= 0) { + toDeliver--; + } + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return super.read(b, off, len); + } + + private void initialize() throws IOException { + long baseSize = readVarInt(delta); + if (baseSize > Integer.MAX_VALUE || baseSize < 0 + || (int) baseSize != base.length) { + throw new IOException(MessageFormat.format( + JGitText.get().binaryDeltaBaseLengthMismatch, + Integer.valueOf(base.length), Long.valueOf(baseSize))); + } + resultLength = readVarInt(delta); + if (resultLength < 0) { + throw new StreamCorruptedException( + JGitText.get().binaryDeltaInvalidResultLength); + } + toDeliver = resultLength; + baseOffset = 0; + } + + private int readNext() throws IOException { + if (baseOffset < 0) { + initialize(); + } + if (fromBase > 0) { + fromBase--; + return base[baseOffset++] & 0xFF; + } else if (fromDelta > 0) { + fromDelta--; + return delta.read(); + } + int command = delta.read(); + if (command < 0) { + return -1; + } + if ((command & 0x80) != 0) { + // Decode offset and length to read from base + long copyOffset = 0; + for (int i = 1, shift = 0; i < 0x10; i *= 2, shift += 8) { + if ((command & i) != 0) { + copyOffset |= ((long) next(delta)) << shift; + } + } + int copySize = 0; + for (int i = 0x10, shift = 0; i < 0x80; i *= 2, shift += 8) { + if ((command & i) != 0) { + copySize |= next(delta) << shift; + } + } + if (copySize == 0) { + copySize = 0x10000; + } + if (copyOffset > base.length - copySize) { + throw new StreamCorruptedException(MessageFormat.format( + JGitText.get().binaryDeltaInvalidOffset, + Long.valueOf(copyOffset), Integer.valueOf(copySize))); + } + baseOffset = (int) copyOffset; + fromBase = copySize; + return readNext(); + } else if (command != 0) { + // The next 'command' bytes come from the delta + fromDelta = command - 1; + return delta.read(); + } else { + // Zero is reserved + throw new StreamCorruptedException( + JGitText.get().unsupportedCommand0); + } + } + + private int next(InputStream in) throws IOException { + int b = in.read(); + if (b < 0) { + throw new EOFException(); + } + return b; + } + + private long readVarInt(InputStream in) throws IOException { + long val = 0; + int shift = 0; + int b; + do { + b = next(in); + val |= ((long) (b & 0x7f)) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return val; + } + + /** + * Tells the expected size of the final result. + * + * @return the size + * @throws IOException + * if the size cannot be determined from {@code delta} + */ + public long getExpectedResultSize() throws IOException { + if (baseOffset < 0) { + initialize(); + } + return resultLength; + } + + /** + * Tells whether the delta has been fully consumed, and the expected number + * of bytes for the combined result have been read from this + * {@link BinaryDeltaInputStream}. + * + * @return whether delta application was successful + */ + public boolean isFullyConsumed() { + try { + return toDeliver == 0 && delta.read() < 0; + } catch (IOException e) { + return toDeliver == 0; + } + } + + @Override + public void close() throws IOException { + delta.close(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java new file mode 100644 index 000000000..4f940d77a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkInputStream.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.util.io; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.StreamCorruptedException; +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.util.Base85; + +/** + * A stream that decodes git binary patch data on the fly. + * + * @since 5.12 + */ +public class BinaryHunkInputStream extends InputStream { + + private final InputStream in; + + private int lineNumber; + + private byte[] buffer; + + private int pos = 0; + + /** + * Creates a new {@link BinaryHunkInputStream}. + * + * @param in + * {@link InputStream} to read the base-85 encoded patch data + * from + */ + public BinaryHunkInputStream(InputStream in) { + this.in = in; + } + + @Override + public int read() throws IOException { + if (pos < 0) { + return -1; + } + if (buffer == null || pos == buffer.length) { + fillBuffer(); + } + if (pos >= 0) { + return buffer[pos++] & 0xFF; + } + return -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return super.read(b, off, len); + } + + @Override + public void close() throws IOException { + in.close(); + buffer = null; + } + + private void fillBuffer() throws IOException { + int length = in.read(); + if (length < 0) { + pos = length; + buffer = null; + return; + } + lineNumber++; + // Length is encoded with characters, A..Z for 1..26 and a..z for 27..52 + if ('A' <= length && length <= 'Z') { + length = length - 'A' + 1; + } else if ('a' <= length && length <= 'z') { + length = length - 'a' + 27; + } else { + throw new StreamCorruptedException(MessageFormat.format( + JGitText.get().binaryHunkInvalidLength, + Integer.valueOf(lineNumber), Integer.toHexString(length))); + } + byte[] encoded = new byte[Base85.encodedLength(length)]; + for (int i = 0; i < encoded.length; i++) { + int b = in.read(); + if (b < 0 || b == '\n') { + throw new EOFException(MessageFormat.format( + JGitText.get().binaryHunkInvalidLength, + Integer.valueOf(lineNumber))); + } + encoded[i] = (byte) b; + } + // Must be followed by a newline; tolerate EOF. + int b = in.read(); + if (b >= 0 && b != '\n') { + throw new StreamCorruptedException(MessageFormat.format( + JGitText.get().binaryHunkMissingNewline, + Integer.valueOf(lineNumber))); + } + try { + buffer = Base85.decode(encoded, length); + } catch (IllegalArgumentException e) { + StreamCorruptedException ex = new StreamCorruptedException( + MessageFormat.format(JGitText.get().binaryHunkDecodeError, + Integer.valueOf(lineNumber))); + ex.initCause(e); + throw ex; + } + pos = 0; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkOutputStream.java new file mode 100644 index 000000000..30551c09f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/BinaryHunkOutputStream.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 Thomas Wolf 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.util.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jgit.util.Base85; + +/** + * An {@link OutputStream} that encodes data for a git binary patch. + * + * @since 5.12 + */ +public class BinaryHunkOutputStream extends OutputStream { + + private static final int MAX_BYTES = 52; + + private final OutputStream out; + + private final byte[] buffer = new byte[MAX_BYTES]; + + private int pos; + + /** + * Creates a new {@link BinaryHunkOutputStream}. + * + * @param out + * {@link OutputStream} to write the encoded data to + */ + public BinaryHunkOutputStream(OutputStream out) { + this.out = out; + } + + /** + * Flushes and closes this stream, and closes the underlying + * {@link OutputStream}. + */ + @Override + public void close() throws IOException { + flush(); + out.close(); + } + + /** + * Writes any buffered output as a binary patch line to the underlying + * {@link OutputStream} and flushes that stream, too. + */ + @Override + public void flush() throws IOException { + if (pos > 0) { + encode(buffer, 0, pos); + pos = 0; + } + out.flush(); + } + + @Override + public void write(int b) throws IOException { + buffer[pos++] = (byte) b; + if (pos == buffer.length) { + encode(buffer, 0, pos); + pos = 0; + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return; + } + int toCopy = len; + int in = off; + if (pos > 0) { + // Fill the buffer + int chunk = Math.min(toCopy, buffer.length - pos); + System.arraycopy(b, in, buffer, pos, chunk); + in += chunk; + pos += chunk; + toCopy -= chunk; + if (pos == buffer.length) { + encode(buffer, 0, pos); + pos = 0; + } + if (toCopy == 0) { + return; + } + } + while (toCopy >= MAX_BYTES) { + encode(b, in, MAX_BYTES); + toCopy -= MAX_BYTES; + in += MAX_BYTES; + } + if (toCopy > 0) { + System.arraycopy(b, in, buffer, 0, toCopy); + pos = toCopy; + } + } + + private void encode(byte[] data, int off, int length) throws IOException { + if (length <= 26) { + out.write('A' + length - 1); + } else { + out.write('a' + length - 27); + } + out.write(Base85.encode(data, off, length)); + out.write('\n'); + } +}