aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.test
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit.test')
-rw-r--r--org.eclipse.jgit.test/META-INF/MANIFEST.MF83
-rw-r--r--org.eclipse.jgit.test/build.properties1
-rw-r--r--org.eclipse.jgit.test/exttst/org/eclipse/jgit/ignore/CGitVsJGitRandomIgnorePatternTest.java275
-rw-r--r--org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest-Proxy.launch20
-rw-r--r--org.eclipse.jgit.test/org.eclipse.jgit.test-WalkEncryptionTest.launch20
-rw-r--r--org.eclipse.jgit.test/pom.xml30
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.disabled.properties48
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.bucket.json20
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/jgit-s3-config.policy.user.json24
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-0.properties11
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-1.properties14
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/jgit-s3-connection-v-2.properties48
-rw-r--r--org.eclipse.jgit.test/tst-rsrc/log4j.properties9
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java47
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java50
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/FastIgnoreRuleTest.java1
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreNodeTest.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreRuleSpecialCasesTest.java55
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsInserterTest.java53
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java8
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java1
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/GcCommitSelectionTest.java271
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparerTest.java136
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java416
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java1295
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java21
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java171
31 files changed, 3086 insertions, 62 deletions
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;
+ }
+}