diff options
150 files changed, 6056 insertions, 995 deletions
diff --git a/.gitignore b/.gitignore index ea8c4bf7f3..139e5aee6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/.project diff --git a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF index 8f5d0b90d3..c3b9a3790a 100644 --- a/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ant.test/META-INF/MANIFEST.MF @@ -3,14 +3,14 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.ant.test -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: org.apache.tools.ant, - org.eclipse.jgit.ant.tasks;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)", + org.eclipse.jgit.ant.tasks;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.junit;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)", org.hamcrest;version="[1.1.0,2.0.0)", org.junit;version="[4.0.0,5.0.0)" diff --git a/org.eclipse.jgit.ant.test/pom.xml b/org.eclipse.jgit.ant.test/pom.xml index a514e3dbc5..f8bf0838b3 100644 --- a/org.eclipse.jgit.ant.test/pom.xml +++ b/org.eclipse.jgit.ant.test/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.ant.test</artifactId> diff --git a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF index 4986f0fbb0..0cdf8bac2a 100644 --- a/org.eclipse.jgit.ant/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ant/META-INF/MANIFEST.MF @@ -2,11 +2,11 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.jgit.ant -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: org.apache.tools.ant, - org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)" + org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)" Bundle-Localization: plugin Bundle-Vendor: %Provider-Name -Export-Package: org.eclipse.jgit.ant.tasks;version="4.1.2"; +Export-Package: org.eclipse.jgit.ant.tasks;version="4.2.0"; uses:="org.apache.tools.ant.types,org.apache.tools.ant" diff --git a/org.eclipse.jgit.ant/pom.xml b/org.eclipse.jgit.ant/pom.xml index cb0fcb0a17..1a445ea877 100644 --- a/org.eclipse.jgit.ant/pom.xml +++ b/org.eclipse.jgit.ant/pom.xml @@ -48,7 +48,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.ant</artifactId> @@ -102,24 +102,92 @@ </configuration> </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - </plugin> + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>cmp</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> <reporting> - <plugins> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - <version>${clirr-version}</version> - <configuration> - <comparisonVersion>${jgit-last-release-version}</comparisonVersion> - <minSeverity>info</minSeverity> - </configuration> - </plugin> - </plugins> - </reporting> + <plugins> + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <reportSets> + <reportSet> + <reports> + <report>cmp-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + </plugin> + </plugins> + </reporting> </project> diff --git a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs index 4e28e0b26b..45d6d2c4c0 100644 --- a/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit.archive/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate diff --git a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF index 4b1e81d40d..2bd0164225 100644 --- a/org.eclipse.jgit.archive/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.archive/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.archive -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Vendor: %provider_name Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 @@ -12,16 +12,15 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.4,2.0)", org.apache.commons.compress.compressors.bzip2;version="[1.4,2.0)", org.apache.commons.compress.compressors.gzip;version="[1.4,2.0)", org.apache.commons.compress.compressors.xz;version="[1.4,2.0)", - org.eclipse.jgit.api;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)", + org.eclipse.jgit.api;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)", org.osgi.framework;version="[1.3.0,2.0.0)" Bundle-ActivationPolicy: lazy Bundle-Activator: org.eclipse.jgit.archive.FormatActivator -Export-Package: org.eclipse.jgit.archive;version="4.1.2"; +Export-Package: org.eclipse.jgit.archive;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.api, org.apache.commons.compress.archivers, org.osgi.framework" -Require-Bundle: org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional diff --git a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF index cc3181962d..8d7846b6a5 100644 --- a/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.jgit.archive/META-INF/SOURCE-MANIFEST.MF @@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.jgit.archive - Sources Bundle-SymbolicName: org.eclipse.jgit.archive.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.1.2.qualifier -Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.1.2.qualifier";roots="." +Bundle-Version: 4.2.0.qualifier +Eclipse-SourceBundle: org.eclipse.jgit.archive;version="4.2.0.qualifier";roots="." diff --git a/org.eclipse.jgit.archive/pom.xml b/org.eclipse.jgit.archive/pom.xml index 1127a773fb..c93da4bf49 100644 --- a/org.eclipse.jgit.archive/pom.xml +++ b/org.eclipse.jgit.archive/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.archive</artifactId> @@ -106,6 +106,93 @@ </archive> </configuration> </plugin> + + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>cmp</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> + + <reporting> + <plugins> + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <reportSets> + <reportSet> + <reports> + <report>cmp-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + </plugin> + </plugins> + </reporting> </project> diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF index 700badbca4..07f364c535 100644 --- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.jgit.http.apache -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-Localization: plugin Bundle-Vendor: %Provider-Name @@ -19,10 +19,10 @@ Import-Package: org.apache.http;version="[4.1.0,5.0.0)", org.apache.http.impl.client;version="[4.1.0,5.0.0)", org.apache.http.impl.client.cache;version="[4.1.0,5.0.0)", org.apache.http.params;version="[4.1.0,5.0.0)", - org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)" -Export-Package: org.eclipse.jgit.transport.http.apache;version="4.1.2"; + org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.http;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)" +Export-Package: org.eclipse.jgit.transport.http.apache;version="4.2.0"; uses:="org.eclipse.jgit.transport.http, javax.net.ssl, org.apache.http.client, diff --git a/org.eclipse.jgit.http.apache/pom.xml b/org.eclipse.jgit.http.apache/pom.xml index dee349cd80..68d3c22691 100644 --- a/org.eclipse.jgit.http.apache/pom.xml +++ b/org.eclipse.jgit.http.apache/pom.xml @@ -48,7 +48,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.http.apache</artifactId> @@ -96,24 +96,92 @@ </configuration> </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - </plugin> + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>cmp</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> <reporting> - <plugins> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - <version>${clirr-version}</version> - <configuration> - <comparisonVersion>${jgit-last-release-version}</comparisonVersion> - <minSeverity>info</minSeverity> - </configuration> - </plugin> - </plugins> - </reporting> + <plugins> + <plugin> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <reportSets> + <reportSet> + <reports> + <report>cmp-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + </plugin> + </plugins> + </reporting> </project> diff --git a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF index 85e1dd7d42..78c41c487c 100644 --- a/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.server/META-INF/MANIFEST.MF @@ -2,13 +2,13 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.http.server -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name -Export-Package: org.eclipse.jgit.http.server;version="4.1.2", - org.eclipse.jgit.http.server.glue;version="4.1.2"; +Export-Package: org.eclipse.jgit.http.server;version="4.2.0", + org.eclipse.jgit.http.server.glue;version="4.2.0"; uses:="javax.servlet,javax.servlet.http", - org.eclipse.jgit.http.server.resolver;version="4.1.2"; + org.eclipse.jgit.http.server.resolver;version="4.2.0"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.lib, org.eclipse.jgit.transport, @@ -17,12 +17,12 @@ Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: javax.servlet;version="[2.5.0,3.2.0)", javax.servlet.http;version="[2.5.0,3.2.0)", - org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.dfs;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)" + org.eclipse.jgit.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.dfs;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.resolver;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)" diff --git a/org.eclipse.jgit.http.server/pom.xml b/org.eclipse.jgit.http.server/pom.xml index 8707e01534..9859a1d126 100644 --- a/org.eclipse.jgit.http.server/pom.xml +++ b/org.eclipse.jgit.http.server/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.http.server</artifactId> @@ -127,8 +127,45 @@ </plugin> <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>cmp</goal> + </goals> + </execution> + </executions> </plugin> </plugins> </build> @@ -136,13 +173,44 @@ <reporting> <plugins> <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - <version>${clirr-version}</version> - <configuration> - <comparisonVersion>${jgit-last-release-version}</comparisonVersion> - <minSeverity>info</minSeverity> - </configuration> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <reportSets> + <reportSet> + <reports> + <report>cmp-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> </plugin> </plugins> </reporting> diff --git a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF index cff5d24fa2..f8bd4915f9 100644 --- a/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.test/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.http.test -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Vendor: %provider_name Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 @@ -22,23 +22,23 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)", org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.thread;version="[9.0.0,10.0.0)", - org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.http.server;version="[4.1.2,4.2.0)", - org.eclipse.jgit.http.server.glue;version="[4.1.2,4.2.0)", - org.eclipse.jgit.http.server.resolver;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", - org.eclipse.jgit.junit.http;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.http.apache;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)", + org.eclipse.jgit.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.http.server;version="[4.2.0,4.3.0)", + org.eclipse.jgit.http.server.glue;version="[4.2.0,4.3.0)", + org.eclipse.jgit.http.server.resolver;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.junit;version="[4.2.0,4.3.0)", + org.eclipse.jgit.junit.http;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.http;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.http.apache;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.resolver;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)", org.hamcrest.core;version="[1.1.0,2.0.0)", org.junit;version="[4.0.0,5.0.0)", org.junit.runner;version="[4.0.0,5.0.0)", diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml index 8f7cd03fd7..dd52a89e6c 100644 --- a/org.eclipse.jgit.http.test/pom.xml +++ b/org.eclipse.jgit.http.test/pom.xml @@ -51,7 +51,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.http.test</artifactId> diff --git a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF index aa1dc8b2f8..32bca1bda0 100644 --- a/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit.http/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.junit.http -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy @@ -20,16 +20,16 @@ Import-Package: javax.servlet;version="[2.5.0,3.2.0)", org.eclipse.jetty.util.component;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.log;version="[9.0.0,10.0.0)", org.eclipse.jetty.util.security;version="[9.0.0,10.0.0)", - org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.http.server;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", + org.eclipse.jgit.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.http.server;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.junit;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.resolver;version="[4.2.0,4.3.0)", org.junit;version="[4.0.0,5.0.0)" -Export-Package: org.eclipse.jgit.junit.http;version="4.1.2"; +Export-Package: org.eclipse.jgit.junit.http;version="4.2.0"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.junit, javax.servlet.http, diff --git a/org.eclipse.jgit.junit.http/pom.xml b/org.eclipse.jgit.junit.http/pom.xml index 40cdca158d..a61e6801a9 100644 --- a/org.eclipse.jgit.junit.http/pom.xml +++ b/org.eclipse.jgit.junit.http/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.junit.http</artifactId> diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF index 048f65d8f2..7449004ba8 100644 --- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF @@ -2,27 +2,27 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.junit -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Import-Package: org.eclipse.jgit.api;version="[4.1.2,4.2.0)", - org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", - org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.merge;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", +Import-Package: org.eclipse.jgit.api;version="[4.2.0,4.3.0)", + org.eclipse.jgit.api.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.dircache;version="[4.2.0,4.3.0)", + org.eclipse.jgit.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.merge;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.treewalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.treewalk.filter;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util.io;version="[4.2.0,4.3.0)", org.junit;version="[4.0.0,5.0.0)" -Export-Package: org.eclipse.jgit.junit;version="4.1.2"; +Export-Package: org.eclipse.jgit.junit;version="4.2.0"; uses:="org.eclipse.jgit.dircache, org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, diff --git a/org.eclipse.jgit.junit/pom.xml b/org.eclipse.jgit.junit/pom.xml index 53f1bdcc85..bf281e62b2 100644 --- a/org.eclipse.jgit.junit/pom.xml +++ b/org.eclipse.jgit.junit/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.junit</artifactId> diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java index 136c64726f..521593ea80 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java @@ -55,6 +55,7 @@ import java.io.Writer; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Path; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FileUtils; @@ -240,4 +241,10 @@ public abstract class JGitTestUtil { FileUtils.delete(path); } + public static Path writeLink(Repository db, String link, + String target) throws Exception { + return FileUtils.createSymLink(new File(db.getWorkTree(), link), + target); + } + } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index b5348f9980..bb5f9efb8f 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -51,7 +51,6 @@ import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.util.*; -import java.util.concurrent.TimeUnit; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -92,11 +91,12 @@ public abstract class LocalDiskRepositoryTestCase { /** A fake (but stable) identity for committer fields in the test. */ protected PersonIdent committer; + /** A {@link SystemReader} used to coordinate time, envars, etc. */ + protected MockSystemReader mockSystemReader; + private final List<Repository> toClose = new ArrayList<Repository>(); private File tmp; - private MockSystemReader mockSystemReader; - @Before public void setUp() throws Exception { tmp = File.createTempFile("jgit_test_", "_tmp"); @@ -171,9 +171,8 @@ public abstract class LocalDiskRepositoryTestCase { /** Increment the {@link #author} and {@link #committer} times. */ protected void tick() { - final long delta = TimeUnit.MILLISECONDS.convert(5 * 60, - TimeUnit.SECONDS); - final long now = author.getWhen().getTime() + delta; + mockSystemReader.tick(5 * 60); + final long now = mockSystemReader.getCurrentTime(); final int tz = mockSystemReader.getTimezone(now); author = new PersonIdent(author, now, tz); @@ -278,11 +277,10 @@ public abstract class LocalDiskRepositoryTestCase { throws IllegalStateException, IOException { DirCache dc = repo.readDirCache(); StringBuilder sb = new StringBuilder(); - TreeSet<Long> timeStamps = null; + TreeSet<Long> timeStamps = new TreeSet<Long>(); // iterate once over the dircache just to collect all time stamps if (0 != (includedOptions & MOD_TIME)) { - timeStamps = new TreeSet<Long>(); for (int i=0; i<dc.getEntryCount(); ++i) timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index d24dd44fff..03a2b1a584 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java @@ -62,6 +62,9 @@ import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; +/** + * Mock {@link SystemReader} for tests. + */ public class MockSystemReader extends SystemReader { private final class MockConfig extends FileBasedConfig { private MockConfig(File cfgLocation, FS fs) { @@ -79,6 +82,8 @@ public class MockSystemReader extends SystemReader { } } + long now = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 + final Map<String, String> values = new HashMap<String, String>(); FileBasedConfig userGitConfig; @@ -138,7 +143,17 @@ public class MockSystemReader extends SystemReader { @Override public long getCurrentTime() { - return 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 + return now; + } + + /** + * Adjusts the current time in seconds. + * + * @param secDelta + * number of seconds to add to the current time. + */ + public void tick(final int secDelta) { + now += secDelta * 1000L; } @Override diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java index ac4539a848..28c61778c7 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java @@ -55,6 +55,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.file.Path; import java.util.Map; import org.eclipse.jgit.api.Git; @@ -107,6 +108,11 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { return JGitTestUtil.writeTrashFile(db, name, data); } + protected Path writeLink(final String link, final String target) + throws Exception { + return JGitTestUtil.writeLink(db, link, target); + } + protected File writeTrashFile(final String subdir, final String name, final String data) throws IOException { diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index 925a6b0216..251e65f553 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -138,7 +138,7 @@ public class TestRepository<R extends Repository> { private final ObjectInserter inserter; - private long now; + private final MockSystemReader mockSystemReader; /** * Wrap a repository with test building tools. @@ -148,7 +148,7 @@ public class TestRepository<R extends Repository> { * @throws IOException */ public TestRepository(R db) throws IOException { - this(db, new RevWalk(db)); + this(db, new RevWalk(db), new MockSystemReader()); } /** @@ -161,11 +161,28 @@ public class TestRepository<R extends Repository> { * @throws IOException */ public TestRepository(R db, RevWalk rw) throws IOException { + this(db, rw, new MockSystemReader()); + } + + /** + * Wrap a repository with test building tools. + * + * @param db + * the test repository to write into. + * @param rw + * the RevObject pool to use for object lookup. + * @param reader + * the MockSystemReader to use for clock and other system + * operations. + * @throws IOException + */ + public TestRepository(R db, RevWalk rw, MockSystemReader reader) + throws IOException { this.db = db; this.git = Git.wrap(db); this.pool = rw; this.inserter = db.newObjectInserter(); - this.now = 1236977987000L; + this.mockSystemReader = reader; } /** @return the repository this helper class operates against. */ @@ -186,14 +203,25 @@ public class TestRepository<R extends Repository> { return git; } - /** @return current time adjusted by {@link #tick(int)}. */ + /** @return current date. */ + public Date getDate() { + return new Date(mockSystemReader.getCurrentTime()); + } + + /** + * @return current date. + * + * @deprecated Use {@link #getDate()} instead. + */ + @Deprecated public Date getClock() { - return new Date(now); + // Remove once Gitiles and Gerrit are using the updated JGit. + return getDate(); } /** @return timezone used for default identities. */ public TimeZone getTimeZone() { - return defaultCommitter.getTimeZone(); + return mockSystemReader.getTimeZone(); } /** @@ -203,18 +231,18 @@ public class TestRepository<R extends Repository> { * number of seconds to add to the current time. */ public void tick(final int secDelta) { - now += secDelta * 1000L; + mockSystemReader.tick(secDelta); } /** - * Set the author and committer using {@link #getClock()}. + * Set the author and committer using {@link #getDate()}. * * @param c * the commit builder to store. */ public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) { - c.setAuthor(new PersonIdent(defaultAuthor, new Date(now))); - c.setCommitter(new PersonIdent(defaultCommitter, new Date(now))); + c.setAuthor(new PersonIdent(defaultAuthor, getDate())); + c.setCommitter(new PersonIdent(defaultCommitter, getDate())); } /** @@ -392,8 +420,8 @@ public class TestRepository<R extends Repository> { c = new org.eclipse.jgit.lib.CommitBuilder(); c.setTreeId(tree); c.setParentIds(parents); - c.setAuthor(new PersonIdent(defaultAuthor, new Date(now))); - c.setCommitter(new PersonIdent(defaultCommitter, new Date(now))); + c.setAuthor(new PersonIdent(defaultAuthor, getDate())); + c.setCommitter(new PersonIdent(defaultCommitter, getDate())); c.setMessage(""); ObjectId id; try (ObjectInserter ins = inserter) { @@ -428,7 +456,7 @@ public class TestRepository<R extends Repository> { final TagBuilder t = new TagBuilder(); t.setObjectId(dst); t.setTag(name); - t.setTagger(new PersonIdent(defaultCommitter, new Date(now))); + t.setTagger(new PersonIdent(defaultCommitter, getDate())); t.setMessage(""); ObjectId id; try (ObjectInserter ins = inserter) { @@ -663,7 +691,7 @@ public class TestRepository<R extends Repository> { b.setParentId(head); b.setTreeId(merger.getResultTreeId()); b.setAuthor(commit.getAuthorIdent()); - b.setCommitter(new PersonIdent(defaultCommitter, new Date(now))); + b.setCommitter(new PersonIdent(defaultCommitter, getDate())); b.setMessage(commit.getFullMessage()); ObjectId result; try (ObjectInserter ins = inserter) { @@ -1100,7 +1128,7 @@ public class TestRepository<R extends Repository> { c.setAuthor(author); if (committer != null) { if (updateCommitterTime) - committer = new PersonIdent(committer, new Date(now)); + committer = new PersonIdent(committer, getDate()); c.setCommitter(committer); } diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml index 43575f525a..99e153edc6 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit" label="%featureName" - version="4.1.2.qualifier" + version="4.2.0.qualifier" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml index f7ef5ee250..e0bdd3871e 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml index f54ec78c50..ac37d07715 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.http.apache" label="%featureName" - version="4.1.2.qualifier" + version="4.2.0.qualifier" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml index 8be8b18b92..b1f8302fd5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.http.apache.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml index 8dbf9f8a3c..00f368cd73 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.junit" label="%featureName" - version="4.1.2.qualifier" + version="4.2.0.qualifier" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml index aa1d85cdc4..2bef9a6d31 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.junit.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml index 084c49f16c..254852f2c7 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.pgm" label="%featureName" - version="4.1.2.qualifier" + version="4.2.0.qualifier" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> @@ -27,7 +27,7 @@ version="0.0.0"/> <requires> - <import feature="org.eclipse.jgit" version="4.1.0" match="equivalent"/> + <import feature="org.eclipse.jgit" version="4.2.0" match="equivalent"/> </requires> <plugin diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml index 6010467d6e..91e00170b1 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml index 8e13dc9396..f7766ea3a2 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.pgm.source" label="%featureName" - version="4.1.2.qualifier" + version="4.2.0.qualifier" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml index b72f82f451..cc3b182797 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.pgm.source.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml index d7c1f6ad78..b6e6869feb 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.repository/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.repository</artifactId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml index 9a7e6e0ba8..967d6c23d5 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/feature.xml @@ -2,7 +2,7 @@ <feature id="org.eclipse.jgit.source" label="%featureName" - version="4.1.2.qualifier" + version="4.2.0.qualifier" provider-name="%providerName"> <description url="http://www.eclipse.org/jgit/"> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml index d710643114..8fd1da658f 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.source.feature/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <groupId>org.eclipse.jgit.feature</groupId> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF index 60492325ca..572ac376ee 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/META-INF/MANIFEST.MF @@ -2,4 +2,4 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: JGit Target Platform Bundle Bundle-SymbolicName: org.eclipse.jgit.target -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml index e8e036ed44..8699688649 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/pom.xml @@ -49,7 +49,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.target</artifactId> diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml index e1f71cbd71..dd329b4b08 100644 --- a/org.eclipse.jgit.packaging/pom.xml +++ b/org.eclipse.jgit.packaging/pom.xml @@ -53,7 +53,7 @@ <groupId>org.eclipse.jgit</groupId> <artifactId>jgit.tycho.parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> <packaging>pom</packaging> <name>JGit Tycho Parent</name> diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF index 08a08a828e..db23c3b55f 100644 --- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF @@ -2,27 +2,27 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.pgm.test -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Vendor: %provider_name Bundle-Localization: plugin Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Import-Package: org.eclipse.jgit.api;version="[4.1.2,4.2.0)", - org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.diff;version="[4.1.2,4.2.0)", - org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", - org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.merge;version="[4.1.2,4.2.0)", - org.eclipse.jgit.pgm;version="[4.1.2,4.2.0)", - org.eclipse.jgit.pgm.internal;version="[4.1.2,4.2.0)", - org.eclipse.jgit.pgm.opt;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", - org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", +Import-Package: org.eclipse.jgit.api;version="[4.2.0,4.3.0)", + org.eclipse.jgit.api.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.diff;version="[4.2.0,4.3.0)", + org.eclipse.jgit.dircache;version="[4.2.0,4.3.0)", + org.eclipse.jgit.junit;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.merge;version="[4.2.0,4.3.0)", + org.eclipse.jgit.pgm;version="[4.2.0,4.3.0)", + org.eclipse.jgit.pgm.internal;version="[4.2.0,4.3.0)", + org.eclipse.jgit.pgm.opt;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.treewalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util.io;version="[4.2.0,4.3.0)", org.hamcrest.core;bundle-version="[1.1.0,2.0.0)", org.junit;version="[4.4.0,5.0.0)", org.kohsuke.args4j;version="[2.0.12,2.1.0)" diff --git a/org.eclipse.jgit.pgm.test/pom.xml b/org.eclipse.jgit.pgm.test/pom.xml index 132645b97c..955674a062 100644 --- a/org.eclipse.jgit.pgm.test/pom.xml +++ b/org.eclipse.jgit.pgm.test/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.pgm.test</artifactId> diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java index 50ddfe04d8..559a6d5d40 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java @@ -164,6 +164,14 @@ public class CLIRepositoryTestCase extends LocalDiskRepositoryTestCase { .replaceAll("\t", "\\\\t"); } + protected void assertStringArrayEquals(String expected, String[] actual) { + // if there is more than one line, ignore last one if empty + assertEquals(1, + actual.length > 1 && actual[actual.length - 1].equals("") + ? actual.length - 1 : actual.length); + assertEquals(expected, actual[0]); + } + protected void assertArrayOfLinesEquals(String[] expected, String[] actual) { assertEquals(toText(expected), toText(actual)); } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java index 387eb2bbb4..7bf4c26c38 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java @@ -556,15 +556,6 @@ public class CheckoutTest extends CLIRepositoryTestCase { // assertEquals("a/c", exception.getConflictingPaths().get(1)); } - static private void assertStringArrayEquals(String expected, String[] actual) { - // if there is more than one line, ignore last one if empty - assertEquals( - 1, - actual.length > 1 && actual[actual.length - 1].equals("") ? actual.length - 1 - : actual.length); - assertEquals(expected, actual[0]); - } - @Test public void testCheckoutPath() throws Exception { Git git = new Git(db); diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java new file mode 100644 index 0000000000..0785c5c5ff --- /dev/null +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.pgm; + +import static org.junit.Assert.*; + +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 ResetTest extends CLIRepositoryTestCase { + + private Git git; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + } + + @Test + public void testResetSelf() throws Exception { + RevCommit commit = git.commit().setMessage("initial commit").call(); + assertStringArrayEquals("", + execute("git reset --hard " + commit.getId().name())); + assertEquals(commit.getId(), + git.getRepository().getRef("HEAD").getObjectId()); + } + + @Test + public void testResetPrevious() throws Exception { + RevCommit commit = git.commit().setMessage("initial commit").call(); + git.commit().setMessage("second commit").call(); + assertStringArrayEquals("", + execute("git reset --hard " + commit.getId().name())); + assertEquals(commit.getId(), + git.getRepository().getRef("HEAD").getObjectId()); + } + + @Test + public void testResetEmptyPath() throws Exception { + RevCommit commit = git.commit().setMessage("initial commit").call(); + assertStringArrayEquals("", + execute("git reset --hard " + commit.getId().name() + " --")); + assertEquals(commit.getId(), + git.getRepository().getRef("HEAD").getObjectId()); + } + + @Test + public void testResetPathDoubleDash() throws Exception { + resetPath(true); + } + + @Test + public void testResetPathNoDoubleDash() throws Exception { + resetPath(false); + } + + private void resetPath(boolean useDoubleDash) throws Exception { + // create files a and b + writeTrashFile("a", "Hello world a"); + writeTrashFile("b", "Hello world b"); + // stage the files + git.add().addFilepattern(".").call(); + // commit the files + RevCommit commit = git.commit().setMessage("files a & b").call(); + + // change both files a and b + writeTrashFile("a", "New Hello world a"); + writeTrashFile("b", "New Hello world b"); + // stage the files + git.add().addFilepattern(".").call(); + + // reset only file a + String cmd = String.format("git reset %s%s a", commit.getId().name(), + (useDoubleDash) ? " --" : ""); + assertStringArrayEquals("", execute(cmd)); + assertEquals(commit.getId(), + git.getRepository().getRef("HEAD").getObjectId()); + + org.eclipse.jgit.api.Status status = git.status().call(); + // assert that file a is unstaged + assertArrayEquals(new String[] { "a" }, + status.getModified().toArray()); + // assert that file b is still staged + assertArrayEquals(new String[] { "b" }, + status.getChanged().toArray()); + } + +} diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs index 4e28e0b26b..45d6d2c4c0 100644 --- a/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 3a78d94df0..567fd05750 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.pgm -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy Bundle-Localization: plugin @@ -10,38 +10,38 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.apache.commons.compress.archivers.tar;version="[1.3,2.0)", org.apache.commons.compress.archivers.zip;version="[1.3,2.0)", - org.eclipse.jgit.api;version="[4.1.2,4.2.0)", - org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.archive;version="[4.1.2,4.2.0)", - org.eclipse.jgit.awtui;version="[4.1.2,4.2.0)", - org.eclipse.jgit.blame;version="[4.1.2,4.2.0)", - org.eclipse.jgit.diff;version="[4.1.2,4.2.0)", - org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", - org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.gitrepo;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.merge;version="4.1.2", - org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", - org.eclipse.jgit.notes;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk.filter;version="[4.1.2,4.2.0)", - org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.storage.pack;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", - org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", + org.eclipse.jgit.api;version="[4.2.0,4.3.0)", + org.eclipse.jgit.api.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.archive;version="[4.2.0,4.3.0)", + org.eclipse.jgit.awtui;version="[4.2.0,4.3.0)", + org.eclipse.jgit.blame;version="[4.2.0,4.3.0)", + org.eclipse.jgit.diff;version="[4.2.0,4.3.0)", + org.eclipse.jgit.dircache;version="[4.2.0,4.3.0)", + org.eclipse.jgit.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.gitrepo;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.merge;version="4.2.0", + org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", + org.eclipse.jgit.notes;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revplot;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk.filter;version="[4.2.0,4.3.0)", + org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.resolver;version="[4.2.0,4.3.0)", + org.eclipse.jgit.treewalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.treewalk.filter;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util.io;version="[4.2.0,4.3.0)", org.kohsuke.args4j;version="[2.0.12,2.1.0)", org.kohsuke.args4j.spi;version="[2.0.15,2.1.0)" -Export-Package: org.eclipse.jgit.console;version="4.1.2"; +Export-Package: org.eclipse.jgit.console;version="4.2.0"; uses:="org.eclipse.jgit.transport, org.eclipse.jgit.util", - org.eclipse.jgit.pgm;version="4.1.2"; + org.eclipse.jgit.pgm;version="4.2.0"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.pgm.opt, @@ -52,15 +52,14 @@ Export-Package: org.eclipse.jgit.console;version="4.1.2"; org.eclipse.jgit.treewalk, javax.swing, org.eclipse.jgit.transport", - org.eclipse.jgit.pgm.debug;version="4.1.2"; + org.eclipse.jgit.pgm.debug;version="4.2.0"; uses:="org.eclipse.jgit.util.io, org.eclipse.jgit.pgm", - org.eclipse.jgit.pgm.internal;version="4.1.2";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test", - org.eclipse.jgit.pgm.opt;version="4.1.2"; + org.eclipse.jgit.pgm.internal;version="4.2.0";x-friends:="org.eclipse.jgit.pgm.test,org.eclipse.jgit.test", + org.eclipse.jgit.pgm.opt;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.kohsuke.args4j.spi, org.kohsuke.args4j" Main-Class: org.eclipse.jgit.pgm.Main Implementation-Title: JGit Command Line Interface -Require-Bundle: org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional diff --git a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF index 303ebf44f7..a3fef2e29a 100644 --- a/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/SOURCE-MANIFEST.MF @@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.jgit.pgm - Sources Bundle-SymbolicName: org.eclipse.jgit.pgm.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.1.2.qualifier -Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.1.2.qualifier";roots="." +Bundle-Version: 4.2.0.qualifier +Eclipse-SourceBundle: org.eclipse.jgit.pgm;version="4.2.0.qualifier";roots="." diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml index 42fde6fe9a..ca2ead2925 100644 --- a/org.eclipse.jgit.pgm/pom.xml +++ b/org.eclipse.jgit.pgm/pom.xml @@ -50,7 +50,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.pgm</artifactId> diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index f7591fd80b..64afdad51e 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -192,6 +192,7 @@ untrackedFiles=Untracked files: updating=Updating {0}..{1} usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time usage_bareClone=Make a bare Git repository. That is, instead of creating [DIRECTORY] and placing the administrative files in [DIRECTORY]/.git, make the [DIRECTORY] itself the $GIT_DIR. +usage_branches=Set branch field in .gitmodules usage_Blame=Show what revision and author last modified each line usage_CommandLineClientForamazonsS3Service=Command line client for Amazon's S3 service usage_CommitAll=commit all modified and deleted files diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java index 9b191e6796..db88008e10 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java @@ -58,12 +58,16 @@ class Repo extends TextBuiltin { @Argument(required = true, usage = "usage_pathToXml") private String path; + @Option(name = "--record-remote-branch", usage = "usage_branches") + private boolean branches; + @Override protected void run() throws Exception { new RepoCommand(db) .setURI(uri) .setPath(path) .setGroups(groups) + .setRecordRemoteBranch(branches) .call(); } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java index 6d1b1c5481..6ba076290e 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java @@ -43,11 +43,15 @@ package org.eclipse.jgit.pgm; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; +import org.kohsuke.args4j.spi.StopOptionHandler; @Command(common = true, usage = "usage_reset") class Reset extends TextBuiltin { @@ -61,24 +65,33 @@ class Reset extends TextBuiltin { @Option(name = "--hard", usage = "usage_resetHard") private boolean hard = false; - @Argument(required = true, metaVar = "metaVar_name", usage = "usage_reset") + @Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_reset") private String commit; + @Argument(index = 1) + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class) + private List<String> paths = new ArrayList<String>(); + @Override protected void run() throws Exception { try (Git git = new Git(db)) { ResetCommand command = git.reset(); command.setRef(commit); - ResetType mode = null; - if (soft) - mode = selectMode(mode, ResetType.SOFT); - if (mixed) - mode = selectMode(mode, ResetType.MIXED); - if (hard) - mode = selectMode(mode, ResetType.HARD); - if (mode == null) - throw die("no reset mode set"); - command.setMode(mode); + if (paths.size() > 0) { + for (String path : paths) + command.addPath(path); + } else { + ResetType mode = null; + if (soft) + mode = selectMode(mode, ResetType.SOFT); + if (mixed) + mode = selectMode(mode, ResetType.MIXED); + if (hard) + mode = selectMode(mode, ResetType.HARD); + if (mode == null) + throw die("no reset mode set"); + command.setMode(mode); + } command.call(); } } diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 8f2f4284f0..37fd367171 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -2,54 +2,55 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.test -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", - org.eclipse.jgit.api;version="[4.1.2,4.2.0)", - org.eclipse.jgit.api.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.attributes;version="[4.1.2,4.2.0)", - org.eclipse.jgit.awtui;version="[4.1.2,4.2.0)", - org.eclipse.jgit.blame;version="[4.1.2,4.2.0)", - org.eclipse.jgit.diff;version="[4.1.2,4.2.0)", - org.eclipse.jgit.dircache;version="[4.1.2,4.2.0)", - org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.events;version="[4.1.2,4.2.0)", - org.eclipse.jgit.fnmatch;version="[4.1.2,4.2.0)", - org.eclipse.jgit.gitrepo;version="[4.1.2,4.2.0)", - org.eclipse.jgit.hooks;version="[4.1.2,4.2.0)", - org.eclipse.jgit.ignore;version="[4.1.2,4.2.0)", - org.eclipse.jgit.ignore.internal;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.dfs;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.internal.storage.pack;version="[4.1.2,4.2.0)", - org.eclipse.jgit.junit;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.merge;version="[4.1.2,4.2.0)", - org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", - org.eclipse.jgit.notes;version="[4.1.2,4.2.0)", - org.eclipse.jgit.patch;version="[4.1.2,4.2.0)", - org.eclipse.jgit.pgm;version="[4.1.2,4.2.0)", - org.eclipse.jgit.pgm.internal;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk.filter;version="[4.1.2,4.2.0)", - org.eclipse.jgit.storage.file;version="[4.1.2,4.2.0)", - org.eclipse.jgit.storage.pack;version="[4.1.2,4.2.0)", - org.eclipse.jgit.submodule;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.http;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport.resolver;version="[4.1.2,4.2.0)", - org.eclipse.jgit.treewalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.treewalk.filter;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util.io;version="[4.1.2,4.2.0)", + org.eclipse.jgit.api;version="[4.2.0,4.3.0)", + org.eclipse.jgit.api.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.attributes;version="[4.2.0,4.3.0)", + org.eclipse.jgit.awtui;version="[4.2.0,4.3.0)", + org.eclipse.jgit.blame;version="[4.2.0,4.3.0)", + org.eclipse.jgit.diff;version="[4.2.0,4.3.0)", + org.eclipse.jgit.dircache;version="[4.2.0,4.3.0)", + org.eclipse.jgit.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.events;version="[4.2.0,4.3.0)", + org.eclipse.jgit.fnmatch;version="[4.2.0,4.3.0)", + org.eclipse.jgit.gitrepo;version="[4.2.0,4.3.0)", + org.eclipse.jgit.hooks;version="[4.2.0,4.3.0)", + org.eclipse.jgit.ignore;version="[4.2.0,4.3.0)", + org.eclipse.jgit.ignore.internal;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.dfs;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.junit;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.merge;version="[4.2.0,4.3.0)", + org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", + org.eclipse.jgit.notes;version="[4.2.0,4.3.0)", + org.eclipse.jgit.patch;version="[4.2.0,4.3.0)", + org.eclipse.jgit.pgm;version="[4.2.0,4.3.0)", + org.eclipse.jgit.pgm.internal;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revplot;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk.filter;version="[4.2.0,4.3.0)", + org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)", + org.eclipse.jgit.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.submodule;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.http;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.resolver;version="[4.2.0,4.3.0)", + org.eclipse.jgit.treewalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.treewalk.filter;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util.io;version="[4.2.0,4.3.0)", org.hamcrest;version="[1.1.0,2.0.0)", org.junit;version="[4.4.0,5.0.0)", org.junit.experimental.theories;version="[4.4.0,5.0.0)", org.junit.runner;version="[4.4.0,5.0.0)", - org.junit.runners;version="[4.11.0,5.0.0)" + org.junit.runners;version="[4.11.0,5.0.0)", + org.slf4j;version="[1.7.2,2.0.0)" Require-Bundle: org.hamcrest.core;bundle-version="[1.1.0,2.0.0)" diff --git a/org.eclipse.jgit.test/build.properties b/org.eclipse.jgit.test/build.properties index afc4855d67..786046c58a 100644 --- a/org.eclipse.jgit.test/build.properties +++ b/org.eclipse.jgit.test/build.properties @@ -4,3 +4,4 @@ source.. = tst/,\ bin.includes = META-INF/,\ .,\ plugin.properties +additional.bundles = org.apache.log4j diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java new file mode 100644 index 0000000000..db5f1b2eb6 --- /dev/null +++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2015, Sebastien Arod <sebastien.arod@gmail.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.ignore; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import org.eclipse.jgit.api.Git; +import org.junit.Assert; +import org.junit.Test; + +/** + * This test generates random ignore patterns and random path and compares the + * output of Cgit check-ignore to the output of {@link FastIgnoreRule}. + */ +public class CGitVsJGitRandomIgnorePatternTest { + + private static class PseudoRandomPatternGenerator { + + private static final int DEFAULT_MAX_FRAGMENTS_PER_PATTERN = 15; + + /** + * Generates 75% Special fragments and 25% "standard" characters + */ + private static final double DEFAULT_SPECIAL_FRAGMENTS_FREQUENCY = 0.75d; + + private static final List<String> SPECIAL_FRAGMENTS = Arrays.asList( + "\\", "!", "#", "[", "]", "|", "/", "*", "?", "{", "}", "(", + ")", "\\d", "(", "**", "[a\\]]", "\\ ", "+", "-", "^", "$", ".", + ":", "=", "[[:", ":]]" + + ); + + private static final String STANDARD_CHARACTERS = new String( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); + + private final Random random = new Random(); + + private final int maxFragmentsPerPattern; + + private final double specialFragmentsFrequency; + + public PseudoRandomPatternGenerator() { + this(DEFAULT_MAX_FRAGMENTS_PER_PATTERN, + DEFAULT_SPECIAL_FRAGMENTS_FREQUENCY); + } + + public PseudoRandomPatternGenerator(int maxFragmentsPerPattern, + double specialFragmentsFrequency) { + this.maxFragmentsPerPattern = maxFragmentsPerPattern; + this.specialFragmentsFrequency = specialFragmentsFrequency; + } + + public String nextRandomString() { + StringBuilder builder = new StringBuilder(); + int length = randomFragmentCount(); + for (int i = 0; i < length; i++) { + if (useSpecialFragment()) { + builder.append(randomSpecialFragment()); + } else { + builder.append(randomStandardCharacters()); + } + + } + return builder.toString(); + } + + private int randomFragmentCount() { + // We want at least one fragment + return 1 + random.nextInt(maxFragmentsPerPattern - 1); + } + + private char randomStandardCharacters() { + return STANDARD_CHARACTERS + .charAt(random.nextInt(STANDARD_CHARACTERS.length())); + } + + private boolean useSpecialFragment() { + return random.nextDouble() < specialFragmentsFrequency; + } + + private String randomSpecialFragment() { + return SPECIAL_FRAGMENTS + .get(random.nextInt(SPECIAL_FRAGMENTS.size())); + } + } + + @SuppressWarnings("serial") + public static class CgitFatalException extends Exception { + + public CgitFatalException(int cgitExitCode, String pattern, String path, + String cgitStdError) { + super("CgitFatalException (" + cgitExitCode + ") for pattern:[" + + pattern + "] and path:[" + path + "]\n" + cgitStdError); + } + + } + + public static class CGitIgnoreRule { + + private File gitDir; + + private String pattern; + + public CGitIgnoreRule(File gitDir, String pattern) + throws UnsupportedEncodingException, IOException { + this.gitDir = gitDir; + this.pattern = pattern; + Files.write(new File(gitDir, ".gitignore").toPath(), + (pattern + "\n").getBytes("UTF-8"), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE); + } + + public boolean isMatch(String path) + throws IOException, InterruptedException, CgitFatalException { + Process proc = startCgitCheckIgnore(path); + + String cgitStdOutput = readProcessStream(proc.getInputStream()); + String cgitStdError = readProcessStream(proc.getErrorStream()); + + int cgitExitCode = proc.waitFor(); + + if (cgitExitCode == 128) { + throw new CgitFatalException(cgitExitCode, pattern, path, + cgitStdError); + } + return !cgitStdOutput.startsWith("::"); + } + + private Process startCgitCheckIgnore(String path) throws IOException { + // Use --stdin instead of using argument otherwise paths starting + // with "-" were interpreted as + // options by git check-ignore + String[] command = new String[] { "git", "check-ignore", + "--no-index", "-v", "-n", "--stdin" }; + Process proc = Runtime.getRuntime().exec(command, new String[0], + gitDir); + OutputStream out = proc.getOutputStream(); + out.write((path + "\n").getBytes("UTF-8")); + out.flush(); + out.close(); + return proc; + } + + private String readProcessStream(InputStream processStream) + throws IOException { + try (BufferedReader stdOut = new BufferedReader( + new InputStreamReader(processStream))) { + + StringBuilder out = new StringBuilder(); + String s; + while ((s = stdOut.readLine()) != null) { + out.append(s); + } + return out.toString(); + } + } + } + + private static final int NB_PATTERN = 1000; + + private static final int PATH_PER_PATTERN = 1000; + + @Test + public void testRandomPatterns() throws Exception { + // Initialize new git repo + File gitDir = Files.createTempDirectory("jgit").toFile(); + Git.init().setDirectory(gitDir).call(); + PseudoRandomPatternGenerator generator = new PseudoRandomPatternGenerator(); + + // Generate random patterns and paths + for (int i = 0; i < NB_PATTERN; i++) { + String pattern = generator.nextRandomString(); + + FastIgnoreRule jgitIgnoreRule = new FastIgnoreRule(pattern); + CGitIgnoreRule cgitIgnoreRule = new CGitIgnoreRule(gitDir, pattern); + + // Test path with pattern as path + assertCgitAndJgitMatch(pattern, jgitIgnoreRule, cgitIgnoreRule, + pattern); + + for (int p = 0; p < PATH_PER_PATTERN; p++) { + String path = generator.nextRandomString(); + assertCgitAndJgitMatch(pattern, jgitIgnoreRule, cgitIgnoreRule, + path); + } + } + } + + @SuppressWarnings({ "boxing" }) + private void assertCgitAndJgitMatch(String pattern, + FastIgnoreRule jgitIgnoreRule, CGitIgnoreRule cgitIgnoreRule, + String pathToTest) throws IOException, InterruptedException { + + try { + boolean cgitMatch = cgitIgnoreRule.isMatch(pathToTest); + boolean jgitMatch = jgitIgnoreRule.isMatch(pathToTest, + pathToTest.endsWith("/")); + if (cgitMatch != jgitMatch) { + System.err.println( + buildAssertionToAdd(pattern, pathToTest, cgitMatch)); + } + Assert.assertEquals("jgit:" + jgitMatch + " <> cgit:" + cgitMatch + + " for pattern:[" + pattern + "] and path:[" + pathToTest + + "]", cgitMatch, jgitMatch); + } catch (CgitFatalException e) { + // Lots of generated patterns or path are rejected by Cgit with a + // fatal error. We want to ignore them. + } + } + + private String buildAssertionToAdd(String pattern, String pathToTest, + boolean cgitMatch) { + return "assertMatch(" + toJavaString(pattern) + ", " + + toJavaString(pathToTest) + ", " + cgitMatch + + " /*cgit result*/);"; + } + + private String toJavaString(String pattern2) { + return "\"" + pattern2.replace("\\", "\\\\").replace("\"", "\\\"") + + "\""; + } +} diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest-Proxy.launch b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest-Proxy.launch new file mode 100644 index 0000000000..fe3a013720 --- /dev/null +++ b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest-Proxy.launch @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.eclipse.m2e.Maven2LaunchConfigurationType"> +<booleanAttribute key="M2_DEBUG_OUTPUT" value="false"/> +<stringAttribute key="M2_GOALS" value="test --define test=WalkEncryptionTest --define http_proxy=http://proxy:3128"/> +<booleanAttribute key="M2_NON_RECURSIVE" value="false"/> +<booleanAttribute key="M2_OFFLINE" value="false"/> +<stringAttribute key="M2_PROFILES" value=""/> +<listAttribute key="M2_PROPERTIES"/> +<stringAttribute key="M2_RUNTIME" value="EMBEDDED"/> +<booleanAttribute key="M2_SKIP_TESTS" value="false"/> +<intAttribute key="M2_THREADS" value="1"/> +<booleanAttribute key="M2_UPDATE_SNAPSHOTS" value="false"/> +<stringAttribute key="M2_USER_SETTINGS" value=""/> +<booleanAttribute key="M2_WORKSPACE_RESOLUTION" value="false"/> +<listAttribute key="org.eclipse.debug.ui.favoriteGroups"> +<listEntry value="org.eclipse.debug.ui.launchGroup.run"/> +</listAttribute> +<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-8-oracle"/> +<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:/org.eclipse.jgit.test}"/> +</launchConfiguration> diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest.launch b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest.launch new file mode 100644 index 0000000000..3b4a5a24e1 --- /dev/null +++ b/org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest.launch @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.eclipse.m2e.Maven2LaunchConfigurationType"> +<booleanAttribute key="M2_DEBUG_OUTPUT" value="false"/> +<stringAttribute key="M2_GOALS" value="test --define test=WalkEncryptionTest --activate-profiles test.long"/> +<booleanAttribute key="M2_NON_RECURSIVE" value="false"/> +<booleanAttribute key="M2_OFFLINE" value="false"/> +<stringAttribute key="M2_PROFILES" value=""/> +<listAttribute key="M2_PROPERTIES"/> +<stringAttribute key="M2_RUNTIME" value="EMBEDDED"/> +<booleanAttribute key="M2_SKIP_TESTS" value="false"/> +<intAttribute key="M2_THREADS" value="1"/> +<booleanAttribute key="M2_UPDATE_SNAPSHOTS" value="false"/> +<stringAttribute key="M2_USER_SETTINGS" value=""/> +<booleanAttribute key="M2_WORKSPACE_RESOLUTION" value="false"/> +<listAttribute key="org.eclipse.debug.ui.favoriteGroups"> +<listEntry value="org.eclipse.debug.ui.launchGroup.run"/> +</listAttribute> +<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-8-oracle"/> +<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc:/org.eclipse.jgit.test}"/> +</launchConfiguration> diff --git a/org.eclipse.jgit.test/pom.xml b/org.eclipse.jgit.test/pom.xml index c174ba270b..539caff149 100644 --- a/org.eclipse.jgit.test/pom.xml +++ b/org.eclipse.jgit.test/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.test</artifactId> @@ -69,6 +69,16 @@ <scope>test</scope> </dependency> + <!-- Optional security provider for encryption tests. --> + <!-- See https://dev.eclipse.org/ipzilla/show_bug.cgi?id=9554 --> + <!-- See https://bugs.eclipse.org/bugs/show_bug.cgi?id=467064 --> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + <version>1.52</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> @@ -101,6 +111,24 @@ </dependency> </dependencies> + <profiles> + <!-- Profile provides a property which enables long running tests. --> + <profile> + <id>test.long</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine>-Djgit.test.long=true</argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> + <build> <sourceDirectory>src/</sourceDirectory> <testSourceDirectory>tst/</testSourceDirectory> diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties new file mode 100644 index 0000000000..d540977e94 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties @@ -0,0 +1,48 @@ +# +# See WalkEncryptionTest.java +# +# This file is a template for test configuration file used by WalkEncryptionTest. +# To be active, this file must have the following hard coded name: jgit-s3-config.properties +# To be active, this file must be discovered by WalkEncryptionTest from one of these locations: +# * ${user.home}/jgit-s3-config.properties +# * ${user.dir}/jgit-s3-config.properties +# * ${user.dir}/tst-rsrc/jgit-s3-config.properties +# When this file is missing, tests in WalkEncryptionTest will not run, only report a warning. +# + +# +# WalkEncryptionTest requires amazon s3 test bucket setup. +# +# Test bucket setup instructions: +# +# Create IAM user: +# http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html +# * user name: jgit.eclipse.org +# +# Configure IAM user S3 bucket access +# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-policies-s3.html +# * attach S3 user policy to user account: jgit-s3-config.policy.user.json +# +# Create S3 bucket: +# http://docs.aws.amazon.com/AmazonS3/latest/gsg/CreatingABucket.html +# * bucket name: jgit.eclipse.org +# +# Configure S3 bucket source address/mask access: +# http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html +# * attach bucket policy to the test bucket: jgit-s3-config.policy.bucket.json +# * verify that any required source address/mask is included in the bucket policy: +# * see https://wiki.eclipse.org/Hudson +# * see http://www.tcpiputils.com/browse/ip-address/198.41.30.200 +# * proxy.eclipse.org 198.41.30.0/24 +# * Andrei Pozolotin 67.175.188.187/32 +# +# Configure bucket 1 day expiration in object life cycle management: +# * https://docs.aws.amazon.com/AmazonS3/latest/dev/manage-lifecycle-using-console.html +# + +# Test bucket name +test.bucket=jgit.eclipse.org + +# IAM credentials for user jgit.eclipse.org +accesskey=AKIAIYWXB4ETREBRMZDQ +secretkey=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 diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json new file mode 100644 index 0000000000..3020b09a00 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json @@ -0,0 +1,20 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DenyAllButKnownSourceAddressWithMask", + "Effect": "Deny", + "Principal": "*", + "Action": "s3:*", + "Resource": "arn:aws:s3:::jgit.eclipse.org/*", + "Condition": { + "NotIpAddress": { + "aws:SourceIp": [ + "198.41.30.0/24", + "67.175.188.187/32" + ] + } + } + } + ] +} diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json new file mode 100644 index 0000000000..830d0888c0 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json @@ -0,0 +1,24 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "BucketList", + "Effect": "Allow", + "Action": "s3:ListAllMyBuckets", + "Resource": [ + "arn:aws:s3:::jgit.eclipse.org" + ] + }, + { + "Sid": "BucketFullControl", + "Effect": "Allow", + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::jgit.eclipse.org", + "arn:aws:s3:::jgit.eclipse.org/*" + ] + } + ] +} diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties new file mode 100644 index 0000000000..2402a4985a --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties @@ -0,0 +1,11 @@ +# +# Sample Amazon S3 connection configuration file, Version 0. +# Version 0 (or lack of version) will produce JetS3tV2 compatible encryption. +# JetS3tV2 supports only PBE algorithms, with partially compromised AES mode. +# + +accesskey = AKIAIYWXB4ETREBRM123 +secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234 + +crypto.algorithm = PBEWithMD5AndDES +password = secret diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties new file mode 100644 index 0000000000..d0d16118e9 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties @@ -0,0 +1,14 @@ +# +# Sample Amazon S3 connection configuration file, Version 1. +# Version 1 will produce JGitV1 compatible encryption. +# It is JetS3tV2-like mode with proper AES support. +# JGitV1 uses hard coded encryption parameters. +# JGitV1 supports only PBE algorithms. +# + +accesskey = AKIAIYWXB4ETREBRM123 +secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234 + +crypto.algorithm = PBEWithHmacSHA1AndAES_128 +crypto.version = 1 +password = secret diff --git a/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties new file mode 100644 index 0000000000..731b3247d2 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties @@ -0,0 +1,48 @@ +# +# Sample Amazon S3 connection configuration file, Version 2. +# Version 2 will produce JGitV2 compatible encryption. +# JGitV2 introduces more flexible control over cipher and key factory parameters. +# JGitV2 hides actual cipher/key algorithms inside the encryption profile. +# JGitV2 does not use any hard coded encryption parameters. +# JGitV2 supports both PBE and Non-PBE algorithms. + +accesskey = AKIAIYWXB4ETREBRM123 +secretkey = ozCuIsqxsARoPe3FFyv3F/jiMSc3Yqay7B9UF234 + +# In Version 2 "crypto.algorithm" is a reference to the encryption "profile". +crypto.algorithm = custom +crypto.version = 2 +password = secret + +# +# Encryption profile is a collection of related properties, +# all having common property root name, or prefix: +# +# Cipher algorithm. +custom.algo = AES/CBC/PKCS5Padding +# Key factory algorithm. +custom.key.algo = PBKDF2WithHmacSHA512 +# Key size, bits. +custom.key.size = 256 +# Number of key generation iterations. +custom.key.iter = 50000 +# Salt used in key generation (hex value, white space OK). +custom.key.salt = e2 55 89 67 8e 8d e8 4c + +# Same file can store multiple profiles. +# Only one profile can be active at a time. +# Active profile is selected via "crypto.algorithm" + +# +# Here is how to create V1 encryption in V2 format: +# +# Cipher algorithm. +legacy.algo = PBEWithHmacSHA1AndAES_128 +# Key factory algorithm. +legacy.key.algo = PBEWithHmacSHA1AndAES_128 +# Key size, bits. +legacy.key.size = 32 +# Number of key generation iterations. +legacy.key.iter = 5000 +# Salt used in key generation (hex value, white space OK). +legacy.key.salt = A40BC834D695F313 diff --git a/org.eclipse.jgit.test/tst-rsrc/log4j.properties b/org.eclipse.jgit.test/tst-rsrc/log4j.properties new file mode 100644 index 0000000000..14620ffae4 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/log4j.properties @@ -0,0 +1,9 @@ + +# Root logger option +log4j.rootLogger=INFO, stdout + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java index 19f074ea55..ccf1a51f1b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java @@ -47,6 +47,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.Properties; @@ -55,7 +56,10 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.hooks.PrePushHook; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; @@ -66,6 +70,7 @@ import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.TrackingRefUpdate; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.FS; import org.junit.Test; public class PushCommandTest extends RepositoryTestCase { @@ -108,6 +113,48 @@ public class PushCommandTest extends RepositoryTestCase { } @Test + public void testPrePushHook() throws JGitInternalException, IOException, + GitAPIException, URISyntaxException { + + // create other repository + Repository db2 = createWorkRepository(); + + // setup the first repository + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish(db2.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.update(config); + config.save(); + + File hookOutput = new File(getTemporaryDirectory(), "hookOutput"); + writeHookFile(PrePushHook.NAME, "#!/bin/sh\necho 1:$1, 2:$2, 3:$3 >\"" + + hookOutput.toPath() + "\"\ncat - >>\"" + hookOutput.toPath() + + "\"\nexit 0"); + + Git git1 = new Git(db); + // create some refs via commits and tag + RevCommit commit = git1.commit().setMessage("initial commit").call(); + + RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); + git1.push().setRemote("test").setRefSpecs(spec).call(); + assertEquals( + "1:test, 2:file://" + db2.getDirectory().toPath() // + + "/, 3:\n" + "refs/heads/master " + commit.getName() + + " refs/heads/x " + ObjectId.zeroId().name(), + read(hookOutput)); + } + + private File writeHookFile(final String name, final String data) + throws IOException { + File path = new File(db.getWorkTree() + "/.git/hooks/", name); + JGitTestUtil.write(path, data); + FS.DETECTED.setExecute(path, true); + return path; + } + + + @Test public void testTrackingUpdate() throws Exception { Repository db2 = createBareRepository(); 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 66e7256432..b6649b3f05 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 @@ -56,6 +56,8 @@ import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; import org.junit.Test; public class RepoCommandTest extends RepositoryTestCase { @@ -692,6 +694,54 @@ public class RepoCommandTest extends RepositoryTestCase { } } + @Test + public void testRecordRemoteBranch() throws Exception { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { + StringBuilder xmlContent = new StringBuilder(); + xmlContent + .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") + .append("<manifest>") + .append("<remote name=\"remote1\" fetch=\".\" />") + .append("<default revision=\"master\" remote=\"remote1\" />") + .append("<project path=\"with-branch\" ") + .append("revision=\"master\" ") + .append("name=\"").append(notDefaultUri).append("\" />") + .append("<project path=\"with-long-branch\" ") + .append("revision=\"refs/heads/master\" ") + .append("name=\"").append(defaultUri).append("\" />") + .append("</manifest>"); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + + RepoCommand command = new RepoCommand(remoteDb); + command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .setRecordRemoteBranch(true) + .call(); + // Clone it + File directory = createTempDirectory("testBareRepo"); + try (Repository localDb = Git.cloneRepository() + .setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository();) { + // The .gitmodules file should exist + File gitmodules = new File(localDb.getWorkTree(), + ".gitmodules"); + assertTrue("The .gitmodules file should exist", + gitmodules.exists()); + FileBasedConfig c = new FileBasedConfig(gitmodules, + FS.DETECTED); + c.load(); + assertEquals("standard branches work", "master", + c.getString("submodule", "with-branch", "branch")); + assertEquals("long branches work", "refs/heads/master", + c.getString("submodule", "with-long-branch", "branch")); + } + } + } + private void resolveRelativeUris() { // Find the longest common prefix ends with "/" as rootUri. defaultUri = defaultDb.getDirectory().toURI().toString(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java index 2c04787e3d..480e326507 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java @@ -54,6 +54,7 @@ public class FastIgnoreRuleTest { @Test public void testSimpleCharClass() { + assertMatched("][a]", "]a"); assertMatched("[a]", "a"); assertMatched("][a]", "]a"); assertMatched("[a]", "a/"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java index 9722ac6750..5893d8c407 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java @@ -63,6 +63,7 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.SystemReader; import org.junit.Test; /** @@ -468,6 +469,9 @@ public class IgnoreNodeTest extends RepositoryTestCase { @Test public void testTrailingSpaces() throws IOException { + // Windows can't create files with trailing spaces + // If this assumption fails the test is halted and ignored. + org.junit.Assume.assumeFalse(SystemReader.getInstance().isWindows()); writeTrashFile("a /a", ""); writeTrashFile("a /a ", ""); writeTrashFile("a /a ", ""); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java index f8eb126826..567f3d866c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java @@ -768,7 +768,7 @@ public class IgnoreRuleSpecialCasesTest { @Test public void testSpecialGroupCase9() throws Exception { - assertMatch("][", "][", true); + assertMatch("][", "][", false); } @Test @@ -968,6 +968,59 @@ public class IgnoreRuleSpecialCasesTest { assertMatch("[a{}()b][a{}()b]?[a{}()b][a{}()b]", "{}x()", true); assertMatch("x*{x}3", "xa{x}3", true); assertMatch("a*{x}3", "axxx", false); + + assertMatch("?", "[", true); + assertMatch("*", "[", true); + + // Escaped bracket matches, but see weird things below... + assertMatch("\\[", "[", true); + } + + /** + * The ignore rules here <b>do not match</b> any paths because single '[' + * begins character group and the entire rule cannot be parsed due the + * invalid glob pattern. See + * http://article.gmane.org/gmane.comp.version-control.git/278699. + * + * @throws Exception + */ + @Test + public void testBracketsUnmatched1() throws Exception { + assertMatch("[", "[", false); + assertMatch("[*", "[", false); + assertMatch("*[", "[", false); + assertMatch("*[", "a[", false); + assertMatch("[a][", "a[", false); + assertMatch("*[", "a", false); + assertMatch("[a", "a", false); + assertMatch("[*", "a", false); + assertMatch("[*a", "a", false); + } + + /** + * Single ']' is treated here literally, not as an and of a character group + * + * @throws Exception + */ + @Test + public void testBracketsUnmatched2() throws Exception { + assertMatch("*]", "a", false); + assertMatch("]a", "a", false); + assertMatch("]*", "a", false); + assertMatch("]*a", "a", false); + + assertMatch("]", "]", true); + assertMatch("]*", "]", true); + assertMatch("]*", "]a", true); + assertMatch("*]", "]", true); + assertMatch("*]", "a]", true); + } + + @Test + public void testBracketsRandom() throws Exception { + assertMatch("[\\]", "[$0+//r4a\\d]", false); + assertMatch("[:]]sZX]", "[:]]sZX]", false); + assertMatch("[:]]:]]]", "[:]]:]]]", false); } @Test diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java index fc8cbaa437..11a092468c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java @@ -51,9 +51,12 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.zip.Deflater; +import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.TestRng; @@ -161,6 +164,56 @@ public class DfsInserterTest { assertEquals(id2, objs.iterator().next()); } + @Test + public void testGarbageSelectivelyVisible() throws IOException { + ObjectInserter ins = db.newObjectInserter(); + ObjectId fooId = ins.insert(Constants.OBJ_BLOB, Constants.encode("foo")); + ins.flush(); + assertEquals(1, db.getObjectDatabase().listPacks().size()); + + // Make pack 0 garbage. + db.getObjectDatabase().listPacks().get(0).setPackSource(PackSource.UNREACHABLE_GARBAGE); + + // Default behavior should be that the database has foo, because we allow garbage objects. + assertTrue(db.getObjectDatabase().has(fooId)); + // But we should not be able to see it if we pass the right args. + assertFalse(db.getObjectDatabase().has(fooId, true)); + } + + @Test + public void testInserterIgnoresUnreachable() throws IOException { + ObjectInserter ins = db.newObjectInserter(); + ObjectId fooId = ins.insert(Constants.OBJ_BLOB, Constants.encode("foo")); + ins.flush(); + assertEquals(1, db.getObjectDatabase().listPacks().size()); + + // Make pack 0 garbage. + db.getObjectDatabase().listPacks().get(0).setPackSource(PackSource.UNREACHABLE_GARBAGE); + + // We shouldn't be able to see foo because it's garbage. + assertFalse(db.getObjectDatabase().has(fooId, true)); + + // But if we re-insert foo, it should become visible again. + ins.insert(Constants.OBJ_BLOB, Constants.encode("foo")); + ins.flush(); + assertTrue(db.getObjectDatabase().has(fooId, true)); + + // Verify that we have a foo in both packs, and 1 of them is garbage. + DfsReader reader = new DfsReader(db.getObjectDatabase()); + DfsPackFile packs[] = db.getObjectDatabase().getPacks(); + Set<PackSource> pack_sources = new HashSet<PackSource>(); + + assertEquals(2, packs.length); + + pack_sources.add(packs[0].getPackDescription().getPackSource()); + pack_sources.add(packs[1].getPackDescription().getPackSource()); + + assertTrue(packs[0].hasObject(reader, fooId)); + assertTrue(packs[1].hasObject(reader, fooId)); + assertTrue(pack_sources.contains(PackSource.UNREACHABLE_GARBAGE)); + assertTrue(pack_sources.contains(PackSource.INSERT)); + } + private static String readString(ObjectLoader loader) throws IOException { return RawParseUtils.decode(readStream(loader)); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java index bbd41237e2..f549fb5cdf 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java @@ -85,6 +85,7 @@ public class GcBasicPackingTest extends GcTestCase { assertEquals(4, stats.numberOfLooseObjects); assertEquals(0, stats.numberOfPackedObjects); assertEquals(0, stats.numberOfPackFiles); + assertEquals(0, stats.numberOfBitmaps); } @Theory @@ -102,6 +103,7 @@ public class GcBasicPackingTest extends GcTestCase { assertEquals(0, stats.numberOfLooseObjects); assertEquals(8, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); + assertEquals(2, stats.numberOfBitmaps); } @Theory @@ -118,6 +120,7 @@ public class GcBasicPackingTest extends GcTestCase { assertEquals(0, stats.numberOfLooseObjects); assertEquals(4, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); + assertEquals(1, stats.numberOfBitmaps); // Do the gc again and check that it hasn't changed anything gc.gc(); @@ -125,10 +128,12 @@ public class GcBasicPackingTest extends GcTestCase { assertEquals(0, stats.numberOfLooseObjects); assertEquals(4, stats.numberOfPackedObjects); assertEquals(1, stats.numberOfPackFiles); + assertEquals(1, stats.numberOfBitmaps); } @Theory - public void testPackCommitsAndLooseOne(boolean aggressive) throws Exception { + public void testPackCommitsAndLooseOne(boolean aggressive) + throws Exception { BranchBuilder bb = tr.branch("refs/heads/master"); RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); bb.commit().add("A", "A2").add("B", "B2").create(); @@ -143,6 +148,7 @@ public class GcBasicPackingTest extends GcTestCase { assertEquals(0, stats.numberOfLooseObjects); assertEquals(8, stats.numberOfPackedObjects); assertEquals(2, stats.numberOfPackFiles); + assertEquals(1, stats.numberOfBitmaps); } @Theory diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java index 2a096fd1c4..3c781a9474 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java @@ -75,6 +75,7 @@ public class GcReflogTest extends GcTestCase { tr.blob("x"); stats = gc.getStatistics(); assertEquals(9, stats.numberOfLooseObjects); + fsTick(); gc.prune(Collections.<ObjectId> emptySet()); stats = gc.getStatistics(); assertEquals(8, stats.numberOfLooseObjects); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java index a764f0fddd..5abf625489 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java @@ -52,6 +52,7 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.CommitBuilder; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.junit.After; import org.junit.Before; @@ -65,7 +66,8 @@ public abstract class GcTestCase extends LocalDiskRepositoryTestCase { public void setUp() throws Exception { super.setUp(); repo = createWorkRepository(); - tr = new TestRepository<FileRepository>((repo)); + tr = new TestRepository<FileRepository>(repo, new RevWalk(repo), + mockSystemReader); gc = new GC(repo); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java new file mode 100644 index 0000000000..5fda070867 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.pack; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.internal.storage.file.GcTestCase; +import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; +import org.eclipse.jgit.internal.storage.pack.ObjectToPack; +import org.eclipse.jgit.internal.storage.pack.PackWriterBitmapPreparer; +import org.eclipse.jgit.internal.storage.pack.PackWriterBitmapPreparer.BitmapCommit; +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.eclipse.jgit.junit.TestRepository.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.junit.Test; + +public class GcCommitSelectionTest extends GcTestCase { + + @Test + public void testBitmapSpansNoMerges() throws Exception { + /* + * Commit counts -> expected bitmap counts for history without merges. + * The top 100 contiguous commits should always have bitmaps, and the + * "recent" bitmaps beyond that are spaced out every 100-200 commits. + * (Starting at 100, the next 100 commits are searched for a merge + * commit. Since one is not found, the spacing between commits is 200. + */ + int[][] bitmapCounts = { // + { 1, 1 }, { 50, 50 }, { 99, 99 }, { 100, 100 }, { 101, 100 }, + { 200, 100 }, { 201, 100 }, { 299, 100 }, { 300, 101 }, + { 301, 101 }, { 401, 101 }, { 499, 101 }, { 500, 102 }, }; + int currentCommits = 0; + BranchBuilder bb = tr.branch("refs/heads/main"); + + for (int[] counts : bitmapCounts) { + int nextCommitCount = counts[0]; + int expectedBitmapCount = counts[1]; + assertTrue(nextCommitCount > currentCommits); // programming error + for (int i = currentCommits; i < nextCommitCount; i++) { + String str = "A" + i; + bb.commit().message(str).add(str, str).create(); + } + currentCommits = nextCommitCount; + + gc.setExpireAgeMillis(0); // immediately delete old packs + gc.gc(); + assertEquals(currentCommits * 3, // commit/tree/object + gc.getStatistics().numberOfPackedObjects); + assertEquals(currentCommits + " commits: ", expectedBitmapCount, + gc.getStatistics().numberOfBitmaps); + } + } + + @Test + public void testBitmapSpansWithMerges() throws Exception { + /* + * Commits that are merged. Since 55 is in the oldest history it is + * never considered. Searching goes from oldest to newest so 115 is the + * first merge commit found. After that the range 116-216 is ignored so + * 175 is never considered. + */ + List<Integer> merges = Arrays.asList(Integer.valueOf(55), + Integer.valueOf(115), Integer.valueOf(175), + Integer.valueOf(235)); + /* + * Commit counts -> expected bitmap counts for history with merges. The + * top 100 contiguous commits should always have bitmaps, and the + * "recent" bitmaps beyond that are spaced out every 100-200 commits. + * Merges in the < 100 range have no effect and merges in the > 100 + * range will only be considered for commit counts > 200. + */ + int[][] bitmapCounts = { // + { 1, 1 }, { 55, 55 }, { 56, 57 }, // +1 bitmap from branch A55 + { 99, 100 }, // still +1 branch @55 + { 100, 100 }, // 101 commits, only 100 newest + { 116, 100 }, // @55 still in 100 newest bitmaps + { 176, 101 }, // @55 branch tip is not in 100 newest + { 213, 101 }, // 216 commits, @115&@175 in 100 newest + { 214, 102 }, // @55 branch tip, merge @115, @177 in newest + { 236, 102 }, // all 4 merge points in history + { 273, 102 }, // 277 commits, @175&@235 in newest + { 274, 103 }, // @55, @115, merge @175, @235 in newest + { 334, 103 }, // @55,@115,@175, @235 in newest + { 335, 104 }, // @55,@115,@175, merge @235 + { 435, 104 }, // @55,@115,@175,@235 tips + { 436, 104 }, // force @236 + }; + + int currentCommits = 0; + BranchBuilder bb = tr.branch("refs/heads/main"); + + for (int[] counts : bitmapCounts) { + int nextCommitCount = counts[0]; + int expectedBitmapCount = counts[1]; + assertTrue(nextCommitCount > currentCommits); // programming error + for (int i = currentCommits; i < nextCommitCount; i++) { + String str = "A" + i; + if (!merges.contains(Integer.valueOf(i))) { + bb.commit().message(str).add(str, str).create(); + } else { + BranchBuilder bbN = tr.branch("refs/heads/A" + i); + bb.commit().message(str).add(str, str) + .parent(bbN.commit().create()).create(); + } + } + currentCommits = nextCommitCount; + + gc.setExpireAgeMillis(0); // immediately delete old packs + gc.gc(); + assertEquals(currentCommits + " commits: ", expectedBitmapCount, + gc.getStatistics().numberOfBitmaps); + } + } + + @Test + public void testBitmapsForExcessiveBranches() throws Exception { + int oneDayInSeconds = 60 * 60 * 24; + + // All of branch A is committed on day1 + BranchBuilder bbA = tr.branch("refs/heads/A"); + for (int i = 0; i < 1001; i++) { + String msg = "A" + i; + bbA.commit().message(msg).add(msg, msg).create(); + } + // All of in branch B is committed on day91 + tr.tick(oneDayInSeconds * 90); + BranchBuilder bbB = tr.branch("refs/heads/B"); + for (int i = 0; i < 1001; i++) { + String msg = "B" + i; + bbB.commit().message(msg).add(msg, msg).create(); + } + // Create 100 other branches with a single commit + for (int i = 0; i < 100; i++) { + BranchBuilder bb = tr.branch("refs/heads/N" + i); + String msg = "singlecommit" + i; + bb.commit().message(msg).add(msg, msg).create(); + } + // now is day92 + tr.tick(oneDayInSeconds); + + // Since there are no merges, commits in recent history are selected + // every 200 commits. + final int commitsForSparseBranch = 1 + (1001 / 200); + final int commitsForFullBranch = 100 + (901 / 200); + final int commitsForShallowBranches = 100; + + // Excessive branch history pruning, one old branch. + gc.setExpireAgeMillis(0); // immediately delete old packs + gc.gc(); + assertEquals( + commitsForSparseBranch + commitsForFullBranch + + commitsForShallowBranches, + gc.getStatistics().numberOfBitmaps); + } + + @Test + public void testSelectionOrderingWithChains() throws Exception { + /*- + * Create a history like this, where 'N' is the number of seconds from + * the first commit in the branch: + * + * ---o---o---o commits b3,b5,b7 + * / \ + * o--o--o---o---o---o--o commits m0,m1,m2,m4,m6,m8,m9 + */ + BranchBuilder bb = tr.branch("refs/heads/main"); + RevCommit m0 = addCommit(bb, "m0"); + RevCommit m1 = addCommit(bb, "m1", m0); + RevCommit m2 = addCommit(bb, "m2", m1); + RevCommit b3 = addCommit(bb, "b3", m1); + RevCommit m4 = addCommit(bb, "m4", m2); + RevCommit b5 = addCommit(bb, "m5", b3); + RevCommit m6 = addCommit(bb, "m6", m4); + RevCommit b7 = addCommit(bb, "m7", b5); + RevCommit m8 = addCommit(bb, "m8", m6, b7); + RevCommit m9 = addCommit(bb, "m9", m8); + + List<RevCommit> commits = Arrays.asList(m0, m1, m2, b3, m4, b5, m6, b7, + m8, m9); + PackWriterBitmapPreparer preparer = newPeparer(m9, commits); + List<BitmapCommit> selection = new ArrayList<>( + preparer.selectCommits(commits.size())); + + // Verify that the output is ordered by the separate "chains" + String[] expected = { m0.name(), m1.name(), m2.name(), m4.name(), + m6.name(), m8.name(), m9.name(), b3.name(), b5.name(), + b7.name() }; + assertEquals(expected.length, selection.size()); + for (int i = 0; i < expected.length; i++) { + assertEquals("Entry " + i, expected[i], selection.get(i).getName()); + } + } + + private RevCommit addCommit(BranchBuilder bb, String msg, + RevCommit... parents) throws Exception { + CommitBuilder commit = bb.commit().message(msg).add(msg, msg).tick(1) + .noParents(); + for (RevCommit parent : parents) { + commit.parent(parent); + } + return commit.create(); + } + + private PackWriterBitmapPreparer newPeparer(RevCommit want, + List<RevCommit> commits) + throws IOException { + List<ObjectToPack> objects = new ArrayList<>(commits.size()); + for (RevCommit commit : commits) { + objects.add(new ObjectToPack(commit, Constants.OBJ_COMMIT)); + } + Set<ObjectId> wants = Collections.singleton((ObjectId) want); + PackConfig config = new PackConfig(); + PackBitmapIndexBuilder builder = new PackBitmapIndexBuilder(objects); + return new PackWriterBitmapPreparer( + tr.getRepository().newObjectReader(), builder, + NullProgressMonitor.INSTANCE, wants, config); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java new file mode 100644 index 0000000000..b0f92ffa0c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java @@ -0,0 +1,136 @@ +package org.eclipse.jgit.internal.storage.pack; + +import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_DISTANT_COMMIT_SPAN; +import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_RECENT_COMMIT_COUNT; +import static org.eclipse.jgit.storage.pack.PackConfig.DEFAULT_BITMAP_RECENT_COMMIT_SPAN; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.junit.Test; + +/** Tests for the {@link PackWriterBitmapPreparer}. */ +public class PackWriterBitmapPreparerTest { + private static class StubObjectReader extends ObjectReader { + @Override + public ObjectReader newReader() { + return null; + } + + @Override + public Collection<ObjectId> resolve(AbbreviatedObjectId id) + throws IOException { + return null; + } + + @Override + public ObjectLoader open(AnyObjectId objectId, int typeHint) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + return null; + } + + @Override + public Set<ObjectId> getShallowCommits() throws IOException { + return null; + } + + @Override + public void close() { + // stub + } + } + + @Test + public void testNextSelectionDistanceForActiveBranch() throws Exception { + PackWriterBitmapPreparer preparer = newPeparer( + DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000 + DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100 + DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000 + int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 10000, 100 }, + { 20000, 100 }, { 20100, 100 }, { 20102, 102 }, { 20200, 200 }, + { 22200, 2200 }, { 24999, 4999 }, { 25000, 5000 }, + { 50000, 5000 }, { 1000000, 5000 }, }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + @Test + public void testNextSelectionDistanceWithFewerRecentCommits() + throws Exception { + PackWriterBitmapPreparer preparer = newPeparer(1000, + DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100 + DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000 + int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 1000, 100 }, + { 1100, 100 }, { 1111, 111 }, { 2000, 1000 }, { 5999, 4999 }, + { 6000, 5000 }, { 10000, 5000 }, { 50000, 5000 }, + { 1000000, 5000 } }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + @Test + public void testNextSelectionDistanceWithSmallerRecentSpan() + throws Exception { + PackWriterBitmapPreparer preparer = newPeparer( + DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000 + 10, // recent span + DEFAULT_BITMAP_DISTANT_COMMIT_SPAN); // 5000 + int[][] distancesAndSpans = { { 0, 10 }, { 100, 10 }, { 10000, 10 }, + { 20000, 10 }, { 20010, 10 }, { 20012, 12 }, { 20050, 50 }, + { 20200, 200 }, { 22200, 2200 }, { 24999, 4999 }, + { 25000, 5000 }, { 50000, 5000 }, { 1000000, 5000 } }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + @Test + public void testNextSelectionDistanceWithSmallerDistantSpan() + throws Exception { + PackWriterBitmapPreparer preparer = newPeparer( + DEFAULT_BITMAP_RECENT_COMMIT_COUNT, // 20000 + DEFAULT_BITMAP_RECENT_COMMIT_SPAN, // 100 + 1000); + int[][] distancesAndSpans = { { 0, 100 }, { 100, 100 }, { 10000, 100 }, + { 20000, 100 }, { 20100, 100 }, { 20102, 102 }, { 20200, 200 }, + { 20999, 999 }, { 21000, 1000 }, { 22000, 1000 }, + { 25000, 1000 }, { 50000, 1000 }, { 1000000, 1000 } }; + + for (int[] pair : distancesAndSpans) { + assertEquals(pair[1], preparer.nextSpan(pair[0])); + } + } + + private PackWriterBitmapPreparer newPeparer(int recentCount, int recentSpan, + int distantSpan) throws IOException { + List<ObjectToPack> objects = Collections.emptyList(); + Set<ObjectId> wants = Collections.emptySet(); + PackConfig config = new PackConfig(); + config.setBitmapRecentCommitCount(recentCount); + config.setBitmapRecentCommitSpan(recentSpan); + config.setBitmapDistantCommitSpan(distantSpan); + PackBitmapIndexBuilder indexBuilder = new PackBitmapIndexBuilder( + objects); + return new PackWriterBitmapPreparer(new StubObjectReader(), + indexBuilder, null, wants, config); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java index fbb9eecdff..b69b8e01e0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java @@ -307,9 +307,9 @@ public class TestRepositoryTest { RevCommit toPick = tr.commit() .parent(tr.commit().create()) // Can't cherry-pick root. .author(new PersonIdent("Cherrypick Author", "cpa@example.com", - tr.getClock(), tr.getTimeZone())) + tr.getDate(), tr.getTimeZone())) .author(new PersonIdent("Cherrypick Committer", "cpc@example.com", - tr.getClock(), tr.getTimeZone())) + tr.getDate(), tr.getTimeZone())) .message("message to cherry-pick") .add("bar", "bar contents\n") .create(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index 7a115e1076..6d62528f85 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -78,6 +78,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; import org.junit.Test; public class DirCacheCheckoutTest extends RepositoryTestCase { @@ -923,6 +924,299 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { } @Test + public void testCheckoutChangeLinkToEmptyDir() throws Exception { + String fname = "was_file"; + Git git = Git.wrap(db); + + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with empty directory + FileUtils.delete(link); + FileUtils.mkdir(link); + assertTrue("Link must be a directory now", link.isDirectory()); + + // modify file + writeTrashFile(fname, "b"); + assertWorkDir(mkmap(fname, "b", linkName, "/")); + + // revert both paths to HEAD state + git.checkout().setStartPoint(Constants.HEAD) + .addPath(fname).addPath(linkName).call(); + + assertWorkDir(mkmap(fname, "a", linkName, "a")); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } + + @Test + public void testCheckoutChangeLinkToEmptyDirs() throws Exception { + String fname = "was_file"; + Git git = Git.wrap(db); + + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with directory containing only directories, no files + FileUtils.delete(link); + FileUtils.mkdirs(new File(link, "dummyDir")); + assertTrue("Link must be a directory now", link.isDirectory()); + + assertFalse("Must not delete non empty directory", link.delete()); + + // modify file + writeTrashFile(fname, "b"); + assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/")); + + // revert both paths to HEAD state + git.checkout().setStartPoint(Constants.HEAD) + .addPath(fname).addPath(linkName).call(); + + assertWorkDir(mkmap(fname, "a", linkName, "a")); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } + + @Test + public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception { + String fname = "file"; + Git git = Git.wrap(db); + + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with directory containing only directories, no files + FileUtils.delete(link); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir1", "file1", "c"); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", link.isDirectory()); + assertFalse("Must not delete non empty directory", link.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call(); + + // expect only the one added to the index + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } + + @Test + public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry() + throws Exception { + String fname = "file"; + Git git = Git.wrap(db); + + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // replace link with directory containing only directories, no files + FileUtils.delete(link); + + // create and add a file in the new directory to the index + writeTrashFile(linkName + "/dir1", "file1", "c"); + git.add().addFilepattern(linkName + "/dir1/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", link.isDirectory()); + assertFalse("Must not delete non empty directory", link.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call(); + + // original file and link + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + Status st = git.status().call(); + assertFalse(st.isClean()); + } + + @Test + public void testCheckoutChangeFileToEmptyDir() throws Exception { + String fname = "was_file"; + Git git = Git.wrap(db); + + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + // replace file with empty directory + FileUtils.delete(file); + FileUtils.mkdir(file); + assertTrue("File must be a directory now", file.isDirectory()); + + assertWorkDir(mkmap(fname, "/")); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + + assertWorkDir(mkmap(fname, "a")); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } + + @Test + public void testCheckoutChangeFileToEmptyDirs() throws Exception { + String fname = "was_file"; + Git git = Git.wrap(db); + + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + // replace file with directory containing only directories, no files + FileUtils.delete(file); + FileUtils.mkdirs(new File(file, "dummyDir")); + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + assertWorkDir(mkmap(fname + "/dummyDir", "/")); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + + assertWorkDir(mkmap(fname, "a")); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } + + @Test + public void testCheckoutChangeFileToNonEmptyDirs() throws Exception { + String fname = "was_file"; + Git git = Git.wrap(db); + + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + assertWorkDir(mkmap(fname, "a")); + + // replace file with directory containing only directories, no files + FileUtils.delete(file); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir1", "file1", "c"); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + // 2 extra files are created + assertWorkDir( + mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d")); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + + // expect only the one added to the index + assertWorkDir(mkmap(fname, "a")); + + Status st = git.status().call(); + assertTrue(st.isClean()); + } + + @Test + public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry() + throws Exception { + String fname = "was_file"; + Git git = Git.wrap(db); + + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("Added file").call(); + + assertWorkDir(mkmap(fname, "a")); + + // replace file with directory containing only directories, no files + FileUtils.delete(file); + + // create and add a file in the new directory to the index + writeTrashFile(fname + "/dir", "file1", "c"); + git.add().addFilepattern(fname + "/dir/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir", "file2", "d"); + + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + // 2 extra files are created + assertWorkDir( + mkmap(fname + "/dir/file1", "c", fname + "/dir/file2", "d")); + + // revert path to HEAD state + git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); + assertWorkDir(mkmap(fname, "a")); + + Status st = git.status().call(); + assertFalse(st.isClean()); + assertEquals(1, st.getAdded().size()); + assertTrue(st.getAdded().contains(fname + "/dir/file1")); + } + + @Test public void testCheckoutOutChangesAutoCRLFfalse() throws IOException { setupCase(mk("foo"), mkmap("foo/bar", "foo\nbar"), mk("foo")); checkout(); @@ -983,6 +1277,14 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { } @Test + public void testDontOverwriteEmptyFolder() throws IOException { + setupCase(mk("foo"), mk("foo"), mk("foo")); + FileUtils.mkdir(new File(db.getWorkTree(), "d")); + checkout(); + assertWorkDir(mkmap("foo", "foo", "d", "/")); + } + + @Test public void testOverwriteUntrackedIgnoredFile() throws IOException, GitAPIException { String fname="file.txt"; @@ -1015,6 +1317,102 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { } @Test + public void testOverwriteUntrackedFileModeChange() + throws IOException, GitAPIException { + String fname = "file.txt"; + Git git = Git.wrap(db); + + // Add a file + File file = writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + git.commit().setMessage("create file").call(); + assertWorkDir(mkmap(fname, "a")); + + // Create branch + git.branchCreate().setName("side").call(); + + // Switch branches + git.checkout().setName("side").call(); + + // replace file with directory containing files + FileUtils.delete(file); + + // create and add a file in the new directory to the index + writeTrashFile(fname + "/dir1", "file1", "c"); + git.add().addFilepattern(fname + "/dir1/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(fname + "/dir2", "file2", "d"); + + assertTrue("File must be a directory now", file.isDirectory()); + assertFalse("Must not delete non empty directory", file.delete()); + + // 2 extra files are created + assertWorkDir( + mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d")); + + try { + git.checkout().setName("master").call(); + fail("did not throw exception"); + } catch (Exception e) { + // 2 extra files are still there + assertWorkDir(mkmap(fname + "/dir1/file1", "c", + fname + "/dir2/file2", "d")); + } + } + + @Test + public void testOverwriteUntrackedLinkModeChange() + throws Exception { + String fname = "file.txt"; + Git git = Git.wrap(db); + + // Add a file + writeTrashFile(fname, "a"); + git.add().addFilepattern(fname).call(); + + // Add a link to file + String linkName = "link"; + File link = writeLink(linkName, fname).toFile(); + git.add().addFilepattern(linkName).call(); + git.commit().setMessage("Added file and link").call(); + + assertWorkDir(mkmap(linkName, "a", fname, "a")); + + // Create branch + git.branchCreate().setName("side").call(); + + // Switch branches + git.checkout().setName("side").call(); + + // replace link with directory containing files + FileUtils.delete(link); + + // create and add a file in the new directory to the index + writeTrashFile(linkName + "/dir1", "file1", "c"); + git.add().addFilepattern(linkName + "/dir1/file1").call(); + + // create but do not add a file in the new directory to the index + writeTrashFile(linkName + "/dir2", "file2", "d"); + + assertTrue("Link must be a directory now", link.isDirectory()); + assertFalse("Must not delete non empty directory", link.delete()); + + // 2 extra files are created + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + + try { + git.checkout().setName("master").call(); + fail("did not throw exception"); + } catch (Exception e) { + // 2 extra files are still there + assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", + linkName + "/dir2/file2", "d")); + } + } + + @Test public void testFileModeChangeWithNoContentChangeUpdate() throws Exception { if (!FS.DETECTED.supportsExecute()) return; @@ -1210,10 +1608,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { assertNotNull(git.checkout().setName(Constants.MASTER).call()); } - public void assertWorkDir(HashMap<String, String> i) throws CorruptObjectException, + public void assertWorkDir(Map<String, String> i) + throws CorruptObjectException, IOException { TreeWalk walk = new TreeWalk(db); - walk.setRecursive(true); + walk.setRecursive(false); walk.addTree(new FileTreeIterator(db)); String expectedValue; String path; @@ -1223,11 +1622,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { ft = walk.getTree(0, FileTreeIterator.class); path = ft.getEntryPathString(); expectedValue = i.get(path); - assertNotNull("found unexpected file for path " + path - + " in workdir", expectedValue); File file = new File(db.getWorkTree(), path); assertTrue(file.exists()); if (file.isFile()) { + assertNotNull("found unexpected file for path " + path + + " in workdir", expectedValue); FileInputStream is = new FileInputStream(file); byte[] buffer = new byte[(int) file.length()]; int offset = 0; @@ -1241,6 +1640,15 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { assertArrayEquals("unexpected content for path " + path + " in workDir. ", buffer, i.get(path).getBytes()); nrFiles++; + } else if (file.isDirectory()) { + if (file.list().length == 0) { + assertEquals("found unexpected empty folder for path " + + path + " in workDir. ", "/", i.get(path)); + nrFiles++; + } + } + if (walk.isSubtree()) { + walk.enterSubtree(); } } assertEquals("WorkDir has not the right size.", i.size(), nrFiles); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java index 643ba26d9a..8ab972837c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java @@ -250,14 +250,14 @@ public class RevWalkFilterTest extends RevWalkTestCase { final RevCommit b = commit(a); tick(100); - Date since = getClock(); + Date since = getDate(); final RevCommit c1 = commit(b); tick(100); final RevCommit c2 = commit(b); tick(100); - Date until = getClock(); + Date until = getDate(); final RevCommit d = commit(c1, c2); tick(100); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java index 881deb1f5a..30586ee1e2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java @@ -70,8 +70,8 @@ public abstract class RevWalkTestCase extends RepositoryTestCase { return new RevWalk(db); } - protected Date getClock() { - return util.getClock(); + protected Date getDate() { + return util.getDate(); } protected void tick(final int secDelta) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java new file mode 100644 index 0000000000..90d78e4b62 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java @@ -0,0 +1,1295 @@ +/* + * Copyright (C) 2015, Andrei Pozolotin. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.Security; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; +import org.eclipse.jgit.util.FileUtils; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.Suite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.*; + +/** + * Amazon S3 encryption pipeline test. + * + * See {@link AmazonS3} {@link WalkEncryption} + * + * Note: CI server must provide amazon credentials (access key, secret key, + * bucket name) via one of methods available in {@link Names}. + * + * Note: long running tests are activated by Maven profile "test.long". There is + * also a separate Eclipse m2e launcher for that. See 'pom.xml' and + * 'WalkEncryptionTest.launch'. + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ // + WalkEncryptionTest.Required.class, // + WalkEncryptionTest.MinimalSet.class, // + WalkEncryptionTest.TestablePBE.class, // + WalkEncryptionTest.TestableTransformation.class, // +}) +public class WalkEncryptionTest { + + /** + * Logger setup: ${project_loc}/tst-rsrc/log4j.properties + */ + static final Logger logger = LoggerFactory.getLogger(WalkEncryptionTest.class); + + /** + * Property names used in test session. + */ + interface Names { + + // Names of discovered test properties. + + String TEST_BUCKET = "test.bucket"; + + // Names of test environment variables for CI. + + String ENV_ACCESS_KEY = "JGIT_S3_ACCESS_KEY"; + + String ENV_SECRET_KEY = "JGIT_S3_SECRET_KEY"; + + String ENV_BUCKET_NAME = "JGIT_S3_BUCKET_NAME"; + + // Name of test environment variable file path for CI. + + String ENV_CONFIG_FILE = "JGIT_S3_CONFIG_FILE"; + + // Names of test system properties for CI. + + String SYS_ACCESS_KEY = "jgit.s3.access.key"; + + String SYS_SECRET_KEY = "jgit.s3.secret.key"; + + String SYS_BUCKET_NAME = "jgit.s3.bucket.name"; + + // Name of test system property file path for CI. + String SYS_CONFIG_FILE = "jgit.s3.config.file"; + + // Hard coded name of test properties file for CI. + // File format follows AmazonS3.Keys: + // # + // # Required entries: + // # + // accesskey = your-amazon-access-key # default AmazonS3.Keys + // secretkey = your-amazon-secret-key # default AmazonS3.Keys + // test.bucket = your-bucket-for-testing # custom name, for this test + String CONFIG_FILE = "jgit-s3-config.properties"; + + // Test properties file in [user home] of CI. + String HOME_CONFIG_FILE = System.getProperty("user.home") + + File.separator + CONFIG_FILE; + + // Test properties file in [project work directory] of CI. + String WORK_CONFIG_FILE = System.getProperty("user.dir") + + File.separator + CONFIG_FILE; + + // Test properties file in [project test source directory] of CI. + String TEST_CONFIG_FILE = System.getProperty("user.dir") + + File.separator + "tst-rsrc" + File.separator + CONFIG_FILE; + + } + + /** + * Find test properties from various sources in order of priority. + */ + static class Props implements WalkEncryptionTest.Names, AmazonS3.Keys { + + static boolean haveEnvVar(String name) { + return System.getenv(name) != null; + } + + static boolean haveEnvVarFile(String name) { + return haveEnvVar(name) && new File(name).exists(); + } + + static boolean haveSysProp(String name) { + return System.getProperty(name) != null; + } + + static boolean haveSysPropFile(String name) { + return haveSysProp(name) && new File(name).exists(); + } + + static void loadEnvVar(String source, String target, Properties props) { + props.put(target, System.getenv(source)); + } + + static void loadSysProp(String source, String target, + Properties props) { + props.put(target, System.getProperty(source)); + } + + static boolean haveProp(String name, Properties props) { + return props.containsKey(name); + } + + static boolean checkTestProps(Properties props) { + return haveProp(ACCESS_KEY, props) && haveProp(SECRET_KEY, props) + && haveProp(TEST_BUCKET, props); + } + + static Properties fromEnvVars() { + if (haveEnvVar(ENV_ACCESS_KEY) && haveEnvVar(ENV_SECRET_KEY) + && haveEnvVar(ENV_BUCKET_NAME)) { + Properties props = new Properties(); + loadEnvVar(ENV_ACCESS_KEY, ACCESS_KEY, props); + loadEnvVar(ENV_SECRET_KEY, SECRET_KEY, props); + loadEnvVar(ENV_BUCKET_NAME, TEST_BUCKET, props); + return props; + } else { + return null; + } + } + + static Properties fromEnvFile() throws Exception { + if (haveEnvVarFile(ENV_CONFIG_FILE)) { + Properties props = new Properties(); + props.load(new FileInputStream(ENV_CONFIG_FILE)); + if (checkTestProps(props)) { + return props; + } else { + throw new Error("Environment config file is incomplete."); + } + } else { + return null; + } + } + + static Properties fromSysProps() { + if (haveSysProp(SYS_ACCESS_KEY) && haveSysProp(SYS_SECRET_KEY) + && haveSysProp(SYS_BUCKET_NAME)) { + Properties props = new Properties(); + loadSysProp(SYS_ACCESS_KEY, ACCESS_KEY, props); + loadSysProp(SYS_SECRET_KEY, SECRET_KEY, props); + loadSysProp(SYS_BUCKET_NAME, TEST_BUCKET, props); + return props; + } else { + return null; + } + } + + static Properties fromSysFile() throws Exception { + if (haveSysPropFile(SYS_CONFIG_FILE)) { + Properties props = new Properties(); + props.load(new FileInputStream(SYS_CONFIG_FILE)); + if (checkTestProps(props)) { + return props; + } else { + throw new Error("System props config file is incomplete."); + } + } else { + return null; + } + } + + static Properties fromConfigFile(String path) throws Exception { + File file = new File(path); + if (file.exists()) { + Properties props = new Properties(); + props.load(new FileInputStream(file)); + if (checkTestProps(props)) { + return props; + } else { + throw new Error("Props config file is incomplete: " + path); + } + } else { + return null; + } + } + + /** + * Find test properties from various sources in order of priority. + * + * @return result + * @throws Exception + */ + static Properties discover() throws Exception { + Properties props; + if ((props = fromEnvVars()) != null) { + logger.debug( + "Using test properties from environment variables."); + return props; + } + if ((props = fromEnvFile()) != null) { + logger.debug( + "Using test properties from environment variable config file."); + return props; + } + if ((props = fromSysProps()) != null) { + logger.debug("Using test properties from system properties."); + return props; + } + if ((props = fromSysFile()) != null) { + logger.debug( + "Using test properties from system property config file."); + return props; + } + if ((props = fromConfigFile(HOME_CONFIG_FILE)) != null) { + logger.debug( + "Using test properties from hard coded ${user.home} file."); + return props; + } + if ((props = fromConfigFile(WORK_CONFIG_FILE)) != null) { + logger.debug( + "Using test properties from hard coded ${user.dir} file."); + return props; + } + if ((props = fromConfigFile(TEST_CONFIG_FILE)) != null) { + logger.debug( + "Using test properties from hard coded ${project.source} file."); + return props; + } + throw new Error("Can not load test properties form any source."); + } + + } + + /** + * Collection of test utility methods. + */ + static class Util { + + static final Charset UTF_8 = Charset.forName("UTF-8"); + + /** + * Read UTF-8 encoded text file into string. + * + * @param file + * @return result + * @throws Exception + */ + static String textRead(File file) throws Exception { + return new String(Files.readAllBytes(file.toPath()), UTF_8); + } + + /** + * Write string into UTF-8 encoded file. + * + * @param file + * @param text + * @throws Exception + */ + static void textWrite(File file, String text) throws Exception { + Files.write(file.toPath(), text.getBytes(UTF_8)); + } + + static void verifyFileContent(File fileOne, File fileTwo) + throws Exception { + assertTrue(fileOne.length() > 0); + assertTrue(fileTwo.length() > 0); + String textOne = textRead(fileOne); + String textTwo = textRead(fileTwo); + assertEquals(textOne, textTwo); + } + + /** + * Create local folder. + * + * @param folder + * @throws Exception + */ + static void folderCreate(String folder) throws Exception { + File path = new File(folder); + assertTrue(path.mkdirs()); + } + + /** + * Delete local folder. + * + * @param folder + * @throws Exception + */ + static void folderDelete(String folder) throws Exception { + File path = new File(folder); + FileUtils.delete(path, + FileUtils.RECURSIVE | FileUtils.SKIP_MISSING); + } + + /** + * Discover public address of CI server. + * + * @return result + * @throws Exception + */ + static String publicAddress() throws Exception { + String service = "http://checkip.amazonaws.com"; + URL url = new URL(service); + BufferedReader reader = new BufferedReader( + new InputStreamReader(url.openStream())); + try { + return reader.readLine(); + } finally { + reader.close(); + } + } + + /** + * Discover Password-Based Encryption (PBE) engines providing both + * [SecretKeyFactory] and [AlgorithmParameters]. + * + * @return result + */ + // https://www.bouncycastle.org/specifications.html + // https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html + static List<String> cryptoCipherListPBE() { + return cryptoCipherList(WalkEncryption.Vals.REGEX_PBE); + } + + // TODO returns inconsistent list. + static List<String> cryptoCipherListTrans() { + return cryptoCipherList(WalkEncryption.Vals.REGEX_TRANS); + } + + static String securityProviderName(String algorithm) throws Exception { + return SecretKeyFactory.getInstance(algorithm).getProvider() + .getName(); + } + + static List<String> cryptoCipherList(String regex) { + Set<String> source = Security.getAlgorithms("Cipher"); + Set<String> target = new TreeSet<String>(); + for (String algo : source) { + algo = algo.toUpperCase(); + if (algo.matches(regex)) { + target.add(algo); + } + } + return new ArrayList<String>(target); + } + + /** + * Stream copy. + * + * @param from + * @param into + * @return count + * @throws IOException + */ + static long transferStream(InputStream from, OutputStream into) + throws IOException { + byte[] array = new byte[1 * 1024]; + long total = 0; + while (true) { + int count = from.read(array); + if (count == -1) { + break; + } + into.write(array, 0, count); + total += count; + } + return total; + } + + /** + * Setup proxy during CI build. + * + * @throws Exception + */ + // https://wiki.eclipse.org/Hudson#Accessing_the_Internet_using_Proxy + // http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html + static void proxySetup() throws Exception { + String keyNoProxy = "no_proxy"; + String keyHttpProxy = "http_proxy"; + String keyHttpsProxy = "https_proxy"; + + String no_proxy = System.getProperty(keyNoProxy, + System.getenv(keyNoProxy)); + if (no_proxy != null) { + System.setProperty("http.nonProxyHosts", no_proxy); + logger.info("Proxy NOT: " + no_proxy); + } + + String http_proxy = System.getProperty(keyHttpProxy, + System.getenv(keyHttpProxy)); + if (http_proxy != null) { + URL url = new URL(http_proxy); + System.setProperty("http.proxyHost", url.getHost()); + System.setProperty("http.proxyPort", "" + url.getPort()); + logger.info("Proxy HTTP: " + http_proxy); + } + + String https_proxy = System.getProperty(keyHttpsProxy, + System.getenv(keyHttpsProxy)); + if (https_proxy != null) { + URL url = new URL(https_proxy); + System.setProperty("https.proxyHost", url.getHost()); + System.setProperty("https.proxyPort", "" + url.getPort()); + logger.info("Proxy HTTPS: " + https_proxy); + } + + if (no_proxy == null && http_proxy == null && https_proxy == null) { + logger.info("Proxy not used."); + } + + } + + /** + * Permit long tests on CI or with manual activation. + * + * @return result + */ + static boolean permitLongTests() { + return isBuildCI() || isProfileActive(); + } + + /** + * Using Maven profile activation, see pom.xml + * + * @return result + */ + static boolean isProfileActive() { + return Boolean.parseBoolean(System.getProperty("jgit.test.long")); + } + + /** + * Detect if build is running on CI. + * + * @return result + */ + static boolean isBuildCI() { + return System.getenv("HUDSON_HOME") != null; + } + + /** + * Setup JCE security policy restrictions. Can remove restrictions when + * restrictions are present, but can not impose them when restrictions + * are missing. + * + * @param restrictedOn + */ + // http://www.docjar.com/html/api/javax/crypto/JceSecurity.java.html + static void policySetup(boolean restrictedOn) { + try { + java.lang.reflect.Field isRestricted = Class + .forName("javax.crypto.JceSecurity") + .getDeclaredField("isRestricted"); + isRestricted.setAccessible(true); + isRestricted.set(null, new Boolean(restrictedOn)); + } catch (Throwable e) { + logger.info( + "Could not setup JCE security policy restrictions."); + } + } + + static void reportPolicy() { + try { + java.lang.reflect.Field isRestricted = Class + .forName("javax.crypto.JceSecurity") + .getDeclaredField("isRestricted"); + isRestricted.setAccessible(true); + logger.info("JCE security policy restricted=" + + isRestricted.get(null)); + } catch (Throwable e) { + logger.info( + "Could not report JCE security policy restrictions."); + } + } + + static List<Object[]> product(List<String> one, List<String> two) { + List<Object[]> result = new ArrayList<Object[]>(); + for (String s1 : one) { + for (String s2 : two) { + result.add(new Object[] { s1, s2 }); + } + } + return result; + } + + } + + /** + * Common base for encryption tests. + */ + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public abstract static class Base extends SampleDataRepositoryTestCase { + + /** + * S3 URI user used by JGIT to discover connection configuration file. + */ + static final String JGIT_USER = "tester-" + System.currentTimeMillis(); + + /** + * S3 content encoding password used for this test session. + */ + static final String JGIT_PASS = "secret-" + System.currentTimeMillis(); + + /** + * S3 repository configuration file expected by {@link AmazonS3}. + */ + static final String JGIT_CONF_FILE = System.getProperty("user.home") + + "/" + JGIT_USER; + + /** + * Name representing remote or local JGIT repository. + */ + static final String JGIT_REPO_DIR = JGIT_USER + ".jgit"; + + /** + * Local JGIT repository for this test session. + */ + static final String JGIT_LOCAL_DIR = System.getProperty("user.dir") + + "/target/" + JGIT_REPO_DIR; + + /** + * Remote JGIT repository for this test session. + */ + static final String JGIT_REMOTE_DIR = JGIT_REPO_DIR; + + /** + * Generate JGIT S3 connection configuration file. + * + * @param algorithm + * @throws Exception + */ + static void configCreate(String algorithm) throws Exception { + Properties props = Props.discover(); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + props.put(AmazonS3.Keys.CRYPTO_ALG, algorithm); + PrintWriter writer = new PrintWriter(JGIT_CONF_FILE); + props.store(writer, "JGIT S3 connection configuration file."); + writer.close(); + } + + /** + * Generate JGIT S3 connection configuration file. + * + * @param source + * @throws Exception + */ + static void configCreate(Properties source) throws Exception { + Properties target = Props.discover(); + target.putAll(source); + PrintWriter writer = new PrintWriter(JGIT_CONF_FILE); + target.store(writer, "JGIT S3 connection configuration file."); + writer.close(); + } + + /** + * Remove JGIT connection configuration file. + * + * @throws Exception + */ + static void configDelete() throws Exception { + File path = new File(JGIT_CONF_FILE); + FileUtils.delete(path, FileUtils.SKIP_MISSING); + } + + /** + * Generate remote URI for the test session. + * + * @return result + * @throws Exception + */ + static String amazonURI() throws Exception { + Properties props = Props.discover(); + String bucket = props.getProperty(Names.TEST_BUCKET); + assertNotNull(bucket); + return TransportAmazonS3.S3_SCHEME + "://" + JGIT_USER + "@" + + bucket + "/" + JGIT_REPO_DIR; + } + + /** + * Create S3 repository folder. + * + * @throws Exception + */ + static void remoteCreate() throws Exception { + Properties props = Props.discover(); + props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption. + String bucket = props.getProperty(Names.TEST_BUCKET); + AmazonS3 s3 = new AmazonS3(props); + String path = JGIT_REMOTE_DIR + "/"; + s3.put(bucket, path, new byte[0]); + logger.debug("remote create: " + JGIT_REMOTE_DIR); + } + + /** + * Delete S3 repository folder. + * + * @throws Exception + */ + static void remoteDelete() throws Exception { + Properties props = Props.discover(); + props.remove(AmazonS3.Keys.PASSWORD); // Disable encryption. + String bucket = props.getProperty(Names.TEST_BUCKET); + AmazonS3 s3 = new AmazonS3(props); + List<String> list = s3.list(bucket, JGIT_REMOTE_DIR); + for (String path : list) { + path = JGIT_REMOTE_DIR + "/" + path; + s3.delete(bucket, path); + } + logger.debug("remote delete: " + JGIT_REMOTE_DIR); + } + + /** + * Verify if we can create/delete remote file. + * + * @throws Exception + */ + static void remoteVerify() throws Exception { + Properties props = Props.discover(); + String bucket = props.getProperty(Names.TEST_BUCKET); + AmazonS3 s3 = new AmazonS3(props); + String file = JGIT_USER + "-" + UUID.randomUUID().toString(); + String path = JGIT_REMOTE_DIR + "/" + file; + s3.put(bucket, path, file.getBytes(UTF_8)); + s3.delete(bucket, path); + } + + /** + * Verify if any security provider published the algorithm. + * + * @param algorithm + * @return result + */ + static boolean isAlgorithmPresent(String algorithm) { + Set<String> cipherSet = Security.getAlgorithms("Cipher"); + for (String source : cipherSet) { + // Standard names are not case-sensitive. + // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html + String target = algorithm.toUpperCase(); + if (source.equalsIgnoreCase(target)) { + return true; + } + } + return false; + } + + static boolean isAlgorithmPresent(Properties props) { + String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG); + String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER, + WalkEncryption.Vals.DEFAULT_VERS); + String crytoAlgo; + String keyAlgo; + switch (version) { + case WalkEncryption.Vals.DEFAULT_VERS: + case WalkEncryption.JGitV1.VERSION: + crytoAlgo = profile; + keyAlgo = profile; + break; + case WalkEncryption.JGitV2.VERSION: + crytoAlgo = props + .getProperty(profile + WalkEncryption.Keys.X_ALGO); + keyAlgo = props + .getProperty(profile + WalkEncryption.Keys.X_KEY_ALGO); + break; + default: + return false; + } + try { + Cipher.getInstance(crytoAlgo); + SecretKeyFactory.getInstance(keyAlgo); + return true; + } catch (Throwable e) { + return false; + } + } + + /** + * Verify if JRE security policy allows the algorithm. + * + * @param algorithm + * @return result + */ + static boolean isAlgorithmAllowed(String algorithm) { + try { + WalkEncryption crypto = new WalkEncryption.JetS3tV2( + algorithm, JGIT_PASS); + verifyCrypto(crypto); + return true; + } catch (IOException e) { + return false; // Encryption failure. + } catch (GeneralSecurityException e) { + throw new Error(e); // Construction failure. + } + } + + static boolean isAlgorithmAllowed(Properties props) { + try { + WalkEncryption.instance(props); + return true; + } catch (GeneralSecurityException e) { + return false; + } + } + + /** + * Verify round trip encryption. + * + * @param crypto + * @throws IOException + */ + static void verifyCrypto(WalkEncryption crypto) throws IOException { + String charset = "UTF-8"; + String sourceText = "secret-message Свобода 老子"; + String targetText; + byte[] cipherText; + { + byte[] origin = sourceText.getBytes(charset); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + OutputStream source = crypto.encrypt(target); + source.write(origin); + source.flush(); + source.close(); + cipherText = target.toByteArray(); + } + { + InputStream source = new ByteArrayInputStream(cipherText); + InputStream target = crypto.decrypt(source); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + transferStream(target, result); + targetText = result.toString(charset); + } + assertEquals(sourceText, targetText); + } + + /** + * Algorithm is testable when it is present and allowed by policy. + * + * @param algorithm + * @return result + */ + static boolean isAlgorithmTestable(String algorithm) { + return isAlgorithmPresent(algorithm) + && isAlgorithmAllowed(algorithm); + } + + static boolean isAlgorithmTestable(Properties props) { + return isAlgorithmPresent(props) && isAlgorithmAllowed(props); + } + + /** + * Log algorithm, provider, testability. + * + * @param algorithm + * @throws Exception + */ + static void reportAlgorithmStatus(String algorithm) throws Exception { + final boolean present = isAlgorithmPresent(algorithm); + final boolean allowed = present && isAlgorithmAllowed(algorithm); + final String provider = present ? securityProviderName(algorithm) + : "N/A"; + String status = "Algorithm: " + algorithm + " @ " + provider + "; " + + "present/allowed : " + present + "/" + allowed; + if (allowed) { + logger.info("Testing " + status); + } else { + logger.warn("Missing " + status); + } + } + + static void reportAlgorithmStatus(Properties props) throws Exception { + final boolean present = isAlgorithmPresent(props); + final boolean allowed = present && isAlgorithmAllowed(props); + + String profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG); + String version = props.getProperty(AmazonS3.Keys.CRYPTO_VER); + + StringBuilder status = new StringBuilder(); + status.append(" Version: " + version); + status.append(" Profile: " + profile); + status.append(" Present: " + present); + status.append(" Allowed: " + allowed); + + if (allowed) { + logger.info("Testing " + status); + } else { + logger.warn("Missing " + status); + } + } + + /** + * Verify if we can perform remote tests. + * + * @return result + */ + static boolean isTestConfigPresent() { + try { + Props.discover(); + return true; + } catch (Throwable e) { + return false; + } + } + + static void reportTestConfigPresent() { + if (isTestConfigPresent()) { + logger.info("Amazon S3 test configuration is present."); + } else { + logger.error( + "Amazon S3 test configuration is missing, tests will not run."); + } + } + + /** + * Log public address of CI. + * + * @throws Exception + */ + static void reportPublicAddress() throws Exception { + logger.info("Public address: " + publicAddress()); + } + + /** + * BouncyCastle provider class. + * + * Needs extra dependency, see pom.xml + */ + // http://search.maven.org/#artifactdetails%7Corg.bouncycastle%7Cbcprov-jdk15on%7C1.52%7Cjar + static final String PROVIDER_BC = "org.bouncycastle.jce.provider.BouncyCastleProvider"; + + /** + * Load BouncyCastle provider if present. + */ + static void loadBouncyCastle() { + try { + Class<?> provider = Class.forName(PROVIDER_BC); + Provider instance = (Provider) provider + .getConstructor(new Class[] {}) + .newInstance(new Object[] {}); + Security.addProvider(instance); + logger.info("Loaded " + PROVIDER_BC); + } catch (Throwable e) { + logger.warn("Failed to load " + PROVIDER_BC); + } + } + + static void reportLongTests() { + if (permitLongTests()) { + logger.info("Long running tests are enabled."); + } else { + logger.warn("Long running tests are disabled."); + } + } + + /** + * Non-PBE algorithm, for error check. + */ + static final String ALGO_ERROR = "PBKDF2WithHmacSHA1"; + + /** + * Default JetS3t algorithm present in most JRE. + */ + static final String ALGO_JETS3T = "PBEWithMD5AndDES"; + + /** + * Minimal strength AES based algorithm present in most JRE. + */ + static final String ALGO_MINIMAL_AES = "PBEWithHmacSHA1AndAES_128"; + + /** + * Selected non-AES algorithm present in BouncyCastle provider. + */ + static final String ALGO_BOUNCY_CASTLE_CBC = "PBEWithSHAAndTwofish-CBC"; + + ////////////////////////////////////////////////// + + @BeforeClass + public static void initialize() throws Exception { + Transport.register(TransportAmazonS3.PROTO_S3); + proxySetup(); + reportPolicy(); + reportLongTests(); + reportPublicAddress(); + reportTestConfigPresent(); + loadBouncyCastle(); + if (isTestConfigPresent()) { + remoteCreate(); + } + } + + @AfterClass + public static void terminate() throws Exception { + configDelete(); + folderDelete(JGIT_LOCAL_DIR); + if (isTestConfigPresent()) { + remoteDelete(); + } + } + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @After + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Optional encrypted amazon remote JGIT life cycle test. + * + * @param props + * @throws Exception + */ + void cryptoTestIfCan(Properties props) throws Exception { + reportAlgorithmStatus(props); + assumeTrue(isTestConfigPresent()); + assumeTrue(isAlgorithmTestable(props)); + cryptoTest(props); + } + + /** + * Required encrypted amazon remote JGIT life cycle test. + * + * @param props + * @throws Exception + */ + void cryptoTest(Properties props) throws Exception { + + remoteDelete(); + configCreate(props); + folderDelete(JGIT_LOCAL_DIR); + + String uri = amazonURI(); + + // Local repositories. + File dirOne = db.getWorkTree(); // Provided by setup. + File dirTwo = new File(JGIT_LOCAL_DIR); + + // Local verification files. + String nameStatic = "master.txt"; // Provided by setup. + String nameDynamic = JGIT_USER + "-" + UUID.randomUUID().toString(); + + String remote = "remote"; + RefSpec specs = new RefSpec("refs/heads/master:refs/heads/master"); + + { // Push into remote from local one. + + StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, remote); + remoteConfig.addURI(new URIish(uri)); + remoteConfig.update(config); + config.save(); + + Git git = Git.open(dirOne); + git.checkout().setName("master").call(); + git.push().setRemote(remote).setRefSpecs(specs).call(); + git.close(); + + File fileStatic = new File(dirOne, nameStatic); + assertTrue("Provided by setup", fileStatic.exists()); + + } + + { // Clone from remote into local two. + + File fileStatic = new File(dirTwo, nameStatic); + assertFalse("Not Provided by setup", fileStatic.exists()); + + Git git = Git.cloneRepository().setURI(uri).setDirectory(dirTwo) + .call(); + git.close(); + + assertTrue("Provided by clone", fileStatic.exists()); + } + + { // Verify static file content. + File fileOne = new File(dirOne, nameStatic); + File fileTwo = new File(dirTwo, nameStatic); + verifyFileContent(fileOne, fileTwo); + } + + { // Verify new file commit and push from local one. + + File fileDynamic = new File(dirOne, nameDynamic); + assertFalse("Not Provided by setup", fileDynamic.exists()); + FileUtils.createNewFile(fileDynamic); + textWrite(fileDynamic, nameDynamic); + assertTrue("Provided by create", fileDynamic.exists()); + assertTrue("Need content to encrypt", fileDynamic.length() > 0); + + Git git = Git.open(dirOne); + git.add().addFilepattern(nameDynamic).call(); + git.commit().setMessage(nameDynamic).call(); + git.push().setRemote(remote).setRefSpecs(specs).call(); + git.close(); + + } + + { // Verify new file pull from remote into local two. + + File fileDynamic = new File(dirTwo, nameDynamic); + assertFalse("Not Provided by setup", fileDynamic.exists()); + + Git git = Git.open(dirTwo); + git.pull().call(); + git.close(); + + assertTrue("Provided by pull", fileDynamic.exists()); + } + + { // Verify dynamic file content. + File fileOne = new File(dirOne, nameDynamic); + File fileTwo = new File(dirTwo, nameDynamic); + verifyFileContent(fileOne, fileTwo); + } + + } + + } + + /** + * Verify prerequisites. + */ + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class Required extends Base { + + @Test + public void test_A1_ValidURI() throws Exception { + assumeTrue(isTestConfigPresent()); + URIish uri = new URIish(amazonURI()); + assertTrue("uri=" + uri, TransportAmazonS3.PROTO_S3.canHandle(uri)); + } + + @Test(expected = Exception.class) + public void test_A2_CryptoError() throws Exception { + assumeTrue(isTestConfigPresent()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_ERROR); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + cryptoTest(props); + } + + } + + /** + * Test minimal set of algorithms. + */ + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class MinimalSet extends Base { + + @Test + public void test_V0_Java7_JET() throws Exception { + assumeTrue(isTestConfigPresent()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T); + // Do not set version. + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + cryptoTestIfCan(props); + } + + @Test + public void test_V1_Java7_GIT() throws Exception { + assumeTrue(isTestConfigPresent()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, ALGO_JETS3T); + props.put(AmazonS3.Keys.CRYPTO_VER, "1"); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + cryptoTestIfCan(props); + } + + @Test + public void test_V2_Java7_AES() throws Exception { + assumeTrue(isTestConfigPresent()); + // String profile = "default"; + String profile = "AES/CBC/PKCS5Padding+PBKDF2WithHmacSHA1"; + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, "2"); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + props.put(profile + WalkEncryption.Keys.X_ALGO, "AES/CBC/PKCS5Padding"); + props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBKDF2WithHmacSHA1"); + props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "128"); + props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000"); + props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c"); + cryptoTestIfCan(props); + } + + @Test + public void test_V2_Java8_PBE_AES() throws Exception { + assumeTrue(isTestConfigPresent()); + String profile = "PBEWithHmacSHA512AndAES_256"; + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, "2"); + props.put(AmazonS3.Keys.PASSWORD, JGIT_PASS); + props.put(profile + WalkEncryption.Keys.X_ALGO, "PBEWithHmacSHA512AndAES_256"); + props.put(profile + WalkEncryption.Keys.X_KEY_ALGO, "PBEWithHmacSHA512AndAES_256"); + props.put(profile + WalkEncryption.Keys.X_KEY_SIZE, "256"); + props.put(profile + WalkEncryption.Keys.X_KEY_ITER, "10000"); + props.put(profile + WalkEncryption.Keys.X_KEY_SALT, "e2 55 89 67 8e 8d e8 4c"); + policySetup(false); + cryptoTestIfCan(props); + } + + } + + /** + * Test all present and allowed PBE algorithms. + */ + // https://github.com/junit-team/junit/wiki/Parameterized-tests + @RunWith(Parameterized.class) + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class TestablePBE extends Base { + + @Parameters(name = "Profile: {0} Version: {1}") + public static Collection<Object[]> argsList() { + List<String> algorithmList = new ArrayList<String>(); + algorithmList.addAll(cryptoCipherListPBE()); + + List<String> versionList = new ArrayList<String>(); + versionList.add("0"); + versionList.add("1"); + + return product(algorithmList, versionList); + } + + final String profile; + + final String version; + + final String password = JGIT_PASS; + + public TestablePBE(String profile, String version) { + this.profile = profile; + this.version = version; + } + + @Test + public void testCrypto() throws Exception { + assumeTrue(permitLongTests()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, version); + props.put(AmazonS3.Keys.PASSWORD, password); + cryptoTestIfCan(props); + } + + } + + /** + * Test all present and allowed transformation algorithms. + */ + // https://github.com/junit-team/junit/wiki/Parameterized-tests + @RunWith(Parameterized.class) + @FixMethodOrder(MethodSorters.NAME_ASCENDING) + public static class TestableTransformation extends Base { + + @Parameters(name = "Profile: {0} Version: {1}") + public static Collection<Object[]> argsList() { + List<String> algorithmList = new ArrayList<String>(); + algorithmList.addAll(cryptoCipherListTrans()); + + List<String> versionList = new ArrayList<String>(); + versionList.add("1"); + + return product(algorithmList, versionList); + } + + final String profile; + + final String version; + + final String password = JGIT_PASS; + + public TestableTransformation(String profile, String version) { + this.profile = profile; + this.version = version; + } + + @Test + public void testCrypto() throws Exception { + assumeTrue(permitLongTests()); + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, profile); + props.put(AmazonS3.Keys.CRYPTO_VER, version); + props.put(AmazonS3.Keys.PASSWORD, password); + cryptoTestIfCan(props); + } + + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java index b14a9bf2fa..8aa14c5218 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java @@ -92,9 +92,11 @@ public class HookTest extends RepositoryTestCase { fail("expected commit-msg hook to abort commit"); } catch (AbortedByHookException e) { assertEquals("unexpected error message from commit-msg hook", - "Rejected by \"commit-msg\" hook.\nstderr\n", + "Rejected by \"commit-msg\" hook.\nstderr" + + System.lineSeparator(), e.getMessage()); - assertEquals("unexpected output from commit-msg hook", "test\n", + assertEquals("unexpected output from commit-msg hook", + "test" + System.lineSeparator(), out.toString()); } } @@ -112,7 +114,8 @@ public class HookTest extends RepositoryTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); git.commit().setMessage("commit") .setHookOutputStream(new PrintStream(out)).call(); - assertEquals(".git/COMMIT_EDITMSG\n", out.toString("UTF-8")); + assertEquals(".git/COMMIT_EDITMSG" + System.lineSeparator(), + out.toString("UTF-8")); } @Test @@ -144,9 +147,11 @@ public class HookTest extends RepositoryTestCase { new String[] { "arg1", "arg2" }, new PrintStream(out), new PrintStream(err), "stdin"); - assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n", + assertEquals("unexpected hook output", "test arg1 arg2" + + System.lineSeparator() + "stdin" + System.lineSeparator(), out.toString("UTF-8")); - assertEquals("unexpected output on stderr stream", "stderr\n", + assertEquals("unexpected output on stderr stream", + "stderr" + System.lineSeparator(), err.toString("UTF-8")); assertEquals("unexpected exit code", 0, res.getExitCode()); assertEquals("unexpected process status", ProcessResult.Status.OK, @@ -170,9 +175,11 @@ public class HookTest extends RepositoryTestCase { fail("expected pre-commit hook to abort commit"); } catch (AbortedByHookException e) { assertEquals("unexpected error message from pre-commit hook", - "Rejected by \"pre-commit\" hook.\nstderr\n", + "Rejected by \"pre-commit\" hook.\nstderr" + + System.lineSeparator(), e.getMessage()); - assertEquals("unexpected output from pre-commit hook", "test\n", + assertEquals("unexpected output from pre-commit hook", + "test" + System.lineSeparator(), out.toString()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java new file mode 100644 index 0000000000..82beab2dc8 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.util; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.junit.JGitTestUtil; +import org.junit.Before; +import org.junit.Test; + +public class RunExternalScriptTest { + private ByteArrayOutputStream out; + + private ByteArrayOutputStream err; + + private String sep = System.getProperty("line.separator"); + + @Before + public void setUp() throws Exception { + out = new ByteArrayOutputStream(); + err = new ByteArrayOutputStream(); + } + + @Test + public void testCopyStdIn() throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile("cat -"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh", script.getPath()), out, err, + new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(0, rc); + assertEquals(inputStr, new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testCopyNullStdIn() throws IOException, InterruptedException { + File script = writeTempFile("cat -"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh", script.getPath()), out, err, + (InputStream) null); + assertEquals(0, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testArguments() throws IOException, InterruptedException { + File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6"); + int rc = FS.DETECTED.runProcess(new ProcessBuilder("/bin/bash", + script.getPath(), "a", "b", "c"), out, err, (InputStream) null); + assertEquals(0, rc); + assertEquals("3,a,b,c,,,\n", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testRc() throws IOException, InterruptedException { + File script = writeTempFile("exit 3"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"), + out, err, (InputStream) null); + assertEquals(3, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testNullStdout() throws IOException, InterruptedException { + File script = writeTempFile("echo hi"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh", script.getPath()), null, err, + (InputStream) null); + assertEquals(0, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("", new String(err.toByteArray())); + } + + @Test + public void testStdErr() throws IOException, InterruptedException { + File script = writeTempFile("echo hi >&2"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh", script.getPath()), null, err, + (InputStream) null); + assertEquals(0, rc); + assertEquals("", new String(out.toByteArray())); + assertEquals("hi" + sep, new String(err.toByteArray())); + } + + @Test + public void testAllTogetherBin() throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"), + out, err, new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(5, rc); + assertEquals(inputStr, new String(out.toByteArray())); + assertEquals("3,a,b,c,,," + sep, new String(err.toByteArray())); + } + + @Test(expected = IOException.class) + public void testWrongSh() throws IOException, InterruptedException { + File script = writeTempFile("cat -"); + FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh-foo", script.getPath(), "a", "b", + "c"), out, err, (InputStream) null); + } + + @Test + public void testWrongScript() throws IOException, InterruptedException { + File script = writeTempFile("cat-foo -"); + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"), + out, err, (InputStream) null); + assertEquals(127, rc); + } + + private File writeTempFile(String body) throws IOException { + File f = File.createTempFile("RunProcessTestScript_", ""); + JGitTestUtil.write(f, body); + return f; + } +} diff --git a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF index 3df4347016..4e2db1bfcc 100644 --- a/org.eclipse.jgit.ui/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.ui/META-INF/MANIFEST.MF @@ -3,14 +3,14 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit.ui -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Vendor: %provider_name Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Export-Package: org.eclipse.jgit.awtui;version="4.1.2" -Import-Package: org.eclipse.jgit.errors;version="[4.1.2,4.2.0)", - org.eclipse.jgit.lib;version="[4.1.2,4.2.0)", - org.eclipse.jgit.nls;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revplot;version="[4.1.2,4.2.0)", - org.eclipse.jgit.revwalk;version="[4.1.2,4.2.0)", - org.eclipse.jgit.transport;version="[4.1.2,4.2.0)", - org.eclipse.jgit.util;version="[4.1.2,4.2.0)" +Export-Package: org.eclipse.jgit.awtui;version="4.2.0" +Import-Package: org.eclipse.jgit.errors;version="[4.2.0,4.3.0)", + org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", + org.eclipse.jgit.nls;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revplot;version="[4.2.0,4.3.0)", + org.eclipse.jgit.revwalk;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.util;version="[4.2.0,4.3.0)" diff --git a/org.eclipse.jgit.ui/pom.xml b/org.eclipse.jgit.ui/pom.xml index 79c30f0ac9..6a56f8cf33 100644 --- a/org.eclipse.jgit.ui/pom.xml +++ b/org.eclipse.jgit.ui/pom.xml @@ -52,7 +52,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit.ui</artifactId> diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index a1e79e2d29..ed330b1517 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -1,5 +1,19 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <component id="org.eclipse.jgit" version="2"> + <resource path="src/org/eclipse/jgit/lib/BitmapIndex.java" type="org.eclipse.jgit.lib.BitmapIndex$BitmapBuilder"> + <filter comment="interface is implemented by extenders but not clients of the API" id="403804204"> + <message_arguments> + <message_argument value="org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder"/> + <message_argument value="addObject(AnyObjectId, int)"/> + </message_arguments> + </filter> + <filter comment="interface is implemented by extenders but not clients of the API" id="403804204"> + <message_arguments> + <message_argument value="org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder"/> + <message_argument value="getBitmapIndex()"/> + </message_arguments> + </filter> + </resource> <resource path="src/org/eclipse/jgit/transport/PushCertificate.java" type="org.eclipse.jgit.transport.PushCertificate"> <filter comment="PushCertificate wasn't really usable in 4.0" id="338722907"> <message_arguments> @@ -21,4 +35,12 @@ </message_arguments> </filter> </resource> + <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils"> + <filter id="338792546"> + <message_arguments> + <message_argument value="org.eclipse.jgit.util.FileUtils"/> + <message_argument value="createSymLink(File, String)"/> + </message_arguments> + </filter> + </resource> </component> diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs index 4e28e0b26b..195987db64 100644 --- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 24798be02a..b6899b2801 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -2,11 +2,11 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %plugin_name Bundle-SymbolicName: org.eclipse.jgit -Bundle-Version: 4.1.2.qualifier +Bundle-Version: 4.2.0.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy -Export-Package: org.eclipse.jgit.api;version="4.1.2"; +Export-Package: org.eclipse.jgit.api;version="4.2.0"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, @@ -20,60 +20,60 @@ Export-Package: org.eclipse.jgit.api;version="4.1.2"; org.eclipse.jgit.submodule, org.eclipse.jgit.transport, org.eclipse.jgit.merge", - org.eclipse.jgit.api.errors;version="4.1.2"; + org.eclipse.jgit.api.errors;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.errors", - org.eclipse.jgit.attributes;version="4.1.2", - org.eclipse.jgit.blame;version="4.1.2"; + org.eclipse.jgit.attributes;version="4.2.0", + org.eclipse.jgit.blame;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff", - org.eclipse.jgit.diff;version="4.1.2"; + org.eclipse.jgit.diff;version="4.2.0"; uses:="org.eclipse.jgit.patch, org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util", - org.eclipse.jgit.dircache;version="4.1.2"; + org.eclipse.jgit.dircache;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.util, org.eclipse.jgit.events, org.eclipse.jgit.attributes", - org.eclipse.jgit.errors;version="4.1.2"; + org.eclipse.jgit.errors;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.internal.storage.pack, org.eclipse.jgit.transport, org.eclipse.jgit.dircache", - org.eclipse.jgit.events;version="4.1.2"; + org.eclipse.jgit.events;version="4.2.0"; uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.fnmatch;version="4.1.2", - org.eclipse.jgit.gitrepo;version="4.1.2"; + org.eclipse.jgit.fnmatch;version="4.2.0", + org.eclipse.jgit.gitrepo;version="4.2.0"; uses:="org.eclipse.jgit.api, org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.xml.sax.helpers, org.xml.sax", - org.eclipse.jgit.gitrepo.internal;version="4.1.2";x-internal:=true, - org.eclipse.jgit.hooks;version="4.1.2"; + org.eclipse.jgit.gitrepo.internal;version="4.2.0";x-internal:=true, + org.eclipse.jgit.hooks;version="4.2.0"; uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.ignore;version="4.1.2", - org.eclipse.jgit.ignore.internal;version="4.1.2";x-friends:="org.eclipse.jgit.test", - org.eclipse.jgit.internal;version="4.1.2";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", - org.eclipse.jgit.internal.storage.dfs;version="4.1.2"; + org.eclipse.jgit.ignore;version="4.2.0", + org.eclipse.jgit.ignore.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test", + org.eclipse.jgit.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", + org.eclipse.jgit.internal.storage.dfs;version="4.2.0"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.http.server", - org.eclipse.jgit.internal.storage.file;version="4.1.2"; + org.eclipse.jgit.internal.storage.file;version="4.2.0"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.junit, org.eclipse.jgit.junit.http, org.eclipse.jgit.http.server, org.eclipse.jgit.java7.test, org.eclipse.jgit.pgm", - org.eclipse.jgit.internal.storage.pack;version="4.1.2";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", - org.eclipse.jgit.lib;version="4.1.2"; + org.eclipse.jgit.internal.storage.pack;version="4.2.0";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.lib;version="4.2.0"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, @@ -83,45 +83,45 @@ Export-Package: org.eclipse.jgit.api;version="4.1.2"; org.eclipse.jgit.treewalk, org.eclipse.jgit.transport, org.eclipse.jgit.submodule", - org.eclipse.jgit.merge;version="4.1.2"; + org.eclipse.jgit.merge;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.diff, org.eclipse.jgit.dircache, org.eclipse.jgit.api", - org.eclipse.jgit.nls;version="4.1.2", - org.eclipse.jgit.notes;version="4.1.2"; + org.eclipse.jgit.nls;version="4.2.0", + org.eclipse.jgit.notes;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.merge", - org.eclipse.jgit.patch;version="4.1.2"; + org.eclipse.jgit.patch;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.diff", - org.eclipse.jgit.revplot;version="4.1.2"; + org.eclipse.jgit.revplot;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk", - org.eclipse.jgit.revwalk;version="4.1.2"; + org.eclipse.jgit.revwalk;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, org.eclipse.jgit.revwalk.filter", - org.eclipse.jgit.revwalk.filter;version="4.1.2"; + org.eclipse.jgit.revwalk.filter;version="4.2.0"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.lib, org.eclipse.jgit.util", - org.eclipse.jgit.storage.file;version="4.1.2"; + org.eclipse.jgit.storage.file;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.util", - org.eclipse.jgit.storage.pack;version="4.1.2"; + org.eclipse.jgit.storage.pack;version="4.2.0"; uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.submodule;version="4.1.2"; + org.eclipse.jgit.submodule;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.treewalk", - org.eclipse.jgit.transport;version="4.1.2"; + org.eclipse.jgit.transport;version="4.2.0"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.revwalk, org.eclipse.jgit.internal.storage.pack, @@ -133,29 +133,28 @@ Export-Package: org.eclipse.jgit.api;version="4.1.2"; org.eclipse.jgit.transport.http, org.eclipse.jgit.errors, org.eclipse.jgit.storage.pack", - org.eclipse.jgit.transport.http;version="4.1.2"; + org.eclipse.jgit.transport.http;version="4.2.0"; uses:="javax.net.ssl", - org.eclipse.jgit.transport.resolver;version="4.1.2"; + org.eclipse.jgit.transport.resolver;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.transport", - org.eclipse.jgit.treewalk;version="4.1.2"; + org.eclipse.jgit.treewalk;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, org.eclipse.jgit.attributes, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, org.eclipse.jgit.dircache", - org.eclipse.jgit.treewalk.filter;version="4.1.2"; + org.eclipse.jgit.treewalk.filter;version="4.2.0"; uses:="org.eclipse.jgit.treewalk", - org.eclipse.jgit.util;version="4.1.2"; + org.eclipse.jgit.util;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.transport.http, org.eclipse.jgit.storage.file, org.ietf.jgss", - org.eclipse.jgit.util.io;version="4.1.2" + org.eclipse.jgit.util.io;version="4.2.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: com.jcraft.jsch;bundle-version="[0.1.37,0.2.0)", - org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional +Require-Bundle: com.jcraft.jsch;bundle-version="[0.1.37,0.2.0)" Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", javax.crypto, javax.net.ssl, diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF index c5476a85cf..a03d7df6ce 100644 --- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF @@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2 Bundle-Name: org.eclipse.jgit - Sources Bundle-SymbolicName: org.eclipse.jgit.source Bundle-Vendor: Eclipse.org - JGit -Bundle-Version: 4.1.2.qualifier -Eclipse-SourceBundle: org.eclipse.jgit;version="4.1.2.qualifier";roots="." +Bundle-Version: 4.2.0.qualifier +Eclipse-SourceBundle: org.eclipse.jgit;version="4.2.0.qualifier";roots="." diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml index 8791389dac..4f98323e5d 100644 --- a/org.eclipse.jgit/pom.xml +++ b/org.eclipse.jgit/pom.xml @@ -53,7 +53,7 @@ <parent> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> </parent> <artifactId>org.eclipse.jgit</artifactId> @@ -88,12 +88,6 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> - - <dependency> - <groupId>org.eclipse.jdt</groupId> - <artifactId>org.eclipse.jdt.annotation</artifactId> - <version>1.1.0</version> - </dependency> </dependencies> <build> @@ -166,8 +160,45 @@ </plugin> <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>cmp</goal> + </goals> + </execution> + </executions> </plugin> </plugins> @@ -187,13 +218,44 @@ <reporting> <plugins> <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - <version>${clirr-version}</version> - <configuration> - <comparisonVersion>${jgit-last-release-version}</comparisonVersion> - <minSeverity>info</minSeverity> - </configuration> + <groupId>com.github.siom79.japicmp</groupId> + <artifactId>japicmp-maven-plugin</artifactId> + <version>${japicmp-version}</version> + <reportSets> + <reportSet> + <reports> + <report>cmp-report</report> + </reports> + </reportSet> + </reportSets> + <configuration> + <oldVersion> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${jgit-last-release-version}</version> + </dependency> + </oldVersion> + <newVersion> + <file> + <path>${project.build.directory}/${project.artifactId}-${project.version}.jar</path> + </file> + </newVersion> + <parameter> + <onlyModified>true</onlyModified> + <includes> + <include>org.eclipse.jgit.*</include> + </includes> + <accessModifier>public</accessModifier> + <breakBuildOnModifications>false</breakBuildOnModifications> + <breakBuildOnBinaryIncompatibleModifications>false</breakBuildOnBinaryIncompatibleModifications> + <onlyBinaryIncompatible>false</onlyBinaryIncompatible> + <includeSynthetic>false</includeSynthetic> + <ignoreMissingClasses>false</ignoreMissingClasses> + <skipPomModules>true</skipPomModules> + </parameter> + <skip>false</skip> + </configuration> </plugin> </plugins> </reporting> 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 34bbb415ba..51e44fd778 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -228,6 +228,7 @@ emptyCommit=No changes emptyPathNotPermitted=Empty path not permitted. emptyRef=Empty ref: {0} encryptionError=Encryption error: {0} +encryptionOnlyPBE=Encryption error: only password-based encryption (PBE) algorithms are supported. endOfFileInEscape=End of file in escape entryNotFoundByPath=Entry not found by path: {0} enumValueNotSupported2=Invalid value: {0}.{1}={2} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java new file mode 100644 index 0000000000..254920e7a3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JGit's replacement for the {@code javax.annotations.Nullable}. + * <p> + * Denotes that a local variable, parameter, field, method return value can be + * {@code null}. + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE }) +public @interface Nullable { + // marker annotation with no members +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index 8d8aada622..8d85bfcb15 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -457,7 +457,7 @@ public class CheckoutCommand extends GitCommand<Ref> { private void checkoutPath(DirCacheEntry entry, ObjectReader reader) { try { - DirCacheCheckout.checkoutEntry(repo, entry, reader); + DirCacheCheckout.checkoutEntry(repo, entry, reader, true); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java index 6de25a052a..8ef550871f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java @@ -357,7 +357,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { private void checkoutPath(DirCacheEntry entry, ObjectReader reader) { try { - DirCacheCheckout.checkoutEntry(repo, entry, reader); + DirCacheCheckout.checkoutEntry(repo, entry, reader, true); } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().checkoutConflictWithFile, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java index 3c780e7d88..0dc4b05787 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java @@ -148,7 +148,7 @@ public abstract class ContentSource { private String current; - private WorkingTreeIterator ptr; + WorkingTreeIterator ptr; WorkingTreeSource(WorkingTreeIterator iterator) { this.tw = new TreeWalk((ObjectReader) null); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java index e57faaf858..2f5c9ea84c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java @@ -94,7 +94,7 @@ import java.util.List; */ public class HistogramDiff extends LowLevelDiffAlgorithm { /** Algorithm to use when there are too many element occurrences. */ - private DiffAlgorithm fallback = MyersDiff.INSTANCE; + DiffAlgorithm fallback = MyersDiff.INSTANCE; /** * Maximum number of positions to consider for a given element hash. @@ -103,7 +103,7 @@ public class HistogramDiff extends LowLevelDiffAlgorithm { * size is capped to ensure search is linear time at O(len_A + len_B) rather * than quadratic at O(len_A * len_B). */ - private int maxChainLength = 64; + int maxChainLength = 64; /** * Set the algorithm used when there are too many element occurrences. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index 00252547db..0036ab5089 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -447,7 +447,7 @@ public class DirCacheCheckout { for (String path : updated.keySet()) { DirCacheEntry entry = dc.getEntry(path); if (!FileMode.GITLINK.equals(entry.getRawMode())) - checkoutEntry(repo, entry, objectReader); + checkoutEntry(repo, entry, objectReader, false); } // commit the index builder - a new index is persisted @@ -1127,6 +1127,13 @@ public class DirCacheCheckout { * final filename. * * <p> + * <b>Note:</b> if the entry path on local file system exists as a non-empty + * directory, and the target entry type is a link or file, the checkout will + * fail with {@link IOException} since existing non-empty directory cannot + * be renamed to file or link without deleting it recursively. + * </p> + * + * <p> * TODO: this method works directly on File IO, we may need another * abstraction (like WorkingTreeIterator). This way we could tell e.g. * Eclipse that Files in the workspace got changed @@ -1143,6 +1150,42 @@ public class DirCacheCheckout { */ public static void checkoutEntry(Repository repo, DirCacheEntry entry, ObjectReader or) throws IOException { + checkoutEntry(repo, entry, or, false); + } + + /** + * Updates the file in the working tree with content and mode from an entry + * in the index. The new content is first written to a new temporary file in + * the same directory as the real file. Then that new file is renamed to the + * final filename. + * + * <p> + * <b>Note:</b> if the entry path on local file system exists as a file, it + * will be deleted and if it exists as a directory, it will be deleted + * recursively, independently if has any content. + * </p> + * + * <p> + * TODO: this method works directly on File IO, we may need another + * abstraction (like WorkingTreeIterator). This way we could tell e.g. + * Eclipse that Files in the workspace got changed + * </p> + * + * @param repo + * repository managing the destination work tree. + * @param entry + * the entry containing new mode and content + * @param or + * object reader to use for checkout + * @param deleteRecursive + * true to recursively delete final path if it exists on the file + * system + * + * @throws IOException + * @since 4.2 + */ + public static void checkoutEntry(Repository repo, DirCacheEntry entry, + ObjectReader or, boolean deleteRecursive) throws IOException { ObjectLoader ol = or.open(entry.getObjectId()); File f = new File(repo.getWorkTree(), entry.getPathString()); File parentDir = f.getParentFile(); @@ -1153,6 +1196,9 @@ public class DirCacheCheckout { && opt.getSymLinks() == SymLinks.TRUE) { byte[] bytes = ol.getBytes(); String target = RawParseUtils.decode(bytes); + if (deleteRecursive && f.isDirectory()) { + FileUtils.delete(f, FileUtils.RECURSIVE); + } fs.createSymLink(f, target); entry.setLength(bytes.length); entry.setLastModified(fs.lastModified(f)); @@ -1183,11 +1229,18 @@ public class DirCacheCheckout { } } try { + if (deleteRecursive && f.isDirectory()) { + FileUtils.delete(f, FileUtils.RECURSIVE); + } FileUtils.rename(tmpFile, f); } catch (IOException e) { throw new IOException(MessageFormat.format( JGitText.get().renameFileFailed, tmpFile.getPath(), f.getPath())); + } finally { + if (tmpFile.exists()) { + FileUtils.delete(tmpFile); + } } entry.setLastModified(f.lastModified()); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java index f139afc00b..0cbb83d6e2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java @@ -103,7 +103,7 @@ public class DirCacheTree { private DirCacheTree parent; /** Name of this tree within its parent. */ - private byte[] encodedName; + byte[] encodedName; /** Number of {@link DirCacheEntry} records that belong to this tree. */ private int entrySpan; 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 790f4db672..1d2d3bfaaf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -106,6 +106,7 @@ public class RepoCommand extends GitCommand<RevCommit> { private String groups; private String branch; private String targetBranch = Constants.HEAD; + private boolean recordRemoteBranch = false; private PersonIdent author; private RemoteReader callback; private InputStream inputStream; @@ -314,6 +315,30 @@ public class RepoCommand extends GitCommand<RevCommit> { } /** + * Set whether the branch name should be recorded in .gitmodules + * <p> + * Submodule entries in .gitmodules can include a "branch" field + * to indicate what remote branch each submodule tracks. + * <p> + * That field is used by "git submodule update --remote" to update + * to the tip of the tracked branch when asked and by Gerrit to + * update the superproject when a change on that branch is merged. + * <p> + * Subprojects that request a specific commit or tag will not have + * a branch name recorded. + * <p> + * Not implemented for non-bare repositories. + * + * @param enable Whether to record the branch name + * @return this command + * @since 4.2 + */ + public RepoCommand setRecordRemoteBranch(boolean enable) { + this.recordRemoteBranch = enable; + return this; + } + + /** * The progress monitor associated with the clone operation. By default, * this is set to <code>NullProgressMonitor</code> * @@ -429,10 +454,14 @@ public class RepoCommand extends GitCommand<RevCommit> { // create gitlink DirCacheEntry dcEntry = new DirCacheEntry(name); ObjectId objectId; - if (ObjectId.isId(proj.getRevision())) + if (ObjectId.isId(proj.getRevision())) { objectId = ObjectId.fromString(proj.getRevision()); - else { + } else { objectId = callback.sha1(nameUri, proj.getRevision()); + if (recordRemoteBranch) + // can be branch or tag + cfg.setString("submodule", name, "branch", //$NON-NLS-1$ //$NON-NLS-2$ + proj.getRevision()); } if (objectId == null) throw new RemoteUnavailableException(nameUri); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java index 1494576ab8..6f7a21a73f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java @@ -74,4 +74,15 @@ public class Hooks { PrintStream outputStream) { return new CommitMsgHook(repo, outputStream); } + + /** + * @param repo + * @param outputStream + * The output stream, or {@code null} to use {@code System.out} + * @return The pre-push hook for the given repository. + * @since 4.2 + */ + public static PrePushHook prePush(Repository repo, PrintStream outputStream) { + return new PrePushHook(repo, outputStream); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java new file mode 100644 index 0000000000..2e6582819f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Obeo. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.hooks; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; + +import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteRefUpdate; + +/** + * The <code>pre-push</code> hook implementation. The pre-push hook runs during + * git push, after the remote refs have been updated but before any objects have + * been transferred. + * + * @since 4.2 + */ +public class PrePushHook extends GitHook<String> { + + /** + * Constant indicating the name of the pre-push hook. + */ + public static final String NAME = "pre-push"; //$NON-NLS-1$ + + private String remoteName; + + private String remoteLocation; + + private String refs; + + /** + * @param repo + * The repository + * @param outputStream + * The output stream the hook must use. {@code null} is allowed, + * in which case the hook will use {@code System.out}. + */ + protected PrePushHook(Repository repo, PrintStream outputStream) { + super(repo, outputStream); + } + + @Override + protected String getStdinArgs() { + return refs; + } + + @Override + public String call() throws IOException, AbortedByHookException { + if (canRun()) { + doRun(); + } + return ""; //$NON-NLS-1$ + } + + /** + * @return {@code true} + */ + private boolean canRun() { + return true; + } + + @Override + public String getHookName() { + return NAME; + } + + /** + * This hook receives two parameters, which is the name and the location of + * the remote repository. + */ + @Override + protected String[] getParameters() { + return new String[] { remoteName, remoteLocation }; + } + + /** + * @param name + */ + public void setRemoteName(String name) { + remoteName = name; + } + + /** + * @param location + */ + public void setRemoteLocation(String location) { + remoteLocation = location; + } + + /** + * @param toRefs + */ + public void setRefs(Collection<RemoteRefUpdate> toRefs) { + StringBuilder b = new StringBuilder(); + boolean first = true; + for (RemoteRefUpdate u : toRefs) { + if (!first) + b.append("\n"); //$NON-NLS-1$ + else + first = false; + b.append(u.getSrcRef()); + b.append(" "); //$NON-NLS-1$ + b.append(u.getNewObjectId().getName()); + b.append(" "); //$NON-NLS-1$ + b.append(u.getRemoteName()); + b.append(" "); //$NON-NLS-1$ + ObjectId ooid = u.getExpectedOldObjectId(); + b.append((ooid == null) ? ObjectId.zeroId().getName() : ooid + .getName()); + } + refs = b.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java index f972828bc6..e354c7114c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java @@ -141,9 +141,7 @@ public class Strings { private static boolean isComplexWildcard(String pattern) { int idx1 = pattern.indexOf('['); if (idx1 != -1) { - int idx2 = pattern.indexOf(']', idx1); - if (idx2 > idx1) - return true; + return true; } if (pattern.indexOf('?') != -1) { return true; 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 9067e82954..e39469bd8c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -287,6 +287,7 @@ public class JGitText extends TranslationBundle { /***/ public String emptyPathNotPermitted; /***/ public String emptyRef; /***/ public String encryptionError; + /***/ public String encryptionOnlyPBE; /***/ public String endOfFileInEscape; /***/ public String entryNotFoundByPath; /***/ public String enumValueNotSupported2; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java index 488eee9794..e5ae9800fa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java @@ -95,16 +95,16 @@ public class DfsInserter extends ObjectInserter { /** Always produce version 2 indexes, to get CRC data. */ private static final int INDEX_VERSION = 2; - private final DfsObjDatabase db; - private int compression = Deflater.BEST_COMPRESSION; + final DfsObjDatabase db; + int compression = Deflater.BEST_COMPRESSION; - private List<PackedObjectInfo> objectList; - private ObjectIdOwnerMap<PackedObjectInfo> objectMap; + List<PackedObjectInfo> objectList; + ObjectIdOwnerMap<PackedObjectInfo> objectMap; - private DfsBlockCache cache; - private DfsPackKey packKey; - private DfsPackDescription packDsc; - private PackStream packOut; + DfsBlockCache cache; + DfsPackKey packKey; + DfsPackDescription packDsc; + PackStream packOut; private boolean rollback; /** @@ -137,7 +137,8 @@ public class DfsInserter extends ObjectInserter { ObjectId id = idFor(type, data, off, len); if (objectMap != null && objectMap.contains(id)) return id; - if (db.has(id)) + // Ignore unreachable (garbage) objects here. + if (db.has(id, true)) return id; long offset = beginObject(type, len); @@ -322,7 +323,7 @@ public class DfsInserter extends ObjectInserter { private class PackStream extends OutputStream { private final DfsOutputStream out; private final MessageDigest md; - private final byte[] hdrBuf; + final byte[] hdrBuf; private final Deflater deflater; private final int blockSize; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java index b92f784f29..5f491ff2fd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java @@ -54,6 +54,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.internal.storage.pack.PackExt; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; @@ -181,6 +182,28 @@ public abstract class DfsObjDatabase extends ObjectDatabase { } /** + * Does the requested object exist in this database? + * <p> + * This differs from ObjectDatabase's implementation in that we can selectively + * ignore unreachable (garbage) objects. + * + * @param objectId + * identity of the object to test for existence of. + * @param avoidUnreachableObjects + * if true, ignore objects that are unreachable. + * @return true if the specified object is stored in this database. + * @throws IOException + * the object store cannot be accessed. + */ + public boolean has(AnyObjectId objectId, boolean avoidUnreachableObjects) + throws IOException { + try (ObjectReader or = newReader()) { + or.setAvoidUnreachableObjects(avoidUnreachableObjects); + return or.has(objectId); + } + } + + /** * Generate a new unique name for a pack file. * * @param source diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java index 8e7af0d290..832e4fb6a8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java @@ -43,7 +43,7 @@ public class InMemoryRepository extends DfsRepository { } } - private static final AtomicInteger packId = new AtomicInteger(); + static final AtomicInteger packId = new AtomicInteger(); private final DfsObjDatabase objdb; @@ -60,7 +60,7 @@ public class InMemoryRepository extends DfsRepository { this(new Builder().setRepositoryDescription(repoDesc)); } - private InMemoryRepository(Builder builder) { + InMemoryRepository(Builder builder) { super(builder); objdb = new MemObjDatabase(this); refdb = new MemRefDatabase(); @@ -139,7 +139,7 @@ public class InMemoryRepository extends DfsRepository { } private static class MemPack extends DfsPackDescription { - private final Map<PackExt, byte[]> + final Map<PackExt, byte[]> fileMap = new HashMap<PackExt, byte[]>(); MemPack(String name, DfsRepositoryDescription repoDesc) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java index 0c3c7361a9..b27bcc4258 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java @@ -63,11 +63,11 @@ import org.eclipse.jgit.util.BlockList; public class BitmapIndexImpl implements BitmapIndex { private static final int EXTRA_BITS = 10 * 1024; - private final PackBitmapIndex packIndex; + final PackBitmapIndex packIndex; - private final MutableBitmapIndex mutableIndex; + final MutableBitmapIndex mutableIndex; - private final int indexObjectCount; + final int indexObjectCount; /** * Creates a BitmapIndex that is back by Compressed bitmaps. @@ -85,18 +85,20 @@ public class BitmapIndexImpl implements BitmapIndex { return packIndex; } + @Override public CompressedBitmap getBitmap(AnyObjectId objectId) { EWAHCompressedBitmap compressed = packIndex.getBitmap(objectId); if (compressed == null) return null; - return new CompressedBitmap(compressed); + return new CompressedBitmap(compressed, this); } + @Override public CompressedBitmapBuilder newBitmapBuilder() { - return new CompressedBitmapBuilder(); + return new CompressedBitmapBuilder(this); } - private int findPosition(AnyObjectId objectId) { + int findPosition(AnyObjectId objectId) { int position = packIndex.findPosition(objectId); if (position < 0) { position = mutableIndex.findPosition(objectId); @@ -106,10 +108,10 @@ public class BitmapIndexImpl implements BitmapIndex { return position; } - private int addObject(AnyObjectId objectId, int type) { + int findOrInsert(AnyObjectId objectId, int type) { int position = findPosition(objectId); if (position < 0) { - position = mutableIndex.addObject(objectId, type); + position = mutableIndex.findOrInsert(objectId, type); position += indexObjectCount; } return position; @@ -122,11 +124,11 @@ public class BitmapIndexImpl implements BitmapIndex { private BitSet toRemove; - private ComboBitset() { + ComboBitset() { this(new EWAHCompressedBitmap()); } - private ComboBitset(EWAHCompressedBitmap bitmap) { + ComboBitset(EWAHCompressedBitmap bitmap) { this.inflatingBitmap = new InflatingBitSet(bitmap); } @@ -197,15 +199,22 @@ public class BitmapIndexImpl implements BitmapIndex { } } - private final class CompressedBitmapBuilder implements BitmapBuilder { - private ComboBitset bitset = new ComboBitset(); + private static final class CompressedBitmapBuilder implements BitmapBuilder { + private ComboBitset bitset; + private final BitmapIndexImpl bitmapIndex; + CompressedBitmapBuilder(BitmapIndexImpl bitmapIndex) { + this.bitset = new ComboBitset(); + this.bitmapIndex = bitmapIndex; + } + + @Override public boolean add(AnyObjectId objectId, int type) { - int position = addObject(objectId, type); + int position = bitmapIndex.findOrInsert(objectId, type); if (bitset.contains(position)) return false; - Bitmap entry = getBitmap(objectId); + Bitmap entry = bitmapIndex.getBitmap(objectId); if (entry != null) { or(entry); return false; @@ -215,120 +224,142 @@ public class BitmapIndexImpl implements BitmapIndex { return true; } + @Override public boolean contains(AnyObjectId objectId) { - int position = findPosition(objectId); + int position = bitmapIndex.findPosition(objectId); return 0 <= position && bitset.contains(position); } + @Override + public BitmapBuilder addObject(AnyObjectId objectId, int type) { + bitset.set(bitmapIndex.findOrInsert(objectId, type)); + return this; + } + + @Override public void remove(AnyObjectId objectId) { - int position = findPosition(objectId); + int position = bitmapIndex.findPosition(objectId); if (0 <= position) bitset.remove(position); } + @Override public CompressedBitmapBuilder or(Bitmap other) { - if (isSameCompressedBitmap(other)) { - bitset.or(((CompressedBitmap) other).bitmap); - } else if (isSameCompressedBitmapBuilder(other)) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - bitset.or(b.bitset.combine()); - } else { - throw new IllegalArgumentException(); - } + bitset.or(ewahBitmap(other)); return this; } + @Override public CompressedBitmapBuilder andNot(Bitmap other) { - if (isSameCompressedBitmap(other)) { - bitset.andNot(((CompressedBitmap) other).bitmap); - } else if (isSameCompressedBitmapBuilder(other)) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - bitset.andNot(b.bitset.combine()); - } else { - throw new IllegalArgumentException(); - } + bitset.andNot(ewahBitmap(other)); return this; } + @Override public CompressedBitmapBuilder xor(Bitmap other) { - if (isSameCompressedBitmap(other)) { - bitset.xor(((CompressedBitmap) other).bitmap); - } else if (isSameCompressedBitmapBuilder(other)) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - bitset.xor(b.bitset.combine()); - } else { - throw new IllegalArgumentException(); - } + bitset.xor(ewahBitmap(other)); return this; } /** @return the fully built immutable bitmap */ + @Override public CompressedBitmap build() { - return new CompressedBitmap(bitset.combine()); + return new CompressedBitmap(bitset.combine(), bitmapIndex); } + @Override public Iterator<BitmapObject> iterator() { return build().iterator(); } + @Override public int cardinality() { return bitset.combine().cardinality(); } + @Override public boolean removeAllOrNone(PackBitmapIndex index) { - if (!packIndex.equals(index)) + if (!bitmapIndex.packIndex.equals(index)) return false; EWAHCompressedBitmap curr = bitset.combine() - .xor(ones(indexObjectCount)); + .xor(ones(bitmapIndex.indexObjectCount)); IntIterator ii = curr.intIterator(); - if (ii.hasNext() && ii.next() < indexObjectCount) + if (ii.hasNext() && ii.next() < bitmapIndex.indexObjectCount) return false; bitset = new ComboBitset(curr); return true; } - private BitmapIndexImpl getBitmapIndex() { - return BitmapIndexImpl.this; + @Override + public BitmapIndexImpl getBitmapIndex() { + return bitmapIndex; } - } - final class CompressedBitmap implements Bitmap { - private final EWAHCompressedBitmap bitmap; + private EWAHCompressedBitmap ewahBitmap(Bitmap other) { + if (other instanceof CompressedBitmap) { + CompressedBitmap b = (CompressedBitmap) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitmap; + } + if (other instanceof CompressedBitmapBuilder) { + CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitset.combine(); + } + throw new IllegalArgumentException(); + } + } - private CompressedBitmap(EWAHCompressedBitmap bitmap) { + /** + * Wrapper for a {@link EWAHCompressedBitmap} and {@link PackBitmapIndex}. + * <p> + * For a EWAHCompressedBitmap {@code bitmap} representing a vector of + * bits, {@code new CompressedBitmap(bitmap, bitmapIndex)} represents the + * objects at those positions in {@code bitmapIndex.packIndex}. + */ + public static final class CompressedBitmap implements Bitmap { + final EWAHCompressedBitmap bitmap; + final BitmapIndexImpl bitmapIndex; + + /** + * Construct compressed bitmap for given bitmap and bitmap index + * + * @param bitmap + * @param bitmapIndex + */ + public CompressedBitmap(EWAHCompressedBitmap bitmap, BitmapIndexImpl bitmapIndex) { this.bitmap = bitmap; + this.bitmapIndex = bitmapIndex; } + @Override public CompressedBitmap or(Bitmap other) { - return new CompressedBitmap(bitmap.or(bitmapOf(other))); + return new CompressedBitmap(bitmap.or(ewahBitmap(other)), bitmapIndex); } + @Override public CompressedBitmap andNot(Bitmap other) { - return new CompressedBitmap(bitmap.andNot(bitmapOf(other))); + return new CompressedBitmap(bitmap.andNot(ewahBitmap(other)), bitmapIndex); } + @Override public CompressedBitmap xor(Bitmap other) { - return new CompressedBitmap(bitmap.xor(bitmapOf(other))); - } - - private EWAHCompressedBitmap bitmapOf(Bitmap other) { - if (isSameCompressedBitmap(other)) - return ((CompressedBitmap) other).bitmap; - if (isSameCompressedBitmapBuilder(other)) - return ((CompressedBitmapBuilder) other).build().bitmap; - CompressedBitmapBuilder builder = newBitmapBuilder(); - builder.or(other); - return builder.build().bitmap; + return new CompressedBitmap(bitmap.xor(ewahBitmap(other)), bitmapIndex); } private final IntIterator ofObjectType(int type) { - return packIndex.ofObjectType(bitmap, type).intIterator(); + return bitmapIndex.packIndex.ofObjectType(bitmap, type).intIterator(); } + @Override public Iterator<BitmapObject> iterator() { - final IntIterator dynamic = bitmap.andNot(ones(indexObjectCount)) + final IntIterator dynamic = bitmap.andNot(ones(bitmapIndex.indexObjectCount)) .intIterator(); final IntIterator commits = ofObjectType(Constants.OBJ_COMMIT); final IntIterator trees = ofObjectType(Constants.OBJ_TREE); @@ -365,12 +396,12 @@ public class BitmapIndexImpl implements BitmapIndex { throw new NoSuchElementException(); int position = cached.next(); - if (position < indexObjectCount) { + if (position < bitmapIndex.indexObjectCount) { out.type = type; - out.objectId = packIndex.getObject(position); + out.objectId = bitmapIndex.packIndex.getObject(position); } else { - position -= indexObjectCount; - MutableEntry entry = mutableIndex.getObject(position); + position -= bitmapIndex.indexObjectCount; + MutableEntry entry = bitmapIndex.mutableIndex.getObject(position); out.type = entry.type; out.objectId = entry; } @@ -387,8 +418,22 @@ public class BitmapIndexImpl implements BitmapIndex { return bitmap; } - private BitmapIndexImpl getPackBitmapIndex() { - return BitmapIndexImpl.this; + private EWAHCompressedBitmap ewahBitmap(Bitmap other) { + if (other instanceof CompressedBitmap) { + CompressedBitmap b = (CompressedBitmap) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitmap; + } + if (other instanceof CompressedBitmapBuilder) { + CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; + if (b.bitmapIndex != bitmapIndex) { + throw new IllegalArgumentException(); + } + return b.bitset.combine(); + } + throw new IllegalArgumentException(); } } @@ -419,7 +464,7 @@ public class BitmapIndexImpl implements BitmapIndex { } } - int addObject(AnyObjectId objectId, int type) { + int findOrInsert(AnyObjectId objectId, int type) { MutableEntry entry = new MutableEntry( objectId, type, revList.size()); revList.add(entry); @@ -429,9 +474,9 @@ public class BitmapIndexImpl implements BitmapIndex { } private static final class MutableEntry extends ObjectIdOwnerMap.Entry { - private final int type; + final int type; - private final int position; + final int position; MutableEntry(AnyObjectId objectId, int type, int position) { super(objectId); @@ -456,23 +501,7 @@ public class BitmapIndexImpl implements BitmapIndex { } } - private boolean isSameCompressedBitmap(Bitmap other) { - if (other instanceof CompressedBitmap) { - CompressedBitmap b = (CompressedBitmap) other; - return this == b.getPackBitmapIndex(); - } - return false; - } - - private boolean isSameCompressedBitmapBuilder(Bitmap other) { - if (other instanceof CompressedBitmapBuilder) { - CompressedBitmapBuilder b = (CompressedBitmapBuilder) other; - return this == b.getBitmapIndex(); - } - return false; - } - - private static final EWAHCompressedBitmap ones(int sizeInBits) { + static final EWAHCompressedBitmap ones(int sizeInBits) { EWAHCompressedBitmap mask = new EWAHCompressedBitmap(); mask.addStreamOfEmptyWords( true, sizeInBits / EWAHCompressedBitmap.wordinbits); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java index 2f30496e2f..a95dea74b6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/DeltaBaseCache.java @@ -50,7 +50,7 @@ import org.eclipse.jgit.storage.file.WindowCacheConfig; class DeltaBaseCache { private static final int CACHE_SZ = 1024; - private static final SoftReference<Entry> DEAD; + static final SoftReference<Entry> DEAD; private static int hash(final long position) { return (((int) position) << 22) >>> 22; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index c5723c0594..e7005c247e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -877,6 +877,11 @@ public class GC { */ public long numberOfPackedRefs; + /** + * The number of bitmaps in the bitmap indices. + */ + public long numberOfBitmaps; + public String toString() { final StringBuilder b = new StringBuilder(); b.append("numberOfPackedObjects=").append(numberOfPackedObjects); //$NON-NLS-1$ @@ -886,15 +891,15 @@ public class GC { b.append(", numberOfPackedRefs=").append(numberOfPackedRefs); //$NON-NLS-1$ b.append(", sizeOfLooseObjects=").append(sizeOfLooseObjects); //$NON-NLS-1$ b.append(", sizeOfPackedObjects=").append(sizeOfPackedObjects); //$NON-NLS-1$ + b.append(", numberOfBitmaps=").append(numberOfBitmaps); //$NON-NLS-1$ return b.toString(); } } /** - * Returns the number of objects stored in pack files. If an object is - * contained in multiple pack files it is counted as often as it occurs. + * Returns information about objects and pack files for a FileRepository. * - * @return the number of objects stored in pack files + * @return information about objects and pack files for a FileRepository * @throws IOException */ public RepoStatistics getStatistics() throws IOException { @@ -904,6 +909,8 @@ public class GC { ret.numberOfPackedObjects += f.getIndex().getObjectCount(); ret.numberOfPackFiles++; ret.sizeOfPackedObjects += f.getPackFile().length(); + if (f.getBitmapIndex() != null) + ret.numberOfBitmaps += f.getBitmapIndex().getBitmapCount(); } File objDir = repo.getObjectsDirectory(); String[] fanout = objDir.list(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java index 06eb42cbbc..50297a97a0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java @@ -120,11 +120,11 @@ public class LockFile { private boolean haveLck; - private FileOutputStream os; + FileOutputStream os; private boolean needSnapshot; - private boolean fsync; + boolean fsync; private FileSnapshot commitSnapshot; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java index ae4de84a07..e743cb4aff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java @@ -193,4 +193,11 @@ public abstract class PackBitmapIndex { * pack that this index was generated from. */ public abstract int getObjectCount(); + + /** + * Returns the number of bitmaps in this bitmap index. + * + * @return the number of bitmaps in this bitmap index. + */ + public abstract int getBitmapCount(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java index 93f891864a..4ff09a148f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java @@ -74,9 +74,9 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { private final EWAHCompressedBitmap blobs; private final EWAHCompressedBitmap tags; private final BlockList<PositionEntry> byOffset; - private final BlockList<StoredBitmap> + final BlockList<StoredBitmap> byAddOrder = new BlockList<StoredBitmap>(); - private final ObjectIdOwnerMap<PositionEntry> + final ObjectIdOwnerMap<PositionEntry> positionEntries = new ObjectIdOwnerMap<PositionEntry>(); /** @@ -253,7 +253,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { return PackBitmapIndexV1.OPT_FULL; } - /** @return the number of bitmaps. */ + @Override public int getBitmapCount() { return getBitmaps().size(); } @@ -330,7 +330,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { private final int xorOffset; private final int flags; - private StoredEntry(long objectId, EWAHCompressedBitmap bitmap, + StoredEntry(long objectId, EWAHCompressedBitmap bitmap, int xorOffset, int flags) { this.objectId = objectId; this.bitmap = bitmap; @@ -360,11 +360,11 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex { } private static final class PositionEntry extends ObjectIdOwnerMap.Entry { - private final int namePosition; + final int namePosition; - private int offsetPosition; + int offsetPosition; - private PositionEntry(AnyObjectId objectId, int namePosition) { + PositionEntry(AnyObjectId objectId, int namePosition) { super(objectId); this.namePosition = namePosition; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java index 6b96b07ed1..7cd68b625e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java @@ -66,7 +66,7 @@ public class PackBitmapIndexRemapper extends PackBitmapIndex implements Iterable<PackBitmapIndexRemapper.Entry> { private final BasePackBitmapIndex oldPackIndex; - private final PackBitmapIndex newPackIndex; + final PackBitmapIndex newPackIndex; private final ObjectIdOwnerMap<StoredBitmap> convertedBitmaps; private final BitSet inflated; private final int[] prevToNewMapping; @@ -199,7 +199,7 @@ public class PackBitmapIndexRemapper extends PackBitmapIndex public final class Entry extends ObjectId { private final int flags; - private Entry(AnyObjectId src, int flags) { + Entry(AnyObjectId src, int flags) { super(src); this.flags = flags; } @@ -209,4 +209,10 @@ public class PackBitmapIndexRemapper extends PackBitmapIndex return flags; } } + + @Override + public int getBitmapCount() { + // The count is only useful for the end index, not the remapper. + return 0; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java index a38a26dec0..a7ab00db2d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java @@ -213,6 +213,11 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex { } @Override + public int getBitmapCount() { + return bitmaps.size(); + } + + @Override public boolean equals(Object o) { // TODO(cranger): compare the pack checksum? if (o instanceof PackBitmapIndexV1) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java index 589a811735..b385b8ab73 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java @@ -119,7 +119,7 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> { private int activeCopyRawData; - private int packLastModified; + int packLastModified; private volatile boolean invalid; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java index ab3297ad2a..e5a729dbf3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java @@ -67,7 +67,7 @@ class PackIndexV1 extends PackIndex { private final long[] idxHeader; - private byte[][] idxdata; + byte[][] idxdata; private long objectCnt; @@ -233,9 +233,9 @@ class PackIndexV1 extends PackIndex { } private class IndexV1Iterator extends EntriesIterator { - private int levelOne; + int levelOne; - private int levelTwo; + int levelTwo; @Override protected MutableEntry initEntry() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java index cb8c91a5a7..d87336f99c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java @@ -75,16 +75,16 @@ class PackIndexV2 extends PackIndex { private final long[] fanoutTable; /** 256 arrays of contiguous object names. */ - private int[][] names; + int[][] names; /** 256 arrays of the 32 bit offset data, matching {@link #names}. */ - private byte[][] offset32; + byte[][] offset32; /** 256 arrays of the CRC-32 of objects, matching {@link #names}. */ private byte[][] crc32; /** 64 bit offset table. */ - private byte[] offset64; + byte[] offset64; PackIndexV2(final InputStream fd) throws IOException { final byte[] fanoutRaw = new byte[4 * FANOUT]; @@ -304,9 +304,9 @@ class PackIndexV2 extends PackIndex { } private class EntriesIteratorV2 extends EntriesIterator { - private int levelOne; + int levelOne; - private int levelTwo; + int levelTwo; @Override protected MutableEntry initEntry() { 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 bb5b044405..7851678a88 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 @@ -138,7 +138,7 @@ public class RefDirectory extends RefDatabase { private final File gitDir; - private final File refsDir; + final File refsDir; private final ReflogWriter logWriter; @@ -155,7 +155,7 @@ public class RefDirectory extends RefDatabase { private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<RefList<LooseRef>>(); /** Immutable sorted list of packed references. */ - private final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>(); + final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>(); /** * Number of modifications made to this database. @@ -925,7 +925,7 @@ public class RefDirectory extends RefDatabase { return n; } - private LooseRef scanRef(LooseRef ref, String name) throws IOException { + LooseRef scanRef(LooseRef ref, String name) throws IOException { final File path = fileFor(name); FileSnapshot currentSnapshot = null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java index e4cc697962..2cc2563962 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java @@ -232,7 +232,7 @@ public class UnpackedObject { } } - private static void checkValidEndOfStream(InputStream in, Inflater inf, + static void checkValidEndOfStream(InputStream in, Inflater inf, AnyObjectId id, final byte[] buf) throws IOException, CorruptObjectException { for (;;) { @@ -266,7 +266,7 @@ public class UnpackedObject { } } - private static boolean isStandardFormat(final byte[] hdr) { + static boolean isStandardFormat(final byte[] hdr) { /* * We must determine if the buffer contains the standard * zlib-deflated stream or the experimental format based @@ -298,7 +298,7 @@ public class UnpackedObject { return (fb & 0x8f) == 0x08 && (((fb << 8) | hdr[1] & 0xff) % 31) == 0; } - private static InputStream inflate(final InputStream in, final long size, + static InputStream inflate(final InputStream in, final long size, final ObjectId id) { final Inflater inf = InflaterCache.get(); return new InflaterInputStream(in, inf) { @@ -334,11 +334,11 @@ public class UnpackedObject { return new InflaterInputStream(in, inf, BUFFER_SIZE); } - private static BufferedInputStream buffer(InputStream in) { + static BufferedInputStream buffer(InputStream in) { return new BufferedInputStream(in, BUFFER_SIZE); } - private static int readSome(InputStream in, final byte[] hdr, int off, + static int readSome(InputStream in, final byte[] hdr, int off, int cnt) throws IOException { int avail = 0; while (0 < cnt) { @@ -363,7 +363,7 @@ public class UnpackedObject { private final FileObjectDatabase source; - private LargeObject(int type, long size, File path, AnyObjectId id, + LargeObject(int type, long size, File path, AnyObjectId id, FileObjectDatabase db) { this.type = type; this.size = size; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java index 8ea0c23eaf..42927426b9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java @@ -73,7 +73,7 @@ final class DeltaTask implements Callable<Object> { final int endIndex; private long totalWeight; - private long bytesPerUnit; + long bytesPerUnit; Block(int threads, PackConfig config, ObjectReader reader, DeltaCache dc, ThreadSafeProgressMonitor pm, @@ -110,10 +110,12 @@ final class DeltaTask implements Callable<Object> { maxWork = s.size(); } } - if (maxTask == null) + if (maxTask == null) { return null; - if (maxTask.tryStealWork(maxSlice)) + } + if (maxTask.tryStealWork(maxSlice)) { return forThread.initWindow(maxSlice); + } } } @@ -138,26 +140,30 @@ final class DeltaTask implements Callable<Object> { for (; w < weightPerThread && i < endIndex;) { if (nextTop < topPaths.size() && i == topPaths.get(nextTop).slice.beginIndex) { - if (s < i) + if (s < i) { task.add(new Slice(s, i)); + } s = i = topPaths.get(nextTop++).slice.endIndex; - } else - w += list[i++].getWeight(); + } else { + w += getAdjustedWeight(list[i++]); + } } // Round up the slice to the end of a path. if (s < i) { int h = list[i - 1].getPathHash(); while (i < endIndex) { - if (h == list[i].getPathHash()) + if (h == list[i].getPathHash()) { i++; - else + } else { break; + } } task.add(new Slice(s, i)); } - if (!task.slices.isEmpty()) + if (!task.slices.isEmpty()) { tasks.add(task); + } } while (topPathItr.hasNext()) { WeightedPath p = topPathItr.next(); @@ -174,8 +180,8 @@ final class DeltaTask implements Callable<Object> { threads); int cp = beginIndex; int ch = list[cp].getPathHash(); - long cw = list[cp].getWeight(); - totalWeight = list[cp].getWeight(); + long cw = getAdjustedWeight(list[cp]); + totalWeight = cw; for (int i = cp + 1; i < endIndex; i++) { ObjectToPack o = list[i]; @@ -184,24 +190,25 @@ final class DeltaTask implements Callable<Object> { if (topPaths.size() < threads) { Slice s = new Slice(cp, i); topPaths.add(new WeightedPath(cw, s)); - if (topPaths.size() == threads) + if (topPaths.size() == threads) { Collections.sort(topPaths); + } } else if (topPaths.get(0).weight < cw) { Slice s = new Slice(cp, i); WeightedPath p = new WeightedPath(cw, s); topPaths.set(0, p); - if (p.compareTo(topPaths.get(1)) > 0) + if (p.compareTo(topPaths.get(1)) > 0) { Collections.sort(topPaths); + } } } cp = i; ch = o.getPathHash(); cw = 0; } - if (o.isEdge() || o.doNotAttemptDelta()) - continue; - cw += o.getWeight(); - totalWeight += o.getWeight(); + int weight = getAdjustedWeight(o); + cw += weight; + totalWeight += weight; } // Sort by starting index to identify gaps later. @@ -212,12 +219,22 @@ final class DeltaTask implements Callable<Object> { }); bytesPerUnit = 1; - while (MAX_METER <= (totalWeight / bytesPerUnit)) + while (MAX_METER <= (totalWeight / bytesPerUnit)) { bytesPerUnit <<= 10; + } return topPaths; } } + static int getAdjustedWeight(ObjectToPack o) { + // Edge objects and those with reused deltas do not need to be + // compressed. For compression calculations, ignore their weights. + if (o.isEdge() || o.doNotAttemptDelta()) { + return 0; + } + return o.getWeight(); + } + static final class WeightedPath implements Comparable<WeightedPath> { final long weight; final Slice slice; @@ -229,8 +246,9 @@ final class DeltaTask implements Callable<Object> { public int compareTo(WeightedPath o) { int cmp = Long.signum(weight - o.weight); - if (cmp != 0) + if (cmp != 0) { return cmp; + } return slice.beginIndex - o.slice.beginIndex; } } @@ -250,7 +268,7 @@ final class DeltaTask implements Callable<Object> { } private final Block block; - private final LinkedList<Slice> slices; + final LinkedList<Slice> slices; private ObjectReader or; private DeltaWindow dw; @@ -278,14 +296,16 @@ final class DeltaTask implements Callable<Object> { DeltaWindow w; for (;;) { synchronized (this) { - if (slices.isEmpty()) + if (slices.isEmpty()) { break; + } w = initWindow(slices.removeFirst()); } runWindow(w); } - while ((w = block.stealWork(this)) != null) + while ((w = block.stealWork(this)) != null) { runWindow(w); + } } finally { block.pm.endWorker(); or.close(); @@ -315,8 +335,9 @@ final class DeltaTask implements Callable<Object> { } synchronized Slice remaining() { - if (!slices.isEmpty()) + if (!slices.isEmpty()) { return slices.getLast(); + } DeltaWindow d = dw; return d != null ? d.remaining() : null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 683d1cd6e9..19b6b080da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -218,7 +218,7 @@ public class PackWriter implements AutoCloseable { } @SuppressWarnings("unchecked") - private BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1]; + BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1]; { objectsLists[OBJ_COMMIT] = new BlockList<ObjectToPack>(); objectsLists[OBJ_TREE] = new BlockList<ObjectToPack>(); @@ -249,7 +249,7 @@ public class PackWriter implements AutoCloseable { /** {@link #reader} recast to the reuse interface, if it supports it. */ private final ObjectReuseAsIs reuseSupport; - private final PackConfig config; + final PackConfig config; private final PackStatistics.Accumulator stats; @@ -1306,8 +1306,7 @@ public class PackWriter implements AutoCloseable { long totalWeight = 0; for (int i = 0; i < cnt; i++) { ObjectToPack o = list[i]; - if (!o.isEdge() && !o.doNotAttemptDelta()) - totalWeight += o.getWeight(); + totalWeight += DeltaTask.getAdjustedWeight(o); } long bytesPerUnit = 1; @@ -2015,10 +2014,10 @@ public class PackWriter implements AutoCloseable { byName = null; PackWriterBitmapPreparer bitmapPreparer = new PackWriterBitmapPreparer( - reader, writeBitmaps, pm, stats.interestingObjects); + reader, writeBitmaps, pm, stats.interestingObjects, config); Collection<PackWriterBitmapPreparer.BitmapCommit> selectedCommits = - bitmapPreparer.doCommitSelection(numCommits); + bitmapPreparer.selectCommits(numCommits); beginPhase(PackingPhase.BUILDING_BITMAPS, pm, selectedCommits.size()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java index 756d4b0005..77311abaa5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.internal.storage.pack; import static org.eclipse.jgit.internal.storage.file.PackBitmapIndex.FLAG_REUSE; +import static org.eclipse.jgit.revwalk.RevFlag.SEEN; import java.io.IOException; import java.util.ArrayList; @@ -55,34 +56,44 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import com.googlecode.javaewah.EWAHCompressedBitmap; - import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl; +import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap; import org.eclipse.jgit.internal.storage.file.PackBitmapIndex; import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; import org.eclipse.jgit.internal.storage.file.PackBitmapIndexRemapper; +import org.eclipse.jgit.internal.storage.pack.PackWriterBitmapWalker.AddUnseenToBitmapFilter; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; -import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.util.BlockList; +import org.eclipse.jgit.util.SystemReader; -/** Helper class for the PackWriter to select commits for pack index bitmaps. */ +import com.googlecode.javaewah.EWAHCompressedBitmap; + +/** + * Helper class for the {@link PackWriter} to select commits for which to build + * pack index bitmaps. + */ class PackWriterBitmapPreparer { - private static final Comparator<BitmapBuilder> BUILDER_BY_CARDINALITY_DSC = - new Comparator<BitmapBuilder>() { - public int compare(BitmapBuilder a, BitmapBuilder b) { - return Integer.signum(b.cardinality() - a.cardinality()); + private static final int DAY_IN_SECONDS = 24 * 60 * 60; + + private static final Comparator<BitmapBuilderEntry> ORDER_BY_CARDINALITY = new Comparator<BitmapBuilderEntry>() { + public int compare(BitmapBuilderEntry a, BitmapBuilderEntry b) { + return Integer.signum(a.getBuilder().cardinality() + - b.getBuilder().cardinality()); } }; @@ -93,12 +104,18 @@ class PackWriterBitmapPreparer { private final BitmapIndexImpl commitBitmapIndex; private final PackBitmapIndexRemapper bitmapRemapper; private final BitmapIndexImpl bitmapIndex; - private final int minCommits = 100; - private final int maxCommits = 5000; + + private final int contiguousCommitCount; + private final int recentCommitCount; + private final int recentCommitSpan; + private final int distantCommitSpan; + private final int excessiveBranchCount; + private final long inactiveBranchTimestamp; PackWriterBitmapPreparer(ObjectReader reader, PackBitmapIndexBuilder writeBitmaps, ProgressMonitor pm, - Set<? extends ObjectId> want) throws IOException { + Set<? extends ObjectId> want, PackConfig config) + throws IOException { this.reader = reader; this.writeBitmaps = writeBitmaps; this.pm = pm; @@ -107,208 +124,379 @@ class PackWriterBitmapPreparer { this.bitmapRemapper = PackBitmapIndexRemapper.newPackBitmapIndex( reader.getBitmapIndex(), writeBitmaps); this.bitmapIndex = new BitmapIndexImpl(bitmapRemapper); + this.contiguousCommitCount = config.getBitmapContiguousCommitCount(); + this.recentCommitCount = config.getBitmapRecentCommitCount(); + this.recentCommitSpan = config.getBitmapRecentCommitSpan(); + this.distantCommitSpan = config.getBitmapDistantCommitSpan(); + this.excessiveBranchCount = config.getBitmapExcessiveBranchCount(); + long now = SystemReader.getInstance().getCurrentTime(); + long ageInSeconds = config.getBitmapInactiveBranchAgeInDays() + * DAY_IN_SECONDS; + this.inactiveBranchTimestamp = (now / 1000) - ageInSeconds; } - Collection<BitmapCommit> doCommitSelection(int expectedNumCommits) - throws MissingObjectException, IncorrectObjectTypeException, - IOException { + /** + * Returns the commit objects for which bitmap indices should be built. + * + * @param expectedCommitCount + * count of commits in the pack + * @return commit objects for which bitmap indices should be built + * @throws IncorrectObjectTypeException + * if any of the processed objects is not a commit + * @throws IOException + * on errors reading pack or index files + * @throws MissingObjectException + * if an expected object is missing + */ + Collection<BitmapCommit> selectCommits(int expectedCommitCount) + throws IncorrectObjectTypeException, IOException, + MissingObjectException { + /* + * Thinking of bitmap indices as a cache, if we find bitmaps at or at a + * close ancestor to 'old' and 'new' when calculating old..new, then all + * objects can be calculated with minimal graph walking. A distribution + * that favors creating bitmaps for the most recent commits maximizes + * the cache hits for clients that are close to HEAD, which is the + * majority of calculations performed. + */ pm.beginTask(JGitText.get().selectingCommits, ProgressMonitor.UNKNOWN); RevWalk rw = new RevWalk(reader); rw.setRetainBody(false); - WalkResult result = findPaths(rw, expectedNumCommits); + CommitSelectionHelper selectionHelper = setupTipCommitBitmaps(rw, + expectedCommitCount); pm.endTask(); - int totCommits = result.commitsByOldest.length - result.commitStartPos; + int totCommits = selectionHelper.getCommitCount(); BlockList<BitmapCommit> selections = new BlockList<BitmapCommit>( - totCommits / minCommits + 1); - for (BitmapCommit reuse : result.reuse) + totCommits / recentCommitSpan + 1); + for (BitmapCommit reuse : selectionHelper.reusedCommits) { selections.add(reuse); + } if (totCommits == 0) { - for (AnyObjectId id : result.peeledWant) + for (AnyObjectId id : selectionHelper.peeledWants) { selections.add(new BitmapCommit(id, false, 0)); + } return selections; } pm.beginTask(JGitText.get().selectingCommits, totCommits); - - for (BitmapBuilder bitmapableCommits : result.paths) { - int cardinality = bitmapableCommits.cardinality(); - - List<List<BitmapCommit>> running = new ArrayList< - List<BitmapCommit>>(); + int totalWants = selectionHelper.peeledWants.size(); + + for (BitmapBuilderEntry entry : selectionHelper.tipCommitBitmaps) { + BitmapBuilder bitmap = entry.getBuilder(); + int cardinality = bitmap.cardinality(); + + // Within this branch, keep ordered lists of commits representing + // chains in its history, where each chain is a "sub-branch". + // Ordering commits by these chains makes for fewer differences + // between consecutive selected commits, which in turn provides + // better compression/on the run-length encoding of the XORs between + // them. + List<List<BitmapCommit>> chains = + new ArrayList<List<BitmapCommit>>(); + + // Mark the current branch as inactive if its tip commit isn't + // recent and there are an excessive number of branches, to + // prevent memory bloat of computing too many bitmaps for stale + // branches. + boolean isActiveBranch = true; + if (totalWants > excessiveBranchCount + && !isRecentCommit(entry.getCommit())) { + isActiveBranch = false; + } // Insert bitmaps at the offsets suggested by the - // nextSelectionDistance() heuristic. + // nextSelectionDistance() heuristic. Only reuse bitmaps created + // for more distant commits. int index = -1; - int nextIn = nextSelectionDistance(0, cardinality); - int nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0; - boolean mustPick = nextIn == 0; - for (RevCommit c : result) { - if (!bitmapableCommits.contains(c)) + int nextIn = nextSpan(cardinality); + int nextFlg = nextIn == distantCommitSpan + ? PackBitmapIndex.FLAG_REUSE : 0; + + // For the current branch, iterate through all commits from oldest + // to newest. + for (RevCommit c : selectionHelper) { + // Optimization: if we have found all the commits for this + // branch, stop searching + int distanceFromTip = cardinality - index - 1; + if (distanceFromTip == 0) { + break; + } + + // Ignore commits that are not in this branch + if (!bitmap.contains(c)) { continue; + } index++; nextIn--; pm.update(1); - // Always pick the items in want and prefer merge commits. - if (result.peeledWant.remove(c)) { - if (nextIn > 0) + // Always pick the items in wants, prefer merge commits. + if (selectionHelper.peeledWants.remove(c)) { + if (nextIn > 0) { nextFlg = 0; - } else if (!mustPick && ((nextIn > 0) - || (c.getParentCount() <= 1 && nextIn > -minCommits))) { - continue; + } + } else { + boolean stillInSpan = nextIn >= 0; + boolean isMergeCommit = c.getParentCount() > 1; + // Force selection if: + // a) we have exhausted the window looking for merges + // b) we are in the top commits of an active branch + // c) we are at a branch tip + boolean mustPick = (nextIn <= -recentCommitSpan) + || (isActiveBranch + && (distanceFromTip <= contiguousCommitCount)) + || (distanceFromTip == 1); // most recent commit + if (!mustPick && (stillInSpan || !isMergeCommit)) { + continue; + } } + // This commit is selected. + // Calculate where to look for the next one. int flags = nextFlg; - nextIn = nextSelectionDistance(index, cardinality); - nextFlg = nextIn == maxCommits ? PackBitmapIndex.FLAG_REUSE : 0; - mustPick = nextIn == 0; + nextIn = nextSpan(distanceFromTip); + nextFlg = nextIn == distantCommitSpan + ? PackBitmapIndex.FLAG_REUSE : 0; BitmapBuilder fullBitmap = commitBitmapIndex.newBitmapBuilder(); rw.reset(); rw.markStart(c); - for (AnyObjectId objectId : result.reuse) - rw.markUninteresting(rw.parseCommit(objectId)); - rw.setRevFilter( - PackWriterBitmapWalker.newRevFilter(null, fullBitmap)); + rw.setRevFilter(new AddUnseenToBitmapFilter( + selectionHelper.reusedCommitsBitmap, fullBitmap)); while (rw.next() != null) { - // Work is done in the RevFilter. + // The RevFilter adds the reachable commits from this + // selected commit to fullBitmap. } - List<List<BitmapCommit>> matches = new ArrayList< - List<BitmapCommit>>(); - for (List<BitmapCommit> list : running) { - BitmapCommit last = list.get(list.size() - 1); - if (fullBitmap.contains(last)) - matches.add(list); + // Sort the commits by independent chains in this branch's + // history, yielding better compression when building bitmaps. + List<BitmapCommit> longestAncestorChain = null; + for (List<BitmapCommit> chain : chains) { + BitmapCommit mostRecentCommit = chain.get(chain.size() - 1); + if (fullBitmap.contains(mostRecentCommit)) { + if (longestAncestorChain == null + || longestAncestorChain.size() < chain.size()) { + longestAncestorChain = chain; + } + } } - List<BitmapCommit> match; - if (matches.isEmpty()) { - match = new ArrayList<BitmapCommit>(); - running.add(match); - } else { - match = matches.get(0); - // Append to longest - for (List<BitmapCommit> list : matches) { - if (list.size() > match.size()) - match = list; - } + if (longestAncestorChain == null) { + longestAncestorChain = new ArrayList<BitmapCommit>(); + chains.add(longestAncestorChain); } - match.add(new BitmapCommit(c, !match.isEmpty(), flags)); + longestAncestorChain.add(new BitmapCommit( + c, !longestAncestorChain.isEmpty(), flags)); writeBitmaps.addBitmap(c, fullBitmap, 0); } - for (List<BitmapCommit> list : running) - selections.addAll(list); + for (List<BitmapCommit> chain : chains) { + selections.addAll(chain); + } } writeBitmaps.clearBitmaps(); // Remove the temporary commit bitmaps. // Add the remaining peeledWant - for (AnyObjectId remainingWant : result.peeledWant) + for (AnyObjectId remainingWant : selectionHelper.peeledWants) { selections.add(new BitmapCommit(remainingWant, false, 0)); + } pm.endTask(); return selections; } - private WalkResult findPaths(RevWalk rw, int expectedNumCommits) - throws MissingObjectException, IOException { - BitmapBuilder reuseBitmap = commitBitmapIndex.newBitmapBuilder(); - List<BitmapCommit> reuse = new ArrayList<BitmapCommit>(); + private boolean isRecentCommit(RevCommit revCommit) { + return revCommit.getCommitTime() > inactiveBranchTimestamp; + } + + /** + * A RevFilter that excludes the commits named in a bitmap from the walk. + * <p> + * If a commit is in {@code bitmap} then that commit is not emitted by the + * walk and its parents are marked as SEEN so the walk can skip them. The + * bitmaps passed in have the property that the parents of any commit in + * {@code bitmap} are also in {@code bitmap}, so marking the parents as + * SEEN speeds up the RevWalk by saving it from walking down blind alleys + * and does not change the commits emitted. + */ + private static class NotInBitmapFilter extends RevFilter { + private final BitmapBuilder bitmap; + + NotInBitmapFilter(BitmapBuilder bitmap) { + this.bitmap = bitmap; + } + + @Override + public final boolean include(RevWalk rw, RevCommit c) { + if (!bitmap.contains(c)) { + return true; + } + for (RevCommit p : c.getParents()) { + p.add(SEEN); + } + return false; + } + + @Override + public final NotInBitmapFilter clone() { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean requiresCommitBody() { + return false; + } + } + + /** + * For each of the {@code want}s, which represent the tip commit of each + * branch, set up an initial {@link BitmapBuilder}. Reuse previously built + * bitmaps if possible. + * + * @param rw + * a {@link RevWalk} to find reachable objects in this repository + * @param expectedCommitCount + * expected count of commits. The actual count may be less due to + * unreachable garbage. + * @return a {@link CommitSelectionHelper} containing bitmaps for the tip + * commits + * @throws IncorrectObjectTypeException + * if any of the processed objects is not a commit + * @throws IOException + * on errors reading pack or index files + * @throws MissingObjectException + * if an expected object is missing + */ + private CommitSelectionHelper setupTipCommitBitmaps(RevWalk rw, + int expectedCommitCount) throws IncorrectObjectTypeException, + IOException, MissingObjectException { + BitmapBuilder reuse = commitBitmapIndex.newBitmapBuilder(); + List<BitmapCommit> reuseCommits = new ArrayList<BitmapCommit>(); for (PackBitmapIndexRemapper.Entry entry : bitmapRemapper) { - if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) + // More recent commits did not have the reuse flag set, so skip them + if ((entry.getFlags() & FLAG_REUSE) != FLAG_REUSE) { continue; - + } RevObject ro = rw.peel(rw.parseAny(entry)); - if (ro instanceof RevCommit) { - RevCommit rc = (RevCommit) ro; - reuse.add(new BitmapCommit(rc, false, entry.getFlags())); - rw.markUninteresting(rc); + if (!(ro instanceof RevCommit)) { + continue; + } + RevCommit rc = (RevCommit) ro; + reuseCommits.add(new BitmapCommit(rc, false, entry.getFlags())); + if (!reuse.contains(rc)) { EWAHCompressedBitmap bitmap = bitmapRemapper.ofObjectType( bitmapRemapper.getBitmap(rc), Constants.OBJ_COMMIT); - writeBitmaps.addBitmap(rc, bitmap, 0); - reuseBitmap.add(rc, Constants.OBJ_COMMIT); + reuse.or(new CompressedBitmap(bitmap, commitBitmapIndex)); } } - writeBitmaps.clearBitmaps(); // Remove temporary bitmaps - // Do a RevWalk by commit time descending. Keep track of all the paths - // from the wants. - List<BitmapBuilder> paths = new ArrayList<BitmapBuilder>(want.size()); + // Add branch tips that are not represented in old bitmap indices. Set + // up the RevWalk to walk the new commits not in the old packs. + List<BitmapBuilderEntry> tipCommitBitmaps = new ArrayList<BitmapBuilderEntry>( + want.size()); Set<RevCommit> peeledWant = new HashSet<RevCommit>(want.size()); for (AnyObjectId objectId : want) { RevObject ro = rw.peel(rw.parseAny(objectId)); - if (ro instanceof RevCommit && !reuseBitmap.contains(ro)) { - RevCommit rc = (RevCommit) ro; - peeledWant.add(rc); - rw.markStart(rc); - - BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder(); - bitmap.or(reuseBitmap); - bitmap.add(rc, Constants.OBJ_COMMIT); - paths.add(bitmap); + if (!(ro instanceof RevCommit) || reuse.contains(ro)) { + continue; } + + RevCommit rc = (RevCommit) ro; + peeledWant.add(rc); + rw.markStart(rc); + + BitmapBuilder bitmap = commitBitmapIndex.newBitmapBuilder(); + bitmap.addObject(rc, Constants.OBJ_COMMIT); + tipCommitBitmaps.add(new BitmapBuilderEntry(rc, bitmap)); } - // Update the paths from the wants and create a list of commits in - // reverse iteration order. - RevCommit[] commits = new RevCommit[expectedNumCommits]; + // Create a list of commits in reverse order (older to newer). + // For each branch that contains the commit, mark its parents as being + // in the bitmap. + rw.setRevFilter(new NotInBitmapFilter(reuse)); + RevCommit[] commits = new RevCommit[expectedCommitCount]; int pos = commits.length; RevCommit rc; - while ((rc = rw.next()) != null) { + while ((rc = rw.next()) != null && pos > 0) { commits[--pos] = rc; - for (BitmapBuilder path : paths) { - if (path.contains(rc)) { - for (RevCommit c : rc.getParents()) - path.add(c, Constants.OBJ_COMMIT); + for (BitmapBuilderEntry entry : tipCommitBitmaps) { + BitmapBuilder bitmap = entry.getBuilder(); + if (!bitmap.contains(rc)) { + continue; + } + for (RevCommit c : rc.getParents()) { + if (reuse.contains(c)) { + continue; + } + bitmap.addObject(c, Constants.OBJ_COMMIT); } } - pm.update(1); } - // Remove the reused bitmaps from the paths - if (!reuse.isEmpty()) - for (BitmapBuilder bitmap : paths) - bitmap.andNot(reuseBitmap); - - // Sort the paths - List<BitmapBuilder> distinctPaths = new ArrayList<BitmapBuilder>(paths.size()); - while (!paths.isEmpty()) { - Collections.sort(paths, BUILDER_BY_CARDINALITY_DSC); - BitmapBuilder largest = paths.remove(0); - distinctPaths.add(largest); + // Sort the tip commit bitmaps. Find the one containing the most + // commits, remove those commits from the remaining bitmaps, resort and + // repeat. + List<BitmapBuilderEntry> orderedTipCommitBitmaps = new ArrayList<>( + tipCommitBitmaps.size()); + while (!tipCommitBitmaps.isEmpty()) { + BitmapBuilderEntry largest = + Collections.max(tipCommitBitmaps, ORDER_BY_CARDINALITY); + tipCommitBitmaps.remove(largest); + orderedTipCommitBitmaps.add(largest); // Update the remaining paths, by removing the objects from // the path that was just added. - for (int i = paths.size() - 1; i >= 0; i--) - paths.get(i).andNot(largest); + for (int i = tipCommitBitmaps.size() - 1; i >= 0; i--) { + tipCommitBitmaps.get(i).getBuilder() + .andNot(largest.getBuilder()); + } } - return new WalkResult(peeledWant, commits, pos, distinctPaths, reuse); + return new CommitSelectionHelper(peeledWant, commits, pos, + orderedTipCommitBitmaps, reuse, reuseCommits); } - private int nextSelectionDistance(int idx, int cardinality) { - if (idx > cardinality) + /*- + * Returns the desired distance to the next bitmap based on the distance + * from the tip commit. Only differentiates recent from distant spans, + * selectCommits() handles the contiguous commits at the tip for active + * or inactive branches. + * + * A graph of this function looks like this, where + * the X axis is the distance from the tip commit and the Y axis is the + * bitmap selection distance. + * + * 5000 ____... + * / + * / + * / + * / + * 100 _____/ + * 0 20100 25000 + * + * Linear scaling between 20100 and 25000 prevents spans >100 for distances + * <20000 (otherwise, a span of 5000 would be returned for a distance of + * 21000, and the range 16000-20000 would have no selections). + */ + int nextSpan(int distanceFromTip) { + if (distanceFromTip < 0) { throw new IllegalArgumentException(); - int idxFromStart = cardinality - idx; - int mustRegionEnd = 100; - if (idxFromStart <= mustRegionEnd) - return 0; + } // Commits more toward the start will have more bitmaps. - int minRegionEnd = 20000; - if (idxFromStart <= minRegionEnd) - return Math.min(idxFromStart - mustRegionEnd, minCommits); + if (distanceFromTip <= recentCommitCount) { + return recentCommitSpan; + } - // Commits more toward the end will have fewer. - int next = Math.min(idxFromStart - minRegionEnd, maxCommits); - return Math.max(next, minCommits); + int next = Math.min(distanceFromTip - recentCommitCount, + distantCommitSpan); + return Math.max(next, recentCommitSpan); } PackWriterBitmapWalker newBitmapWalker() { @@ -316,12 +504,14 @@ class PackWriterBitmapPreparer { new ObjectWalk(reader), bitmapIndex, null); } + /** + * A commit object for which a bitmap index should be built. + */ static final class BitmapCommit extends ObjectId { private final boolean reuseWalker; private final int flags; - private BitmapCommit( - AnyObjectId objectId, boolean reuseWalker, int flags) { + BitmapCommit(AnyObjectId objectId, boolean reuseWalker, int flags) { super(objectId); this.reuseWalker = reuseWalker; this.flags = flags; @@ -336,24 +526,62 @@ class PackWriterBitmapPreparer { } } - private static final class WalkResult implements Iterable<RevCommit> { - private final Set<? extends ObjectId> peeledWant; - private final RevCommit[] commitsByOldest; - private final int commitStartPos; - private final List<BitmapBuilder> paths; - private final Iterable<BitmapCommit> reuse; + /** + * A POJO representing a Pair<RevCommit, BitmapBuidler>. + */ + private static final class BitmapBuilderEntry { + private final RevCommit commit; + private final BitmapBuilder builder; + + BitmapBuilderEntry(RevCommit commit, BitmapBuilder builder) { + this.commit = commit; + this.builder = builder; + } - private WalkResult(Set<? extends ObjectId> peeledWant, + RevCommit getCommit() { + return commit; + } + + BitmapBuilder getBuilder() { + return builder; + } + } + + /** + * Container for state used in the first phase of selecting commits, which + * walks all of the reachable commits via the branch tips ( + * {@code peeledWants}), stores them in {@code commitsByOldest}, and sets up + * bitmaps for each branch tip ({@code tipCommitBitmaps}). + * {@code commitsByOldest} is initialized with an expected size of all + * commits, but may be smaller if some commits are unreachable, in which + * case {@code commitStartPos} will contain a positive offset to the root + * commit. + */ + private static final class CommitSelectionHelper implements Iterable<RevCommit> { + final Set<? extends ObjectId> peeledWants; + final List<BitmapBuilderEntry> tipCommitBitmaps; + + final BitmapBuilder reusedCommitsBitmap; + final Iterable<BitmapCommit> reusedCommits; + final RevCommit[] commitsByOldest; + final int commitStartPos; + + CommitSelectionHelper(Set<? extends ObjectId> peeledWant, RevCommit[] commitsByOldest, int commitStartPos, - List<BitmapBuilder> paths, Iterable<BitmapCommit> reuse) { - this.peeledWant = peeledWant; + List<BitmapBuilderEntry> bitmapEntries, + BitmapBuilder reusedCommitsBitmap, + Iterable<BitmapCommit> reuse) { + this.peeledWants = peeledWant; this.commitsByOldest = commitsByOldest; this.commitStartPos = commitStartPos; - this.paths = paths; - this.reuse = reuse; + this.tipCommitBitmaps = bitmapEntries; + this.reusedCommitsBitmap = reusedCommitsBitmap; + this.reusedCommits = reuse; } public Iterator<RevCommit> iterator() { + // Member variables referenced by this iterator will have synthetic + // accessors generated for them if they are made private. return new Iterator<RevCommit>() { int pos = commitStartPos; @@ -370,5 +598,9 @@ class PackWriterBitmapPreparer { } }; } + + int getCommitCount() { + return commitsByOldest.length - commitStartPos; + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java index debb2f2abc..d9ac9ef16b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapWalker.java @@ -110,21 +110,32 @@ final class PackWriterBitmapWalker { } if (marked) { - BitmapRevFilter filter = newRevFilter(seen, bitmapResult); - walker.setRevFilter(filter); + if (seen == null) { + walker.setRevFilter(new AddToBitmapFilter(bitmapResult)); + } else { + walker.setRevFilter( + new AddUnseenToBitmapFilter(seen, bitmapResult)); + } while (walker.next() != null) { // Iterate through all of the commits. The BitmapRevFilter does // the work. + // + // filter.include returns true for commits that do not have + // a bitmap in bitmapIndex and are not reachable from a + // bitmap in bitmapIndex encountered earlier in the walk. + // Thus the number of commits returned by next() measures how + // much history was traversed without being able to make use + // of bitmaps. pm.update(1); + countOfBitmapIndexMisses++; } RevObject ro; while ((ro = walker.nextObject()) != null) { - bitmapResult.add(ro, ro.getType()); + bitmapResult.addObject(ro, ro.getType()); pm.update(1); } - countOfBitmapIndexMisses += filter.getCountOfLoadedCommits(); } return bitmapResult; @@ -134,53 +145,105 @@ final class PackWriterBitmapWalker { walker.reset(); } - static BitmapRevFilter newRevFilter( - final BitmapBuilder seen, final BitmapBuilder bitmapResult) { - if (seen != null) { - return new BitmapRevFilter() { - protected boolean load(RevCommit cmit) { - if (seen.contains(cmit)) - return false; - return bitmapResult.add(cmit, Constants.OBJ_COMMIT); - } - }; + /** + * A RevFilter that adds the visited commits to {@code bitmap} as a side + * effect. + * <p> + * When the walk hits a commit that is part of {@code bitmap}'s + * BitmapIndex, that entire bitmap is ORed into {@code bitmap} and the + * commit and its parents are marked as SEEN so that the walk does not + * have to visit its ancestors. This ensures the walk is very short if + * there is good bitmap coverage. + */ + static class AddToBitmapFilter extends RevFilter { + private final BitmapBuilder bitmap; + + AddToBitmapFilter(BitmapBuilder bitmap) { + this.bitmap = bitmap; } - return new BitmapRevFilter() { - @Override - protected boolean load(RevCommit cmit) { - return bitmapResult.add(cmit, Constants.OBJ_COMMIT); + + @Override + public final boolean include(RevWalk walker, RevCommit cmit) { + Bitmap visitedBitmap; + + if (bitmap.contains(cmit)) { + // already included + } else if ((visitedBitmap = bitmap.getBitmapIndex() + .getBitmap(cmit)) != null) { + bitmap.or(visitedBitmap); + } else { + bitmap.addObject(cmit, Constants.OBJ_COMMIT); + return true; } - }; - } - static abstract class BitmapRevFilter extends RevFilter { - private long countOfLoadedCommits; + for (RevCommit p : cmit.getParents()) { + p.add(RevFlag.SEEN); + } + return false; + } + + @Override + public final RevFilter clone() { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean requiresCommitBody() { + return false; + } + } - protected abstract boolean load(RevCommit cmit); + /** + * A RevFilter that adds the visited commits to {@code bitmap} as a side + * effect. + * <p> + * When the walk hits a commit that is part of {@code bitmap}'s + * BitmapIndex, that entire bitmap is ORed into {@code bitmap} and the + * commit and its parents are marked as SEEN so that the walk does not + * have to visit its ancestors. This ensures the walk is very short if + * there is good bitmap coverage. + * <p> + * Commits named in {@code seen} are considered already seen. If one is + * encountered, that commit and its parents will be marked with the SEEN + * flag to prevent the walk from visiting its ancestors. + */ + static class AddUnseenToBitmapFilter extends RevFilter { + private final BitmapBuilder seen; + private final BitmapBuilder bitmap; + + AddUnseenToBitmapFilter(BitmapBuilder seen, BitmapBuilder bitmapResult) { + this.seen = seen; + this.bitmap = bitmapResult; + } @Override public final boolean include(RevWalk walker, RevCommit cmit) { - if (load(cmit)) { - countOfLoadedCommits++; + Bitmap visitedBitmap; + + if (seen.contains(cmit) || bitmap.contains(cmit)) { + // already seen or included + } else if ((visitedBitmap = bitmap.getBitmapIndex() + .getBitmap(cmit)) != null) { + bitmap.or(visitedBitmap); + } else { + bitmap.addObject(cmit, Constants.OBJ_COMMIT); return true; } - for (RevCommit p : cmit.getParents()) + + for (RevCommit p : cmit.getParents()) { p.add(RevFlag.SEEN); + } return false; } @Override public final RevFilter clone() { - return this; + throw new UnsupportedOperationException(); } @Override public final boolean requiresCommitBody() { return false; } - - long getCountOfLoadedCommits() { - return countOfLoadedCommits; - } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java index 3c0e2c128e..9ddff25480 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java @@ -125,7 +125,9 @@ public interface BitmapIndex { * @param type * the Git object type. See {@link Constants}. * @return true if the value was not contained or able to be loaded. + * @deprecated use {@link #or} or {@link #addObject} instead. */ + @Deprecated boolean add(AnyObjectId objectId, int type); /** @@ -138,6 +140,18 @@ public interface BitmapIndex { boolean contains(AnyObjectId objectId); /** + * Adds the id to the bitmap. + * + * @param objectId + * the object ID + * @param type + * the Git object type. See {@link Constants}. + * @return the current builder. + * @since 4.2 + */ + BitmapBuilder addObject(AnyObjectId objectId, int type); + + /** * Remove the id from the bitmap. * * @param objectId @@ -190,5 +204,14 @@ public interface BitmapIndex { /** @return the number of elements in the bitmap. */ int cardinality(); + + /** + * Get the BitmapIndex for this BitmapBuilder. + * + * @return the BitmapIndex for this BitmapBuilder + * + * @since 4.2 + */ + BitmapIndex getBitmapIndex(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java index c9b483f7f0..95b16d9176 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java @@ -83,16 +83,16 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> implements * The low {@link #bits} of the SHA-1 are used to select the segment from * this directory. Each segment is constant sized at 2^SEGMENT_BITS. */ - private V[][] directory; + V[][] directory; /** Total number of objects in this map. */ - private int size; + int size; /** The map doubles in capacity when {@link #size} reaches this target. */ private int grow; /** Number of low bits used to form the index into {@link #directory}. */ - private int bits; + int bits; /** Low bit mask to index into {@link #directory}, {@code 2^bits-1}. */ private int mask; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java index 69972dc013..48aa109e7c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java @@ -63,13 +63,13 @@ import java.util.NoSuchElementException; public class ObjectIdSubclassMap<V extends ObjectId> implements Iterable<V> { private static final int INITIAL_TABLE_SIZE = 2048; - private int size; + int size; private int grow; private int mask; - private V[] table; + V[] table; /** Create an empty map. */ public ObjectIdSubclassMap() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java index dc3c772efb..106f9c7796 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java @@ -70,7 +70,7 @@ import org.eclipse.jgit.util.IntList; public class MergeResult<S extends Sequence> implements Iterable<MergeChunk> { private final List<S> sequences; - private final IntList chunks = new IntList(); + final IntList chunks = new IntList(); private boolean containsConflicts = false; @@ -127,7 +127,7 @@ public class MergeResult<S extends Sequence> implements Iterable<MergeChunk> { return sequences; } - private static final ConflictState[] states = ConflictState.values(); + static final ConflictState[] states = ConflictState.values(); /** * @return an iterator over the MergeChunks. The iterator does not support diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java index 1176d958b0..c8504937a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -1295,7 +1295,6 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable { retainOnReset = 0; carryFlags = UNINTERESTING; objects.clear(); - reader.close(); roots.clear(); queue = new DateRevQueue(); pending = new StartGenerator(this); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java index a8835b76c9..d594e97671 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -138,6 +138,65 @@ public class PackConfig { */ public static final boolean DEFAULT_BUILD_BITMAPS = true; + /** + * Default count of most recent commits to select for bitmaps. Only applies + * when bitmaps are enabled: {@value} + * + * @see #setBitmapContiguousCommitCount(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT = 100; + + /** + * Count at which the span between selected commits changes from + * "bitmapRecentCommitSpan" to "bitmapDistantCommitSpan". Only applies when + * bitmaps are enabled: {@value} + * + * @see #setBitmapRecentCommitCount(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_RECENT_COMMIT_COUNT = 20000; + + /** + * Default spacing between commits in recent history when selecting commits + * for bitmaps. Only applies when bitmaps are enabled: {@value} + * + * @see #setBitmapRecentCommitSpan(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_RECENT_COMMIT_SPAN = 100; + + /** + * Default spacing between commits in distant history when selecting commits + * for bitmaps. Only applies when bitmaps are enabled: {@value} + * + * @see #setBitmapDistantCommitSpan(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_DISTANT_COMMIT_SPAN = 5000; + + /** + * Default count of branches required to activate inactive branch commit + * selection. If the number of branches is less than this then bitmaps for + * the entire commit history of all branches will be created, otherwise + * branches marked as "inactive" will have coverage for only partial + * history: {@value} + * + * @see #setBitmapExcessiveBranchCount(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT = 100; + + /** + * Default age at which a branch is considered inactive. Age is taken as the + * number of days ago that the most recent commit was made to a branch. Only + * affects bitmap processing if bitmaps are enabled and the + * "excessive branch count" has been exceeded: {@value} + * + * @see #setBitmapInactiveBranchAgeInDays(int) + * @since 4.2 + */ + public static final int DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS = 90; private int compressionLevel = Deflater.DEFAULT_COMPRESSION; @@ -169,6 +228,18 @@ public class PackConfig { private boolean buildBitmaps = DEFAULT_BUILD_BITMAPS; + private int bitmapContiguousCommitCount = DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT; + + private int bitmapRecentCommitCount = DEFAULT_BITMAP_RECENT_COMMIT_COUNT; + + private int bitmapRecentCommitSpan = DEFAULT_BITMAP_RECENT_COMMIT_SPAN; + + private int bitmapDistantCommitSpan = DEFAULT_BITMAP_DISTANT_COMMIT_SPAN; + + private int bitmapExcessiveBranchCount = DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT; + + private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS; + private boolean cutDeltaChains; /** Create a default configuration. */ @@ -222,6 +293,12 @@ public class PackConfig { this.executor = cfg.executor; this.indexVersion = cfg.indexVersion; this.buildBitmaps = cfg.buildBitmaps; + this.bitmapContiguousCommitCount = cfg.bitmapContiguousCommitCount; + this.bitmapRecentCommitCount = cfg.bitmapRecentCommitCount; + this.bitmapRecentCommitSpan = cfg.bitmapRecentCommitSpan; + this.bitmapDistantCommitSpan = cfg.bitmapDistantCommitSpan; + this.bitmapExcessiveBranchCount = cfg.bitmapExcessiveBranchCount; + this.bitmapInactiveBranchAgeInDays = cfg.bitmapInactiveBranchAgeInDays; this.cutDeltaChains = cfg.cutDeltaChains; } @@ -650,7 +727,7 @@ public class PackConfig { * oldest (most compatible) format available for the objects. * @see PackIndexWriter */ - public void setIndexVersion(final int version) { + public void setIndexVersion(int version) { indexVersion = version; } @@ -684,6 +761,162 @@ public class PackConfig { } /** + * Get the count of most recent commits for which to build bitmaps. + * + * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT} + * + * @return the count of most recent commits for which to build bitmaps + * @since 4.2 + */ + public int getBitmapContiguousCommitCount() { + return bitmapContiguousCommitCount; + } + + /** + * Set the count of most recent commits for which to build bitmaps. + * + * Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT} + * + * @param count + * the count of most recent commits for which to build bitmaps + * @since 4.2 + */ + public void setBitmapContiguousCommitCount(int count) { + bitmapContiguousCommitCount = count; + } + + /** + * Get the count at which to switch from "bitmapRecentCommitSpan" to + * "bitmapDistantCommitSpan". + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT} + * + * @return the count for switching between recent and distant spans + * @since 4.2 + */ + public int getBitmapRecentCommitCount() { + return bitmapRecentCommitCount; + } + + /** + * Set the count at which to switch from "bitmapRecentCommitSpan" to + * "bitmapDistantCommitSpan". + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_COUNT} + * + * @param count + * the count for switching between recent and distant spans + * @since 4.2 + */ + public void setBitmapRecentCommitCount(int count) { + bitmapRecentCommitCount = count; + } + + /** + * Get the span of commits when building bitmaps for recent history. + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN} + * + * @return the span of commits when building bitmaps for recent history + * @since 4.2 + */ + public int getBitmapRecentCommitSpan() { + return bitmapRecentCommitSpan; + } + + /** + * Set the span of commits when building bitmaps for recent history. + * + * Default setting: {@value #DEFAULT_BITMAP_RECENT_COMMIT_SPAN} + * + * @param span + * the span of commits when building bitmaps for recent history + * @since 4.2 + */ + public void setBitmapRecentCommitSpan(int span) { + bitmapRecentCommitSpan = span; + } + + /** + * Get the span of commits when building bitmaps for distant history. + * + * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN} + * + * @return the span of commits when building bitmaps for distant history + * @since 4.2 + */ + public int getBitmapDistantCommitSpan() { + return bitmapDistantCommitSpan; + } + + /** + * Set the span of commits when building bitmaps for distant history. + * + * Default setting: {@value #DEFAULT_BITMAP_DISTANT_COMMIT_SPAN} + * + * @param span + * the span of commits when building bitmaps for distant history + * @since 4.2 + */ + public void setBitmapDistantCommitSpan(int span) { + bitmapDistantCommitSpan = span; + } + + /** + * Get the count of branches deemed "excessive". If the count of branches in + * a repository exceeds this number and bitmaps are enabled, "inactive" + * branches will have fewer bitmaps than "active" branches. + * + * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} + * + * @return the count of branches deemed "excessive" + * @since 4.2 + */ + public int getBitmapExcessiveBranchCount() { + return bitmapExcessiveBranchCount; + } + + /** + * Set the count of branches deemed "excessive". If the count of branches in + * a repository exceeds this number and bitmaps are enabled, "inactive" + * branches will have fewer bitmaps than "active" branches. + * + * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} + * + * @param count + * the count of branches deemed "excessive" + * @since 4.2 + */ + public void setBitmapExcessiveBranchCount(int count) { + bitmapExcessiveBranchCount = count; + } + + /** + * Get the the age in days that marks a branch as "inactive". + * + * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS} + * + * @return the age in days that marks a branch as "inactive" + * @since 4.2 + */ + public int getBitmapInactiveBranchAgeInDays() { + return bitmapInactiveBranchAgeInDays; + } + + /** + * Set the the age in days that marks a branch as "inactive". + * + * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS} + * + * @param ageInDays + * the age in days that marks a branch as "inactive" + * @since 4.2 + */ + public void setBitmapInactiveBranchAgeInDays(int ageInDays) { + bitmapInactiveBranchAgeInDays = ageInDays; + } + + /** * Update properties by setting fields from the configuration. * * If a property's corresponding variable is not defined in the supplied @@ -712,19 +945,36 @@ public class PackConfig { // These variables aren't standardized // setReuseDeltas(rc.getBoolean("pack", "reusedeltas", isReuseDeltas())); //$NON-NLS-1$ //$NON-NLS-2$ - setReuseObjects(rc.getBoolean("pack", "reuseobjects", isReuseObjects())); //$NON-NLS-1$ //$NON-NLS-2$ - setDeltaCompress(rc.getBoolean( - "pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$ - setCutDeltaChains(rc.getBoolean( - "pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$ - setBuildBitmaps(rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$ + setReuseObjects( + rc.getBoolean("pack", "reuseobjects", isReuseObjects())); //$NON-NLS-1$ //$NON-NLS-2$ + setDeltaCompress( + rc.getBoolean("pack", "deltacompression", isDeltaCompress())); //$NON-NLS-1$ //$NON-NLS-2$ + setCutDeltaChains( + rc.getBoolean("pack", "cutdeltachains", getCutDeltaChains())); //$NON-NLS-1$ //$NON-NLS-2$ + setBuildBitmaps( + rc.getBoolean("pack", "buildbitmaps", isBuildBitmaps())); //$NON-NLS-1$ //$NON-NLS-2$ + setBitmapContiguousCommitCount( + rc.getInt("pack", "bitmapcontiguouscommitcount", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapContiguousCommitCount())); + setBitmapRecentCommitCount(rc.getInt("pack", "bitmaprecentcommitcount", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapRecentCommitCount())); + setBitmapRecentCommitSpan(rc.getInt("pack", "bitmaprecentcommitspan", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapRecentCommitSpan())); + setBitmapDistantCommitSpan(rc.getInt("pack", "bitmapdistantcommitspan", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapDistantCommitSpan())); + setBitmapExcessiveBranchCount(rc.getInt("pack", //$NON-NLS-1$ + "bitmapexcessivebranchcount", getBitmapExcessiveBranchCount())); //$NON-NLS-1$ + setBitmapInactiveBranchAgeInDays( + rc.getInt("pack", "bitmapinactivebranchageindays", //$NON-NLS-1$ //$NON-NLS-2$ + getBitmapInactiveBranchAgeInDays())); } public String toString() { final StringBuilder b = new StringBuilder(); b.append("maxDeltaDepth=").append(getMaxDeltaDepth()); //$NON-NLS-1$ b.append(", deltaSearchWindowSize=").append(getDeltaSearchWindowSize()); //$NON-NLS-1$ - b.append(", deltaSearchMemoryLimit=").append(getDeltaSearchMemoryLimit()); //$NON-NLS-1$ + b.append(", deltaSearchMemoryLimit=") //$NON-NLS-1$ + .append(getDeltaSearchMemoryLimit()); b.append(", deltaCacheSize=").append(getDeltaCacheSize()); //$NON-NLS-1$ b.append(", deltaCacheLimit=").append(getDeltaCacheLimit()); //$NON-NLS-1$ b.append(", compressionLevel=").append(getCompressionLevel()); //$NON-NLS-1$ @@ -735,6 +985,18 @@ public class PackConfig { b.append(", reuseObjects=").append(isReuseObjects()); //$NON-NLS-1$ b.append(", deltaCompress=").append(isDeltaCompress()); //$NON-NLS-1$ b.append(", buildBitmaps=").append(isBuildBitmaps()); //$NON-NLS-1$ + b.append(", bitmapContiguousCommitCount=") //$NON-NLS-1$ + .append(getBitmapContiguousCommitCount()); + b.append(", bitmapRecentCommitCount=") //$NON-NLS-1$ + .append(getBitmapRecentCommitCount()); + b.append(", bitmapRecentCommitSpan=") //$NON-NLS-1$ + .append(getBitmapRecentCommitSpan()); + b.append(", bitmapDistantCommitSpan=") //$NON-NLS-1$ + .append(getBitmapDistantCommitSpan()); + b.append(", bitmapExcessiveBranchCount=") //$NON-NLS-1$ + .append(getBitmapExcessiveBranchCount()); + b.append(", bitmapInactiveBranchAge=") //$NON-NLS-1$ + .append(getBitmapInactiveBranchAgeInDays()); return b.toString(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java index d3cdba5bf3..4069a64535 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java @@ -56,10 +56,10 @@ import java.net.ProxySelector; import java.net.URL; import java.net.URLConnection; import java.security.DigestOutputStream; +import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -175,7 +175,7 @@ public class AmazonS3 { private final String acl; /** Maximum number of times to try an operation. */ - private final int maxAttempts; + final int maxAttempts; /** Encryption algorithm, may be a null instance that provides pass-through. */ private final WalkEncryption encryption; @@ -186,6 +186,19 @@ public class AmazonS3 { /** S3 Bucket Domain. */ private final String domain; + /** Property names used in amazon connection configuration file. */ + interface Keys { + String ACCESS_KEY = "accesskey"; //$NON-NLS-1$ + String SECRET_KEY = "secretkey"; //$NON-NLS-1$ + String PASSWORD = "password"; //$NON-NLS-1$ + String CRYPTO_ALG = "crypto.algorithm"; //$NON-NLS-1$ + String CRYPTO_VER = "crypto.version"; //$NON-NLS-1$ + String ACL = "acl"; //$NON-NLS-1$ + String DOMAIN = "domain"; //$NON-NLS-1$ + String HTTP_RETRY = "httpclient.retry-max"; //$NON-NLS-1$ + String TMP_DIR = "tmpdir"; //$NON-NLS-1$ + } + /** * Create a new S3 client for the supplied user information. * <p> @@ -219,17 +232,18 @@ public class AmazonS3 { * */ public AmazonS3(final Properties props) { - domain = props.getProperty("domain", "s3.amazonaws.com"); //$NON-NLS-1$ //$NON-NLS-2$ - publicKey = props.getProperty("accesskey"); //$NON-NLS-1$ + domain = props.getProperty(Keys.DOMAIN, "s3.amazonaws.com"); //$NON-NLS-1$ + + publicKey = props.getProperty(Keys.ACCESS_KEY); if (publicKey == null) throw new IllegalArgumentException(JGitText.get().missingAccesskey); - final String secret = props.getProperty("secretkey"); //$NON-NLS-1$ + final String secret = props.getProperty(Keys.SECRET_KEY); if (secret == null) throw new IllegalArgumentException(JGitText.get().missingSecretkey); privateKey = new SecretKeySpec(Constants.encodeASCII(secret), HMAC); - final String pacl = props.getProperty("acl", "PRIVATE"); //$NON-NLS-1$ //$NON-NLS-2$ + final String pacl = props.getProperty(Keys.ACL, "PRIVATE"); //$NON-NLS-1$ if (StringUtils.equalsIgnoreCase("PRIVATE", pacl)) //$NON-NLS-1$ acl = "private"; //$NON-NLS-1$ else if (StringUtils.equalsIgnoreCase("PUBLIC", pacl)) //$NON-NLS-1$ @@ -242,26 +256,16 @@ public class AmazonS3 { throw new IllegalArgumentException("Invalid acl: " + pacl); //$NON-NLS-1$ try { - final String cPas = props.getProperty("password"); //$NON-NLS-1$ - if (cPas != null) { - String cAlg = props.getProperty("crypto.algorithm"); //$NON-NLS-1$ - if (cAlg == null) - cAlg = "PBEWithMD5AndDES"; //$NON-NLS-1$ - encryption = new WalkEncryption.ObjectEncryptionV2(cAlg, cPas); - } else { - encryption = WalkEncryption.NONE; - } - } catch (InvalidKeySpecException e) { - throw new IllegalArgumentException(JGitText.get().invalidEncryption, e); - } catch (NoSuchAlgorithmException e) { + encryption = WalkEncryption.instance(props); + } catch (GeneralSecurityException e) { throw new IllegalArgumentException(JGitText.get().invalidEncryption, e); } - maxAttempts = Integer.parseInt(props.getProperty( - "httpclient.retry-max", "3")); //$NON-NLS-1$ //$NON-NLS-2$ + maxAttempts = Integer + .parseInt(props.getProperty(Keys.HTTP_RETRY, "3")); //$NON-NLS-1$ proxySelector = ProxySelector.getDefault(); - String tmp = props.getProperty("tmpdir"); //$NON-NLS-1$ + String tmp = props.getProperty(Keys.TMP_DIR); tmpDir = tmp != null && tmp.length() > 0 ? new File(tmp) : null; } @@ -479,7 +483,7 @@ public class AmazonS3 { return encryption.encrypt(new DigestOutputStream(buffer, md5)); } - private void putImpl(final String bucket, final String key, + void putImpl(final String bucket, final String key, final byte[] csum, final TemporaryBuffer buf, ProgressMonitor monitor, String monitorTask) throws IOException { if (monitor == null) @@ -518,7 +522,7 @@ public class AmazonS3 { throw maxAttempts(JGitText.get().s3ActionWriting, key); } - private IOException error(final String action, final String key, + IOException error(final String action, final String key, final HttpURLConnection c) throws IOException { final IOException err = new IOException(MessageFormat.format( JGitText.get().amazonS3ActionFailed, action, key, @@ -543,7 +547,7 @@ public class AmazonS3 { return err; } - private IOException maxAttempts(final String action, final String key) { + IOException maxAttempts(final String action, final String key) { return new IOException(MessageFormat.format( JGitText.get().amazonS3ActionFailedGivingUp, action, key, Integer.valueOf(maxAttempts))); @@ -555,7 +559,7 @@ public class AmazonS3 { return open(method, bucket, key, noArgs); } - private HttpURLConnection open(final String method, final String bucket, + HttpURLConnection open(final String method, final String bucket, final String key, final Map<String, String> args) throws IOException { final StringBuilder urlstr = new StringBuilder(); @@ -592,7 +596,7 @@ public class AmazonS3 { return c; } - private void authorize(final HttpURLConnection c) throws IOException { + void authorize(final HttpURLConnection c) throws IOException { final Map<String, List<String>> reqHdr = c.getRequestProperties(); final SortedMap<String, String> sigHdr = new TreeMap<String, String>(); for (final Map.Entry<String, List<String>> entry : reqHdr.entrySet()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java index 03f7c72835..d9e0b937e8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java @@ -79,7 +79,7 @@ public class Daemon { private boolean run; - private Thread acceptThread; + Thread acceptThread; private int timeout; @@ -87,9 +87,9 @@ public class Daemon { private volatile RepositoryResolver<DaemonClient> repositoryResolver; - private volatile UploadPackFactory<DaemonClient> uploadPackFactory; + volatile UploadPackFactory<DaemonClient> uploadPackFactory; - private volatile ReceivePackFactory<DaemonClient> receivePackFactory; + volatile ReceivePackFactory<DaemonClient> receivePackFactory; /** Configure a daemon to listen on any available network port. */ public Daemon() { @@ -326,7 +326,7 @@ public class Daemon { } } - private void startClient(final Socket s) { + void startClient(final Socket s) { final DaemonClient dc = new DaemonClient(this); final SocketAddress peer = s.getRemoteSocketAddress(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java index b4a09020b5..85109a5bf0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java @@ -71,8 +71,8 @@ import com.jcraft.jsch.Session; * to the constructor. */ public class JschSession implements RemoteSession { - private final Session sock; - private final URIish uri; + final Session sock; + final URIish uri; /** * Create a new session object by passing the real Jsch session and the URI @@ -119,7 +119,7 @@ public class JschSession implements RemoteSession { private class JschProcess extends Process { private ChannelExec channel; - private final int timeout; + final int timeout; private InputStream inputStream; @@ -141,7 +141,7 @@ public class JschSession implements RemoteSession { * @throws IOException * on problems opening streams */ - private JschProcess(final String commandName, int tms) + JschProcess(final String commandName, int tms) throws TransportException, IOException { timeout = tms; try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java index 918df94de9..6e5fc9f009 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -122,14 +122,14 @@ public abstract class PackParser { private InputStream in; - private byte[] buf; + byte[] buf; /** Position in the input stream of {@code buf[0]}. */ private long bBase; private int bOffset; - private int bAvail; + int bAvail; private ObjectChecker objCheck; @@ -1141,13 +1141,13 @@ public abstract class PackParser { } // Consume cnt bytes from the buffer. - private void use(final int cnt) { + void use(final int cnt) { bOffset += cnt; bAvail -= cnt; } // Ensure at least need bytes are available in in {@link #buf}. - private int fill(final Source src, final int need) throws IOException { + int fill(final Source src, final int need) throws IOException { while (bAvail < need) { int next = bOffset + bAvail; int free = buf.length - next; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java index d8672d5a2b..8947f2779d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java @@ -107,11 +107,11 @@ public class PushCertificateStore implements AutoCloseable { Constants.R_REFS + "meta/push-certs"; //$NON-NLS-1$ private static class PendingCert { - private PushCertificate cert; - private PersonIdent ident; - private Collection<ReceiveCommand> matching; + PushCertificate cert; + PersonIdent ident; + Collection<ReceiveCommand> matching; - private PendingCert(PushCertificate cert, PersonIdent ident, + PendingCert(PushCertificate cert, PersonIdent ident, Collection<ReceiveCommand> matching) { this.cert = cert; this.ident = ident; @@ -121,8 +121,8 @@ public class PushCertificateStore implements AutoCloseable { private final Repository db; private final List<PendingCert> pending; - private ObjectReader reader; - private RevCommit commit; + ObjectReader reader; + RevCommit commit; /** * Create a new store backed by the given repository. @@ -270,7 +270,7 @@ public class PushCertificateStore implements AutoCloseable { }; } - private void load() throws IOException { + void load() throws IOException { close(); reader = db.newObjectReader(); Ref ref = db.getRefDatabase().exactRef(REF_NAME); @@ -283,7 +283,7 @@ public class PushCertificateStore implements AutoCloseable { } } - private static PushCertificate read(TreeWalk tw) throws IOException { + static PushCertificate read(TreeWalk tw) throws IOException { if (tw == null || (tw.getRawMode(0) & TYPE_FILE) != TYPE_FILE) { return null; } @@ -532,7 +532,7 @@ public class PushCertificateStore implements AutoCloseable { return TreeWalk.forPath(reader, pathName(refName), commit.getTree()); } - private static String pathName(String refName) { + static String pathName(String refName) { return refName + "@{cert}"; //$NON-NLS-1$ } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java index 5243010502..5fd2f84b7e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java @@ -78,17 +78,17 @@ public class TestProtocol<C> extends TransportProtocol { private static final String SCHEME = "test"; //$NON-NLS-1$ private class Handle { - private final C req; - private final Repository remote; + final C req; + final Repository remote; - private Handle(C req, Repository remote) { + Handle(C req, Repository remote) { this.req = req; this.remote = remote; } } - private final UploadPackFactory<C> uploadPackFactory; - private final ReceivePackFactory<C> receivePackFactory; + final UploadPackFactory<C> uploadPackFactory; + final ReceivePackFactory<C> receivePackFactory; private final HashMap<URIish, Handle> handles; /** @@ -165,7 +165,7 @@ public class TestProtocol<C> extends TransportProtocol { private class TransportInternal extends Transport implements PackTransport { private final Handle handle; - private TransportInternal(Repository local, URIish uri, Handle handle) { + TransportInternal(Repository local, URIish uri, Handle handle) { super(local, uri); this.handle = handle; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java index 1ef3fbf627..5aae5ea21a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java @@ -52,10 +52,10 @@ import org.eclipse.jgit.lib.RefUpdate; /** Update of a locally stored tracking branch. */ public class TrackingRefUpdate { private final String remoteName; - private final String localName; - private boolean forceUpdate; - private ObjectId oldObjectId; - private ObjectId newObjectId; + final String localName; + boolean forceUpdate; + ObjectId oldObjectId; + ObjectId newObjectId; private RefUpdate.Result result; private ReceiveCommand cmd; @@ -142,7 +142,7 @@ public class TrackingRefUpdate { } final class Command extends ReceiveCommand { - private Command() { + Command() { super(oldObjectId, newObjectId, localName); } 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 f4de82147d..f0c513427a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -72,13 +72,13 @@ public class TransferConfig { private final boolean safeForMacOS; private final boolean allowTipSha1InWant; private final boolean allowReachableSha1InWant; - private final String[] hideRefs; + final String[] hideRefs; TransferConfig(final Repository db) { this(db.getConfig()); } - private TransferConfig(final Config rc) { + TransferConfig(final Config rc) { checkReceivedObjects = rc.getBoolean( "fetch", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$ rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index 218562254c..cc7db47df5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -53,6 +53,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.PrintStream; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -70,8 +71,11 @@ import java.util.Map; import java.util.Vector; import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.hooks.Hooks; +import org.eclipse.jgit.hooks.PrePushHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -557,8 +561,13 @@ public abstract class Transport { continue; } - if (proto.canHandle(uri, local, remoteName)) - return proto.open(uri, local, remoteName); + if (proto.canHandle(uri, local, remoteName)) { + Transport tn = proto.open(uri, local, remoteName); + tn.prePush = Hooks.prePush(local, tn.hookOutRedirect); + tn.prePush.setRemoteLocation(uri.toString()); + tn.prePush.setRemoteName(remoteName); + return tn; + } } throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri)); @@ -761,6 +770,9 @@ public abstract class Transport { /** Assists with authentication the connection. */ private CredentialsProvider credentialsProvider; + private PrintStream hookOutRedirect; + + private PrePushHook prePush; /** * Create a new transport instance. * @@ -778,6 +790,7 @@ public abstract class Transport { this.uri = uri; this.objectChecker = tc.newObjectChecker(); this.credentialsProvider = CredentialsProvider.getDefault(); + prePush = Hooks.prePush(local, hookOutRedirect); } /** @@ -1196,6 +1209,15 @@ public abstract class Transport { if (toPush.isEmpty()) throw new TransportException(JGitText.get().nothingToPush); } + if (prePush != null) { + try { + prePush.setRefs(toPush); + prePush.call(); + } catch (AbortedByHookException | IOException e) { + throw new TransportException(e.getMessage(), e); + } + } + final PushProcess pushProcess = new PushProcess(this, toPush, out); return pushProcess.execute(monitor); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java index 745cdb72d5..7729c11ff9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java @@ -125,10 +125,10 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport { }; /** User information necessary to connect to S3. */ - private final AmazonS3 s3; + final AmazonS3 s3; /** Bucket the remote repository is stored in. */ - private final String bucket; + final String bucket; /** * Key prefix which all objects related to the repository start with. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index b23771e952..594827886b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -218,16 +218,16 @@ public class TransportHttp extends HttpTransport implements WalkTransport, sslVerify = rc.getBoolean("http", "sslVerify", true); //$NON-NLS-1$ //$NON-NLS-2$ } - private HttpConfig() { + HttpConfig() { this(new Config()); } } - private final URL baseUrl; + final URL baseUrl; private final URL objectsUrl; - private final HttpConfig http; + final HttpConfig http; private final ProxySelector proxySelector; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java index e55b984380..fe03bdc867 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java @@ -47,22 +47,28 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.KeySpec; import java.text.MessageFormat; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; -import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.util.Base64; abstract class WalkEncryption { static final WalkEncryption NONE = new NoEncryption(); @@ -71,29 +77,40 @@ abstract class WalkEncryption { static final String JETS3T_CRYPTO_ALG = "jets3t-crypto-alg"; //$NON-NLS-1$ - abstract OutputStream encrypt(OutputStream os) throws IOException; + // Note: encrypt -> request state machine, step 1. + abstract OutputStream encrypt(OutputStream output) throws IOException; - abstract InputStream decrypt(InputStream in) throws IOException; + // Note: encrypt -> request state machine, step 2. + abstract void request(HttpURLConnection conn, String prefix) throws IOException; - abstract void request(HttpURLConnection u, String prefix); + // Note: validate -> decrypt state machine, step 1. + abstract void validate(HttpURLConnection conn, String prefix) throws IOException; - abstract void validate(HttpURLConnection u, String p) throws IOException; + // Note: validate -> decrypt state machine, step 2. + abstract InputStream decrypt(InputStream input) throws IOException; - protected void validateImpl(final HttpURLConnection u, final String p, + + // TODO mixed ciphers + // consider permitting mixed ciphers to facilitate algorithm migration + // i.e. user keeps the password, but changes the algorithm + // then existing remote entries will still be readable + protected void validateImpl(final HttpURLConnection u, final String prefix, final String version, final String name) throws IOException { String v; - v = u.getHeaderField(p + JETS3T_CRYPTO_VER); + v = u.getHeaderField(prefix + JETS3T_CRYPTO_VER); if (v == null) v = ""; //$NON-NLS-1$ if (!version.equals(v)) throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionVersion, v)); - v = u.getHeaderField(p + JETS3T_CRYPTO_ALG); + v = u.getHeaderField(prefix + JETS3T_CRYPTO_ALG); if (v == null) v = ""; //$NON-NLS-1$ - if (!name.equals(v)) - throw new IOException(JGitText.get().unsupportedEncryptionAlgorithm + v); + // Standard names are not case-sensitive. + // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html + if (!name.equalsIgnoreCase(v)) + throw new IOException(MessageFormat.format(JGitText.get().unsupportedEncryptionAlgorithm, v)); } IOException error(final Throwable why) { @@ -110,9 +127,9 @@ abstract class WalkEncryption { } @Override - void validate(final HttpURLConnection u, final String p) + void validate(final HttpURLConnection u, final String prefix) throws IOException { - validateImpl(u, p, "", ""); //$NON-NLS-1$ //$NON-NLS-2$ + validateImpl(u, prefix, "", ""); //$NON-NLS-1$ //$NON-NLS-2$ } @Override @@ -126,53 +143,139 @@ abstract class WalkEncryption { } } - static class ObjectEncryptionV2 extends WalkEncryption { - private static int ITERATION_COUNT = 5000; + // PBEParameterSpec factory for Java (version <= 7). + // Does not support AlgorithmParameterSpec. + static PBEParameterSpec java7PBEParameterSpec(byte[] salt, + int iterationCount) { + return new PBEParameterSpec(salt, iterationCount); + } + + // PBEParameterSpec factory for Java (version >= 8). + // Adds support for AlgorithmParameterSpec. + static PBEParameterSpec java8PBEParameterSpec(byte[] salt, + int iterationCount, AlgorithmParameterSpec paramSpec) { + try { + @SuppressWarnings("boxing") + PBEParameterSpec instance = PBEParameterSpec.class + .getConstructor(byte[].class, int.class, + AlgorithmParameterSpec.class) + .newInstance(salt, iterationCount, paramSpec); + return instance; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // Current runtime version. + // https://docs.oracle.com/javase/7/docs/technotes/guides/versioning/spec/versioning2.html + static double javaVersion() { + return Double.parseDouble(System.getProperty("java.specification.version")); //$NON-NLS-1$ + } + + /** + * JetS3t compatibility reference: <a href= + * "https://bitbucket.org/jmurty/jets3t/src/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java"> + * EncryptionUtil.java</a> + * <p> + * Note: EncryptionUtil is inadequate: + * <li>EncryptionUtil.isCipherAvailableForUse checks encryption only which + * "always works", but in JetS3t both encryption and decryption use non-IV + * aware algorithm parameters for all PBE specs, which breaks in case of AES + * <li>that means that only non-IV algorithms will work round trip in + * JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC + * <li>any AES based algorithms such as "PBE...With...And...AES" will not + * work, since they need proper IV setup + */ + static class JetS3tV2 extends WalkEncryption { + + static final String VERSION = "2"; //$NON-NLS-1$ + + static final String ALGORITHM = "PBEWithMD5AndDES"; //$NON-NLS-1$ + + static final int ITERATIONS = 5000; + + static final int KEY_SIZE = 32; + + static final byte[] SALT = { // + (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, // + (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 // + }; + + // Size 16, see com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE + static final byte[] ZERO_AES_IV = new byte[16]; + + private static final String cryptoVer = VERSION; - private static byte[] salt = { (byte) 0xA4, (byte) 0x0B, (byte) 0xC8, - (byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13 }; + private final String cryptoAlg; - private final String algorithmName; + private final SecretKey secretKey; - private final SecretKey skey; + private final AlgorithmParameterSpec paramSpec; - private final PBEParameterSpec aspec; + JetS3tV2(final String algo, final String key) + throws GeneralSecurityException { + cryptoAlg = algo; - ObjectEncryptionV2(final String algo, final String key) - throws InvalidKeySpecException, NoSuchAlgorithmException { - algorithmName = algo; + // Verify if cipher is present. + Cipher cipher = Cipher.getInstance(cryptoAlg); + + // Standard names are not case-sensitive. + // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html + String cryptoName = cryptoAlg.toUpperCase(); + + if (!cryptoName.startsWith("PBE")) //$NON-NLS-1$ + throw new GeneralSecurityException(JGitText.get().encryptionOnlyPBE); + + PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), SALT, ITERATIONS, KEY_SIZE); + secretKey = SecretKeyFactory.getInstance(algo).generateSecret(keySpec); + + // Detect algorithms which require initialization vector. + boolean useIV = cryptoName.contains("AES"); //$NON-NLS-1$ + + // PBEParameterSpec algorithm parameters are supported from Java 8. + boolean isJava8 = javaVersion() >= 1.8; + + if (useIV && isJava8) { + // Support IV where possible: + // * since JCE provider uses random IV for PBE/AES + // * and there is no place to store dynamic IV in JetS3t V2 + // * we use static IV, and tolerate increased security risk + // TODO back port this change to JetS3t V2 + // See: + // https://bitbucket.org/jmurty/jets3t/raw/156c00eb160598c2e9937fd6873f00d3190e28ca/src/org/jets3t/service/security/EncryptionUtil.java + // http://cr.openjdk.java.net/~mullan/webrevs/ascarpin/webrev.00/raw_files/new/src/share/classes/com/sun/crypto/provider/PBES2Core.java + IvParameterSpec paramIV = new IvParameterSpec(ZERO_AES_IV); + paramSpec = java8PBEParameterSpec(SALT, ITERATIONS, paramIV); + } else { + // Strict legacy JetS3t V2 compatibility, with no IV support. + paramSpec = java7PBEParameterSpec(SALT, ITERATIONS); + } + + // Verify if cipher + key are allowed by policy. + cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); + cipher.doFinal(); - final PBEKeySpec s; - s = new PBEKeySpec(key.toCharArray(), salt, ITERATION_COUNT, 32); - skey = SecretKeyFactory.getInstance(algo).generateSecret(s); - aspec = new PBEParameterSpec(salt, ITERATION_COUNT); } @Override void request(final HttpURLConnection u, final String prefix) { - u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, "2"); //$NON-NLS-1$ - u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, algorithmName); + u.setRequestProperty(prefix + JETS3T_CRYPTO_VER, cryptoVer); + u.setRequestProperty(prefix + JETS3T_CRYPTO_ALG, cryptoAlg); } @Override - void validate(final HttpURLConnection u, final String p) + void validate(final HttpURLConnection u, final String prefix) throws IOException { - validateImpl(u, p, "2", algorithmName); //$NON-NLS-1$ + validateImpl(u, prefix, cryptoVer, cryptoAlg); } @Override OutputStream encrypt(final OutputStream os) throws IOException { try { - final Cipher c = Cipher.getInstance(algorithmName); - c.init(Cipher.ENCRYPT_MODE, skey, aspec); - return new CipherOutputStream(os, c); - } catch (NoSuchAlgorithmException e) { - throw error(e); - } catch (NoSuchPaddingException e) { - throw error(e); - } catch (InvalidKeyException e) { - throw error(e); - } catch (InvalidAlgorithmParameterException e) { + final Cipher cipher = Cipher.getInstance(cryptoAlg); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); + return new CipherOutputStream(os, cipher); + } catch (GeneralSecurityException e) { throw error(e); } } @@ -180,18 +283,311 @@ abstract class WalkEncryption { @Override InputStream decrypt(final InputStream in) throws IOException { try { - final Cipher c = Cipher.getInstance(algorithmName); - c.init(Cipher.DECRYPT_MODE, skey, aspec); - return new CipherInputStream(in, c); - } catch (NoSuchAlgorithmException e) { + final Cipher cipher = Cipher.getInstance(cryptoAlg); + cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); + return new CipherInputStream(in, cipher); + } catch (GeneralSecurityException e) { throw error(e); - } catch (NoSuchPaddingException e) { - throw error(e); - } catch (InvalidKeyException e) { + } + } + } + + /** Encryption property names. */ + interface Keys { + // Remote S3 meta: V1 algorithm name or V2 profile name. + String JGIT_PROFILE = "jgit-crypto-profile"; //$NON-NLS-1$ + + // Remote S3 meta: JGit encryption implementation version. + String JGIT_VERSION = "jgit-crypto-version"; //$NON-NLS-1$ + + // Remote S3 meta: base-64 encoded cipher algorithm parameters. + String JGIT_CONTEXT = "jgit-crypto-context"; //$NON-NLS-1$ + + // Amazon S3 connection configuration file profile property suffixes: + String X_ALGO = ".algo"; //$NON-NLS-1$ + String X_KEY_ALGO = ".key.algo"; //$NON-NLS-1$ + String X_KEY_SIZE = ".key.size"; //$NON-NLS-1$ + String X_KEY_ITER = ".key.iter"; //$NON-NLS-1$ + String X_KEY_SALT = ".key.salt"; //$NON-NLS-1$ + } + + /** Encryption constants and defaults. */ + interface Vals { + // Compatibility defaults. + String DEFAULT_VERS = "0"; //$NON-NLS-1$ + String DEFAULT_ALGO = JetS3tV2.ALGORITHM; + String DEFAULT_KEY_ALGO = JetS3tV2.ALGORITHM; + String DEFAULT_KEY_SIZE = Integer.toString(JetS3tV2.KEY_SIZE); + String DEFAULT_KEY_ITER = Integer.toString(JetS3tV2.ITERATIONS); + String DEFAULT_KEY_SALT = DatatypeConverter.printHexBinary(JetS3tV2.SALT); + + String EMPTY = ""; //$NON-NLS-1$ + + // Match white space. + String REGEX_WS = "\\s+"; //$NON-NLS-1$ + + // Match PBE ciphers, i.e: PBEWithMD5AndDES + String REGEX_PBE = "(PBE).*(WITH).+(AND).+"; //$NON-NLS-1$ + + // Match transformation ciphers, i.e: AES/CBC/PKCS5Padding + String REGEX_TRANS = "(.+)/(.+)/(.+)"; //$NON-NLS-1$ + } + + static GeneralSecurityException securityError(String message) { + return new GeneralSecurityException( + MessageFormat.format(JGitText.get().encryptionError, message)); + } + + /** + * Base implementation of JGit symmetric encryption. Supports V2 properties + * format. + */ + static abstract class SymmetricEncryption extends WalkEncryption + implements Keys, Vals { + + /** Encryption profile, root name of group of related properties. */ + final String profile; + + /** Encryption version, reflects actual implementation class. */ + final String version; + + /** Full cipher algorithm name. */ + final String cipherAlgo; + + /** Cipher algorithm name for parameters lookup. */ + final String paramsAlgo; + + /** Generated secret key. */ + final SecretKey secretKey; + + SymmetricEncryption(Properties props) throws GeneralSecurityException { + + profile = props.getProperty(AmazonS3.Keys.CRYPTO_ALG); + version = props.getProperty(AmazonS3.Keys.CRYPTO_VER); + String pass = props.getProperty(AmazonS3.Keys.PASSWORD); + + cipherAlgo = props.getProperty(profile + X_ALGO, DEFAULT_ALGO); + + String keyAlgo = props.getProperty(profile + X_KEY_ALGO, DEFAULT_KEY_ALGO); + String keySize = props.getProperty(profile + X_KEY_SIZE, DEFAULT_KEY_SIZE); + String keyIter = props.getProperty(profile + X_KEY_ITER, DEFAULT_KEY_ITER); + String keySalt = props.getProperty(profile + X_KEY_SALT, DEFAULT_KEY_SALT); + + // Verify if cipher is present. + Cipher cipher = Cipher.getInstance(cipherAlgo); + + // Verify if key factory is present. + SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgo); + + final int size; + try { + size = Integer.parseInt(keySize); + } catch (Exception e) { + throw securityError(X_KEY_SIZE + EMPTY + keySize); + } + + final int iter; + try { + iter = Integer.parseInt(keyIter); + } catch (Exception e) { + throw securityError(X_KEY_ITER + EMPTY + keyIter); + } + + final byte[] salt; + try { + salt = DatatypeConverter + .parseHexBinary(keySalt.replaceAll(REGEX_WS, EMPTY)); + } catch (Exception e) { + throw securityError(X_KEY_SALT + EMPTY + keySalt); + } + + KeySpec keySpec = new PBEKeySpec(pass.toCharArray(), salt, iter, size); + + SecretKey keyBase = factory.generateSecret(keySpec); + + String name = cipherAlgo.toUpperCase(); + Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name); + Matcher matcherTrans = Pattern.compile(REGEX_TRANS).matcher(name); + if (matcherPBE.matches()) { + paramsAlgo = cipherAlgo; + secretKey = keyBase; + } else if (matcherTrans.find()) { + paramsAlgo = matcherTrans.group(1); + secretKey = new SecretKeySpec(keyBase.getEncoded(), paramsAlgo); + } else { + throw new GeneralSecurityException(MessageFormat.format( + JGitText.get().unsupportedEncryptionAlgorithm, + cipherAlgo)); + } + + // Verify if cipher + key are allowed by policy. + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + cipher.doFinal(); + + } + + // Shared state encrypt -> request. + volatile String context; + + @Override + OutputStream encrypt(OutputStream output) throws IOException { + try { + Cipher cipher = Cipher.getInstance(cipherAlgo); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + AlgorithmParameters params = cipher.getParameters(); + if (params == null) { + context = EMPTY; + } else { + context = Base64.encodeBytes(params.getEncoded()); + } + return new CipherOutputStream(output, cipher); + } catch (Exception e) { throw error(e); - } catch (InvalidAlgorithmParameterException e) { + } + } + + @Override + void request(HttpURLConnection conn, String prefix) throws IOException { + conn.setRequestProperty(prefix + JGIT_PROFILE, profile); + conn.setRequestProperty(prefix + JGIT_VERSION, version); + conn.setRequestProperty(prefix + JGIT_CONTEXT, context); + // No cleanup: + // single encrypt can be followed by several request + // from the AmazonS3.putImpl() multiple retry attempts + // context = null; // Cleanup encrypt -> request transition. + // TODO re-factor AmazonS3.putImpl to be more transaction-like + } + + // Shared state validate -> decrypt. + volatile Cipher decryptCipher; + + @Override + void validate(HttpURLConnection conn, String prefix) + throws IOException { + String prof = conn.getHeaderField(prefix + JGIT_PROFILE); + String vers = conn.getHeaderField(prefix + JGIT_VERSION); + String cont = conn.getHeaderField(prefix + JGIT_CONTEXT); + + if (prof == null) { + throw new IOException(MessageFormat + .format(JGitText.get().encryptionError, JGIT_PROFILE)); + } + if (vers == null) { + throw new IOException(MessageFormat + .format(JGitText.get().encryptionError, JGIT_VERSION)); + } + if (cont == null) { + throw new IOException(MessageFormat + .format(JGitText.get().encryptionError, JGIT_CONTEXT)); + } + if (!profile.equals(prof)) { + throw new IOException(MessageFormat.format( + JGitText.get().unsupportedEncryptionAlgorithm, prof)); + } + if (!version.equals(vers)) { + throw new IOException(MessageFormat.format( + JGitText.get().unsupportedEncryptionVersion, vers)); + } + try { + decryptCipher = Cipher.getInstance(cipherAlgo); + if (cont.isEmpty()) { + decryptCipher.init(Cipher.DECRYPT_MODE, secretKey); + } else { + AlgorithmParameters params = AlgorithmParameters + .getInstance(paramsAlgo); + params.init(Base64.decode(cont)); + decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, params); + } + } catch (Exception e) { throw error(e); } } + + @Override + InputStream decrypt(InputStream input) throws IOException { + try { + return new CipherInputStream(input, decryptCipher); + } finally { + decryptCipher = null; // Cleanup validate -> decrypt transition. + } + } + } + + /** + * Provides JetS3t-like encryption with AES support. Uses V1 connection file + * format. For reference, see: 'jgit-s3-connection-v-1.properties'. + */ + static class JGitV1 extends SymmetricEncryption { + + static final String VERSION = "1"; //$NON-NLS-1$ + + // Re-map connection properties V1 -> V2. + static Properties wrap(String algo, String pass) { + Properties props = new Properties(); + props.put(AmazonS3.Keys.CRYPTO_ALG, algo); + props.put(AmazonS3.Keys.CRYPTO_VER, VERSION); + props.put(AmazonS3.Keys.PASSWORD, pass); + props.put(algo + Keys.X_ALGO, algo); + props.put(algo + Keys.X_KEY_ALGO, algo); + props.put(algo + Keys.X_KEY_ITER, DEFAULT_KEY_ITER); + props.put(algo + Keys.X_KEY_SIZE, DEFAULT_KEY_SIZE); + props.put(algo + Keys.X_KEY_SALT, DEFAULT_KEY_SALT); + return props; + } + + JGitV1(String algo, String pass) + throws GeneralSecurityException { + super(wrap(algo, pass)); + String name = cipherAlgo.toUpperCase(); + Matcher matcherPBE = Pattern.compile(REGEX_PBE).matcher(name); + if (!matcherPBE.matches()) + throw new GeneralSecurityException( + JGitText.get().encryptionOnlyPBE); + } + + } + + /** + * Supports both PBE and non-PBE algorithms. Uses V2 connection file format. + * For reference, see: 'jgit-s3-connection-v-2.properties'. + */ + static class JGitV2 extends SymmetricEncryption { + + static final String VERSION = "2"; //$NON-NLS-1$ + + JGitV2(Properties props) + throws GeneralSecurityException { + super(props); + } + } + + /** + * Encryption factory. + * + * @param props + * @return instance + * @throws GeneralSecurityException + */ + static WalkEncryption instance(Properties props) + throws GeneralSecurityException { + + String algo = props.getProperty(AmazonS3.Keys.CRYPTO_ALG, Vals.DEFAULT_ALGO); + String vers = props.getProperty(AmazonS3.Keys.CRYPTO_VER, Vals.DEFAULT_VERS); + String pass = props.getProperty(AmazonS3.Keys.PASSWORD); + + if (pass == null) // Disable encryption. + return WalkEncryption.NONE; + + switch (vers) { + case Vals.DEFAULT_VERS: + return new JetS3tV2(algo, pass); + case JGitV1.VERSION: + return new JGitV1(algo, pass); + case JGitV2.VERSION: + return new JGitV2(props); + default: + throw new GeneralSecurityException(MessageFormat.format( + JGitText.get().unsupportedEncryptionVersion, vers)); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java index dc9dee55a4..1c6b8b7363 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -115,10 +115,10 @@ import org.eclipse.jgit.util.FileUtils; */ class WalkFetchConnection extends BaseFetchConnection { /** The repository this transport fetches into, or pushes out of. */ - private final Repository local; + final Repository local; /** If not null the validator for received objects. */ - private final ObjectChecker objCheck; + final ObjectChecker objCheck; /** * List of all remote repositories we may need to get objects out of. @@ -180,12 +180,12 @@ class WalkFetchConnection extends BaseFetchConnection { */ private final HashMap<ObjectId, List<Throwable>> fetchErrors; - private String lockMessage; + String lockMessage; - private final List<PackLock> packLocks; + final List<PackLock> packLocks; /** Inserter to write objects onto {@link #local}. */ - private final ObjectInserter inserter; + final ObjectInserter inserter; /** Inserter to read objects from {@link #local}. */ private final ObjectReader reader; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java index deecb8e15f..4eaf3f9d8a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java @@ -103,7 +103,7 @@ class WalkPushConnection extends BaseConnection implements PushConnection { private final URIish uri; /** Database connection to the remote repository. */ - private final WalkRemoteObjectDatabase dest; + final WalkRemoteObjectDatabase dest; /** The configured transport we were constructed by. */ private final Transport transport; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java index 0454e7e0d9..9d0ad736fa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java @@ -74,9 +74,9 @@ public class BlockList<T> extends AbstractList<T> { private static final int BLOCK_MASK = BLOCK_SIZE - 1; - private T[][] directory; + T[][] directory; - private int size; + int size; private int tailDirIdx; @@ -282,11 +282,11 @@ public class BlockList<T> extends AbstractList<T> { return new MyIterator(); } - private static final int toDirectoryIndex(int index) { + static final int toDirectoryIndex(int index) { return index >>> BLOCK_BITS; } - private static final int toBlockIndex(int index) { + static final int toBlockIndex(int index) { return index & BLOCK_MASK; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index e5219b2a92..e407dd8be9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -44,15 +44,13 @@ package org.eclipse.jgit.util; import java.io.BufferedReader; -import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.PrintStream; -import java.io.PrintWriter; import java.nio.charset.Charset; import java.security.AccessController; import java.security.PrivilegedAction; @@ -60,12 +58,14 @@ import java.text.MessageFormat; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; @@ -110,7 +110,7 @@ public abstract class FS { } } - private final static Logger LOG = LoggerFactory.getLogger(FS.class); + final static Logger LOG = LoggerFactory.getLogger(FS.class); /** The auto-detected implementation selected for this operating system and JRE. */ public static final FS DETECTED = detect(); @@ -404,8 +404,10 @@ public abstract class FS { * as component array * @param encoding * to be used to parse the command's output - * @return the one-line output of the command + * @return the one-line output of the command or {@code null} if there is + * none */ + @Nullable protected static String readPipe(File dir, String[] command, String encoding) { return readPipe(dir, command, encoding, null); } @@ -422,9 +424,11 @@ public abstract class FS { * @param env * Map of environment variables to be merged with those of the * current process - * @return the one-line output of the command + * @return the one-line output of the command or {@code null} if there is + * none * @since 4.0 */ + @Nullable protected static String readPipe(File dir, String[] command, String encoding, Map<String, String> env) { final boolean debug = LOG.isDebugEnabled(); try { @@ -438,36 +442,30 @@ public abstract class FS { pb.environment().putAll(env); } Process p = pb.start(); - BufferedReader lineRead = new BufferedReader( - new InputStreamReader(p.getInputStream(), encoding)); p.getOutputStream().close(); GobblerThread gobbler = new GobblerThread(p, command, dir); gobbler.start(); String r = null; - try { + try (BufferedReader lineRead = new BufferedReader( + new InputStreamReader(p.getInputStream(), encoding))) { r = lineRead.readLine(); if (debug) { LOG.debug("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$ - LOG.debug("(ignoring remaing output:"); //$NON-NLS-1$ - } - String l; - while ((l = lineRead.readLine()) != null) { - if (debug) { + LOG.debug("remaining output:\n"); //$NON-NLS-1$ + String l; + while ((l = lineRead.readLine()) != null) { LOG.debug(l); } } - } finally { - p.getErrorStream().close(); - lineRead.close(); } for (;;) { try { int rc = p.waitFor(); gobbler.join(); - if (rc == 0 && r != null && r.length() > 0 - && !gobbler.fail.get()) + if (rc == 0 && !gobbler.fail.get()) { return r; + } if (debug) { LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$ } @@ -477,7 +475,7 @@ public abstract class FS { } } } catch (IOException e) { - LOG.debug("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ + LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ } if (debug) { LOG.debug("readpipe returns null"); //$NON-NLS-1$ @@ -489,52 +487,39 @@ public abstract class FS { private final Process p; private final String desc; private final String dir; - private final boolean debug = LOG.isDebugEnabled(); - private final AtomicBoolean fail = new AtomicBoolean(); + final AtomicBoolean fail = new AtomicBoolean(); - private GobblerThread(Process p, String[] command, File dir) { + GobblerThread(Process p, String[] command, File dir) { this.p = p; - if (debug) { - this.desc = Arrays.asList(command).toString(); - this.dir = dir.toString(); - } else { - this.desc = null; - this.dir = null; - } + this.desc = Arrays.toString(command); + this.dir = Objects.toString(dir); } public void run() { - InputStream is = p.getErrorStream(); - try { + StringBuilder err = new StringBuilder(); + try (InputStream is = p.getErrorStream()) { int ch; - if (debug) { - while ((ch = is.read()) != -1) { - System.err.print((char) ch); - } - } else { - while (is.read() != -1) { - // ignore - } + while ((ch = is.read()) != -1) { + err.append((char) ch); } } catch (IOException e) { - logError(e); - fail.set(true); - } - try { - is.close(); - } catch (IOException e) { - logError(e); - fail.set(true); + if (p.exitValue() != 0) { + logError(e); + fail.set(true); + } else { + // ignore. git terminated faster and stream was just closed + } + } finally { + if (err.length() > 0) { + LOG.error(err.toString()); + } } } private void logError(Throwable t) { - if (!debug) { - return; - } String msg = MessageFormat.format( JGitText.get().exceptionCaughtDuringExcecutionOfCommand, desc, dir); - LOG.debug(msg, t); + LOG.error(msg, t); } } @@ -556,6 +541,14 @@ public abstract class FS { return null; } + // Bug 480782: Check if the discovered git executable is JGit CLI + String v = readPipe(gitExe.getParentFile(), + new String[] { "git", "--version" }, //$NON-NLS-1$ //$NON-NLS-2$ + Charset.defaultCharset().name()); + if (v != null && v.startsWith("jgit")) { //$NON-NLS-1$ + return null; + } + // Trick Git into printing the path to the config file by using "echo" // as the editor. Map<String, String> env = new HashMap<>(); @@ -871,52 +864,88 @@ public abstract class FS { * Runs the given process until termination, clearing its stdout and stderr * streams on-the-fly. * - * @param hookProcessBuilder - * The process builder configured for this hook. + * @param processBuilder + * The process builder configured for this process. * @param outRedirect - * A print stream on which to redirect the hook's stdout. Can be - * <code>null</code>, in which case the hook's standard output - * will be lost. + * A OutputStream on which to redirect the processes stdout. Can + * be <code>null</code>, in which case the processes standard + * output will be lost. * @param errRedirect - * A print stream on which to redirect the hook's stderr. Can be - * <code>null</code>, in which case the hook's standard error - * will be lost. + * A OutputStream on which to redirect the processes stderr. Can + * be <code>null</code>, in which case the processes standard + * error will be lost. * @param stdinArgs * A string to pass on to the standard input of the hook. Can be * <code>null</code>. - * @return the exit value of this hook. + * @return the exit value of this process. * @throws IOException - * if an I/O error occurs while executing this hook. + * if an I/O error occurs while executing this process. * @throws InterruptedException * if the current thread is interrupted while waiting for the * process to end. - * @since 3.7 + * @since 4.2 */ - protected int runProcess(ProcessBuilder hookProcessBuilder, + public int runProcess(ProcessBuilder processBuilder, OutputStream outRedirect, OutputStream errRedirect, String stdinArgs) throws IOException, InterruptedException { + InputStream in = (stdinArgs == null) ? null : new ByteArrayInputStream( + stdinArgs.getBytes(Constants.CHARACTER_ENCODING)); + return runProcess(processBuilder, outRedirect, errRedirect, in); + } + + /** + * Runs the given process until termination, clearing its stdout and stderr + * streams on-the-fly. + * + * @param processBuilder + * The process builder configured for this process. + * @param outRedirect + * An OutputStream on which to redirect the processes stdout. Can + * be <code>null</code>, in which case the processes standard + * output will be lost. If binary is set to <code>false</code> + * then it is expected that the process emits text data which + * should be processed line by line. + * @param errRedirect + * An OutputStream on which to redirect the processes stderr. Can + * be <code>null</code>, in which case the processes standard + * error will be lost. + * @param inRedirect + * An InputStream from which to redirect the processes stdin. Can + * be <code>null</code>, in which case the process doesn't get + * any data over stdin. If binary is set to + * <code>false</code> then it is expected that the process + * expects text data which should be processed line by line. + * @return the return code of this process. + * @throws IOException + * if an I/O error occurs while executing this process. + * @throws InterruptedException + * if the current thread is interrupted while waiting for the + * process to end. + * @since 4.2 + */ + public int runProcess(ProcessBuilder processBuilder, + OutputStream outRedirect, OutputStream errRedirect, + InputStream inRedirect) throws IOException, + InterruptedException { final ExecutorService executor = Executors.newFixedThreadPool(2); Process process = null; // We'll record the first I/O exception that occurs, but keep on trying // to dispose of our open streams and file handles IOException ioException = null; try { - process = hookProcessBuilder.start(); + process = processBuilder.start(); final Callable<Void> errorGobbler = new StreamGobbler( process.getErrorStream(), errRedirect); final Callable<Void> outputGobbler = new StreamGobbler( process.getInputStream(), outRedirect); executor.submit(errorGobbler); executor.submit(outputGobbler); - if (stdinArgs != null) { - final PrintWriter stdinWriter = new PrintWriter( - process.getOutputStream()); - stdinWriter.print(stdinArgs); - stdinWriter.flush(); - // We are done with this hook's input. Explicitly close its - // stdin now to kick off any blocking read the hook might have. - stdinWriter.close(); + OutputStream outputStream = process.getOutputStream(); + if (inRedirect != null) { + new StreamGobbler(inRedirect, outputStream) + .call(); } + outputStream.close(); return process.waitFor(); } catch (IOException e) { ioException = e; @@ -1194,30 +1223,27 @@ public abstract class FS { * </p> */ private static class StreamGobbler implements Callable<Void> { - private final BufferedReader reader; + private InputStream in; - private final BufferedWriter writer; + private OutputStream out; public StreamGobbler(InputStream stream, OutputStream output) { - this.reader = new BufferedReader(new InputStreamReader(stream)); - if (output == null) - this.writer = null; - else - this.writer = new BufferedWriter(new OutputStreamWriter(output)); + this.in = stream; + this.out = output; } public Void call() throws IOException { boolean writeFailure = false; - - String line = null; - while ((line = reader.readLine()) != null) { - // Do not try to write again after a failure, but keep reading - // as long as possible to prevent the input stream from choking. - if (!writeFailure && writer != null) { + byte buffer[] = new byte[4096]; + int readBytes; + while ((readBytes = in.read(buffer)) != -1) { + // Do not try to write again after a failure, but keep + // reading as long as possible to prevent the input stream + // from choking. + if (!writeFailure && out != null) { try { - writer.write(line); - writer.newLine(); - writer.flush(); + out.write(buffer, 0, readBytes); + out.flush(); } catch (IOException e) { writeFailure = true; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 548d239c8d..6d0318c029 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -399,20 +399,25 @@ public class FileUtils { * * @param path * @param target + * @return path to the created link * @throws IOException - * @since 3.0 + * @since 4.2 */ - public static void createSymLink(File path, String target) + public static Path createSymLink(File path, String target) throws IOException { Path nioPath = path.toPath(); if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) { - Files.delete(nioPath); + if (Files.isRegularFile(nioPath)) { + delete(path); + } else { + delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE); + } } if (SystemReader.getInstance().isWindows()) { target = target.replace('/', '\\'); } Path nioTarget = new File(target).toPath(); - Files.createSymbolicLink(nioPath, nioTarget); + return Files.createSymbolicLink(nioPath, nioTarget); } /** @@ -730,4 +735,29 @@ public class FileUtils { } return name; } + + /** + * Best-effort variation of {@link File#getCanonicalFile()} returning the + * input file if the file cannot be canonicalized instead of throwing + * {@link IOException}. + * + * @param file + * to be canonicalized; may be {@code null} + * @return canonicalized file, or the unchanged input file if + * canonicalization failed or if {@code file == null} + * @throws SecurityException + * if {@link File#getCanonicalFile()} throws one + * @since 4.2 + */ + public static File canonicalize(File file) { + if (file == null) { + return null; + } + try { + return file.getCanonicalFile(); + } catch (IOException e) { + return file; + } + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java index 4695111de9..0853e95366 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java @@ -80,9 +80,9 @@ public class RefList<T extends Ref> implements Iterable<Ref> { return (RefList<T>) EMPTY; } - private final Ref[] list; + final Ref[] list; - private final int cnt; + final int cnt; RefList(Ref[] list, int cnt) { this.list = list; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java index 5cc7e92c52..c72727b542 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java @@ -78,10 +78,10 @@ public class RefMap extends AbstractMap<String, Ref> { * All reference names in this map must start with this prefix. If the * prefix is not the empty string, it must end with a '/'. */ - private final String prefix; + final String prefix; /** Immutable collection of the packed references at construction time. */ - private RefList<Ref> packed; + RefList<Ref> packed; /** * Immutable collection of the loose references at construction time. @@ -91,7 +91,7 @@ public class RefMap extends AbstractMap<String, Ref> { * are typically unresolved, so they only tell us who their target is, but * not the current value of the target. */ - private RefList<Ref> loose; + RefList<Ref> loose; /** * Immutable collection of resolved symbolic references. @@ -101,11 +101,11 @@ public class RefMap extends AbstractMap<String, Ref> { * from {@link #loose}. Every entry in this list must be matched by an entry * in {@code loose}, otherwise it might be omitted by the map. */ - private RefList<Ref> resolved; + RefList<Ref> resolved; - private int size; + int size; - private boolean sizeIsValid; + boolean sizeIsValid; private Set<Entry<String, Ref>> entrySet; @@ -280,7 +280,7 @@ public class RefMap extends AbstractMap<String, Ref> { return name; } - private String toMapKey(Ref ref) { + String toMapKey(Ref ref) { String name = ref.getName(); if (0 < prefix.length()) name = name.substring(prefix.length()); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java index e2738c03f9..ca47f50fd9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -69,7 +69,7 @@ public abstract class TemporaryBuffer extends OutputStream { protected static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024; /** Chain of data, if we are still completely in-core; otherwise null. */ - private ArrayList<Block> blocks; + ArrayList<Block> blocks; /** * Maximum number of bytes we will permit storing in memory. @@ -51,7 +51,7 @@ <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit-parent</artifactId> <packaging>pom</packaging> - <version>4.1.2-SNAPSHOT</version> + <version>4.2.0-SNAPSHOT</version> <name>JGit - Parent</name> <url>${jgit-url}</url> @@ -192,7 +192,7 @@ <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format> <bundle-manifest>${project.build.directory}/META-INF/MANIFEST.MF</bundle-manifest> - <jgit-last-release-version>4.0.0.201506090130-r</jgit-last-release-version> + <jgit-last-release-version>4.1.0.201509280440-r</jgit-last-release-version> <jsch-version>0.1.53</jsch-version> <javaewah-version>0.7.9</javaewah-version> <junit-version>4.11</junit-version> @@ -202,7 +202,7 @@ <osgi-core-version>4.3.1</osgi-core-version> <servlet-api-version>3.1.0</servlet-api-version> <jetty-version>9.2.13.v20150730</jetty-version> - <clirr-version>2.6.1</clirr-version> + <japicmp-version>0.5.3</japicmp-version> <httpclient-version>4.3.6</httpclient-version> <slf4j-version>1.7.2</slf4j-version> <log4j-version>1.2.15</log4j-version> @@ -355,16 +355,6 @@ </plugin> <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - <version>${clirr-version}</version> - <configuration> - <comparisonVersion>${jgit-last-release-version}</comparisonVersion> - <minSeverity>info</minSeverity> - </configuration> - </plugin> - - <plugin> <groupId>org.eclipse.cbi.maven.plugins</groupId> <artifactId>eclipse-jarsigner-plugin</artifactId> <version>1.1.2</version> @@ -529,18 +519,17 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jxr-plugin</artifactId> - </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> + <version>2.5</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> + <version>3.0.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-report-plugin</artifactId> + <version>2.18.1</version> <configuration> <aggregate>true</aggregate> <alwaysGenerateSurefireReport>false</alwaysGenerateSurefireReport> |