Add validator that checks for duplicate pathnames
Project owner can now configure duplicate pathnames validator. Commits
which contain duplicate pathnames will be rejected. If y and z are
pathnames and y.equalsIgnoreCase(z), then y and z are duplicates.
Change-Id: Iaac644a07fc88e855cc9a041f0237157bda9d010
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidator.java
new file mode 100644
index 0000000..bf64f63
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidator.java
@@ -0,0 +1,250 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.uploadvalidator;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.config.ProjectConfigEntry;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+public class DuplicatePathnameValidator implements CommitValidationListener {
+
+ public static AbstractModule module() {
+ return new AbstractModule() {
+ private List<String> getAvailableLocales() {
+ return Lists.transform(Arrays.asList(Locale.getAvailableLocales()),
+ new Function<Locale, String>() {
+ @Override
+ public String apply(Locale input) {
+ return input.toString();
+ }
+ });
+ }
+
+ @Override
+ protected void configure() {
+ DynamicSet.bind(binder(), CommitValidationListener.class)
+ .to(DuplicatePathnameValidator.class);
+ bind(ProjectConfigEntry.class)
+ .annotatedWith(Exports.named(KEY_REJECT_DUPLICATE_PATHNAMES))
+ .toInstance(new ProjectConfigEntry("Reject Duplicate Pathnames",
+ null, ProjectConfigEntry.Type.BOOLEAN, null, false,
+ "Pushes of commits that contain duplicate pathnames, or that "
+ + "contain duplicates of existing pathnames will be "
+ + "rejected. Pathnames y and z are considered to be "
+ + "duplicates if they are equal, case-insensitive."));
+ bind(ProjectConfigEntry.class)
+ .annotatedWith(Exports.named(KEY_REJECT_DUPLICATE_PATHNAMES_LOCALE))
+ .toInstance(new ProjectConfigEntry("Reject Duplicate Pathnames Locale",
+ "en", ProjectConfigEntry.Type.STRING, getAvailableLocales(), false,
+ "To avoid problems caused by comparing pathnames with different "
+ + "locales it is possible to use a specific locale. The "
+ + "default is English (en)."));
+ }
+ };
+ }
+
+ public static String KEY_REJECT_DUPLICATE_PATHNAMES =
+ "rejectDuplicatePathnames";
+ public static String KEY_REJECT_DUPLICATE_PATHNAMES_LOCALE =
+ "rejectDuplicatePathnamesLocale";
+
+ @VisibleForTesting
+ static boolean isActive(PluginConfig cfg) {
+ return cfg.getBoolean(KEY_REJECT_DUPLICATE_PATHNAMES, false);
+ }
+
+ @VisibleForTesting
+ static Locale getLocale(PluginConfig cfg) {
+ return Locale.forLanguageTag(
+ cfg.getString(KEY_REJECT_DUPLICATE_PATHNAMES_LOCALE, "en"));
+ }
+
+ @VisibleForTesting
+ Map<String, String> allPaths(Collection<String> leafs) {
+ Map<String, String> paths = new HashMap<>();
+ for (String cp : leafs) {
+ int n = cp.indexOf('/');
+ while (n > -1) {
+ String s = cp.substring(0, n);
+ paths.put(s.toLowerCase(locale), s);
+ n = cp.indexOf('/', n + 1);
+ }
+ paths.put(cp.toLowerCase(locale), cp);
+ }
+ return paths;
+ }
+
+ Set<String> allParentFolders(Collection<String> paths) {
+ Set<String> folders = new HashSet<>();
+ for (String cp : paths) {
+ int n = cp.indexOf('/');
+ while (n > -1) {
+ String s = cp.substring(0, n);
+ folders.add(s);
+ n = cp.indexOf('/', n + 1);
+ }
+ }
+ return folders;
+ }
+
+ @VisibleForTesting
+ static CommitValidationMessage conflict(String f1, String f2) {
+ return new CommitValidationMessage(f1 + ": pathname conflicts with " + f2,
+ true);
+ }
+
+ private static boolean isDeleted(TreeWalk tw) {
+ return FileMode.MISSING.equals(tw.getRawMode(0));
+ }
+
+ private final String pluginName;
+ private final PluginConfigFactory cfgFactory;
+ private final GitRepositoryManager repoManager;
+
+ private Locale locale;
+
+ @VisibleForTesting
+ void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ @Inject
+ DuplicatePathnameValidator(@PluginName String pluginName,
+ PluginConfigFactory cfgFactory, GitRepositoryManager repoManager) {
+ this.pluginName = pluginName;
+ this.cfgFactory = cfgFactory;
+ this.repoManager = repoManager;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(
+ CommitReceivedEvent receiveEvent) throws CommitValidationException {
+ try {
+ PluginConfig cfg = cfgFactory
+ .getFromProjectConfig(receiveEvent.project.getNameKey(), pluginName);
+ if (!isActive(cfg)) {
+ return Collections.emptyList();
+ }
+ locale = getLocale(cfg);
+ try (Repository repo =
+ repoManager.openRepository(receiveEvent.project.getNameKey())) {
+ List<CommitValidationMessage> messages =
+ performValidation(repo, receiveEvent.commit);
+ if (!messages.isEmpty()) {
+ throw new CommitValidationException("contains duplicate pathnames",
+ messages);
+ }
+ }
+ } catch (NoSuchProjectException | IOException e) {
+ throw new CommitValidationException(
+ "failed to check for duplicate pathnames", e);
+ }
+ return Collections.emptyList();
+ }
+
+ @VisibleForTesting
+ List<CommitValidationMessage> performValidation(Repository repo, RevCommit c)
+ throws IOException {
+ List<CommitValidationMessage> messages = new LinkedList<>();
+
+ Set<String> pathnames = CommitUtils.getChangedPaths(repo, c);
+ checkForDuplicatesInSet(pathnames, messages);
+ if (!messages.isEmpty() || c.getParentCount() == 0) {
+ return messages;
+ }
+
+ try (TreeWalk tw = new TreeWalk(repo)) {
+ tw.setRecursive(false);
+ tw.addTree(c.getTree());
+ checkForDuplicatesAgainstTheWholeTree(tw, pathnames, messages);
+ }
+ return messages;
+ }
+
+ @VisibleForTesting
+ void checkForDuplicatesAgainstTheWholeTree(TreeWalk tw,
+ Set<String> changed, List<CommitValidationMessage> messages)
+ throws IOException {
+ Map<String, String> all = allPaths(changed);
+
+ while (tw.next()) {
+ String currentPath = tw.getPathString();
+
+ if (isDeleted(tw)) {
+ continue;
+ }
+
+ String potentialDuplicate = all.get(currentPath.toLowerCase(locale));
+ if (potentialDuplicate == null) {
+ continue;
+ } else if (potentialDuplicate.equals(currentPath)) {
+ if (tw.isSubtree()) {
+ tw.enterSubtree();
+ }
+ continue;
+ } else {
+ messages.add(conflict(potentialDuplicate, currentPath));
+ }
+ }
+ }
+
+ private void checkForDuplicatesInSet(Set<String> files,
+ List<CommitValidationMessage> messages) {
+ Set<String> filesAndFolders = Sets.newHashSet(files);
+ filesAndFolders.addAll(allParentFolders(files));
+ Map<String, String> seen = new HashMap<>();
+ for (String file : filesAndFolders) {
+ String lc = file.toLowerCase(locale);
+ String duplicate = seen.get(lc);
+ if (duplicate != null) {
+ messages.add(conflict(duplicate, file));
+ } else {
+ seen.put(lc, file);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
index 5d462b2..f63a707 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
@@ -23,6 +23,7 @@
install(new PatternCacheModule());
install(ContentTypeUtil.module());
install(BlockedKeywordValidator.module());
+ install(DuplicatePathnameValidator.module());
install(FileExtensionValidator.module());
install(FooterValidator.module());
install(InvalidFilenameValidator.module());
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index dd2e4d4..5e4fdf3 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -6,6 +6,7 @@
- invalid filenames
- blocked keywords
- blocked content types
+- reject duplicate pathnames
- reject Windows line endings
- symbolic links
- reject submodules
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 702a644..faa4498 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -30,6 +30,8 @@
maxPathLength = 200
rejectSymlink = false
rejectSubmodule = false
+ rejectDuplicatePathnames = false
+ rejectDuplicatePathnamesLocale = en
```
plugin.@PLUGIN@.blockedFileExtension
@@ -177,3 +179,34 @@
interpreted as a blacklist.
Defined patterns are *not* inherited by child projects.
+
+plugin.@PLUGIN@.rejectDuplicatePathnames
+: Reject duplicate pathnames.
+
+ This check looks for duplicate pathnames which only differ in case
+ in the tree of the commit as these can cause problems on case
+ insensitive filesystems commonly used e.g. on Windows or Mac. If the
+ check finds duplicate pathnames the push will be rejected.
+
+ The default value is false. This means duplicate pathnames ignoring
+ case are allowed.
+
+ This option is *not* inherited by child projects.
+
+plugin.@PLUGIN@.rejectDuplicatePathnamesLocale
+: Reject duplicate pathnames locale.
+
+ When the validator checks for duplicate pathnames it will convert
+ the pathnames to lower case. In some cases this leads to a [problem][5].
+
+ To avoid these kind of problems, this option is used to specify a
+ locale which is used when converting a pathname to lower case.
+
+ Full list of supported locales can be found [here][6].
+
+ The default value is "en" (English).
+
+ This option is *not* inherited by child projects.
+
+[5]: http://bugs.java.com/view_bug.do?bug_id=6208680
+[6]: http://www.oracle.com/technetwork/java/javase/javase7locales-334809.html
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidatorTest.java
new file mode 100644
index 0000000..f3be61b
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidatorTest.java
@@ -0,0 +1,197 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.uploadvalidator;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.uploadvalidator.DuplicatePathnameValidator.conflict;
+import static com.googlesource.gerrit.plugins.uploadvalidator.TestUtils.EMPTY_CONTENT;
+import static com.googlesource.gerrit.plugins.uploadvalidator.TestUtils.EMPTY_PLUGIN_CONFIG;
+import static com.googlesource.gerrit.plugins.uploadvalidator.TestUtils.createDirCacheEntry;
+import static com.googlesource.gerrit.plugins.uploadvalidator.TestUtils.createEmptyDirCacheEntries;
+import static com.googlesource.gerrit.plugins.uploadvalidator.TestUtils.makeCommit;
+import static com.googlesource.gerrit.plugins.uploadvalidator.TestUtils.transformMessage;
+import static com.googlesource.gerrit.plugins.uploadvalidator.TestUtils.transformMessages;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+public class DuplicatePathnameValidatorTest extends ValidatorTestCase {
+ private static final List<String> INITIAL_PATHNAMES = ImmutableList.of(
+ "a" , "ab",
+ "f1/a", "f1/ab",
+ "f2/a", "f2/ab", "f2/sF1/a", "f2/sF1/ab");
+
+ private TestRepository<Repository> testRepo;
+ private List<String> vistedPaths = Lists.newArrayList();
+ private List<CommitValidationMessage> messages = Lists.newArrayList();
+ private Set<String> changedPaths;
+ private DuplicatePathnameValidator validator;
+
+ private void runCheck(List<String> existingTreePaths, Set<String> testPaths,
+ List<CommitValidationMessage> messages, List<String> visitedPaths)
+ throws Exception {
+ RevCommit c = makeCommit(
+ createEmptyDirCacheEntries(existingTreePaths, testRepo), testRepo);
+ try (TreeWalk tw = new TreeWalk(repo)) {
+ tw.setRecursive(false);
+ tw.addTree(c.getTree());
+ tw.setFilter(new ListVisitedPathsFilter(visitedPaths));
+ validator.checkForDuplicatesAgainstTheWholeTree(tw, testPaths, messages);
+ }
+ }
+
+ @Override
+ @Before
+ public void init() throws IOException {
+ super.init();
+ testRepo = new TestRepository<>(repo);
+ validator = new DuplicatePathnameValidator(null, null, null);
+ validator.setLocale(Locale.ENGLISH);
+ }
+
+ @Test
+ public void testSkipSubTreesWithImproperPrefix() throws Exception {
+ changedPaths = Sets.newHashSet("f1/A");
+ runCheck(INITIAL_PATHNAMES, changedPaths, messages, vistedPaths);
+ assertThat(transformMessages(messages))
+ .containsExactly(transformMessage(conflict("f1/A", "f1/a")));
+ assertThat(vistedPaths).containsExactlyElementsIn(ImmutableList.of("a",
+ "ab", "f1", "f1/a", "f1/ab", "f2"));
+ }
+
+ @Test
+ public void testFindConflictingSubtree() throws Exception {
+ changedPaths = Sets.newHashSet("F1/a");
+ runCheck(INITIAL_PATHNAMES, changedPaths, messages, vistedPaths);
+ assertThat(transformMessages(messages))
+ .containsExactly(transformMessage(conflict("F1", "f1")));
+ assertThat(vistedPaths).containsExactlyElementsIn(
+ ImmutableList.of("a", "ab", "f1", "f2"));
+ }
+
+ @Test
+ public void testFindConflictingSubtree2() throws Exception {
+ changedPaths = Sets.newHashSet("f2/sf1", "F1/a");
+ runCheck(INITIAL_PATHNAMES, changedPaths, messages, vistedPaths);
+ assertThat(transformMessages(messages)).containsExactly(
+ transformMessage(conflict("F1", "f1")),
+ transformMessage(conflict("f2/sf1", "f2/sF1")));
+ assertThat(vistedPaths).containsExactlyElementsIn(
+ ImmutableList.of("a", "ab", "f1", "f2", "f2/a", "f2/ab", "f2/sF1"));
+ }
+
+ @Test
+ public void testFindDuplicates() throws Exception {
+ changedPaths = Sets.newHashSet("AB", "f1/A", "f2/Ab");
+ runCheck(INITIAL_PATHNAMES, changedPaths, messages, vistedPaths);
+ assertThat(transformMessages(messages)).containsExactly(
+ transformMessage(conflict("AB", "ab")),
+ transformMessage(conflict("f1/A", "f1/a")),
+ transformMessage(conflict("f2/Ab", "f2/ab")));
+ assertThat(vistedPaths).containsExactlyElementsIn(
+ ImmutableList.of("a", "ab", "f1", "f1/a", "f1/ab",
+ "f2", "f2/a", "f2/ab", "f2/sF1"));
+ }
+
+ @Test
+ public void testFindNoDuplicates() throws Exception {
+ changedPaths = Sets.newHashSet("a", "ab", "f1/ab");
+ runCheck(INITIAL_PATHNAMES, changedPaths, messages, vistedPaths);
+ assertThat(messages).isEmpty();
+ assertThat(vistedPaths).containsExactlyElementsIn(ImmutableList.of("a",
+ "ab", "f1", "f1/a", "f1/ab", "f2"));
+ }
+
+ @Test
+ public void testCheckInsideOfCommit() throws Exception {
+ List<String> filenames = Lists.newArrayList(INITIAL_PATHNAMES);
+ // add files with conflicting pathnames
+ filenames.add("A");
+ filenames.add("F1/ab");
+ filenames.add("f2/sF1/aB");
+ RevCommit c =
+ makeCommit(createEmptyDirCacheEntries(filenames, testRepo), testRepo);
+ List<CommitValidationMessage> m = validator.performValidation(repo, c);
+ assertThat(m).hasSize(4);
+ // During checking inside of the commit it's unknown which file is checked
+ // first, because of that, both capabilities must be checked.
+ assertThat(transformMessages(m)).containsAnyOf(
+ transformMessage(conflict("A", "a")),
+ transformMessage(conflict("a", "A")));
+
+ assertThat(transformMessages(m)).containsAnyOf(
+ transformMessage(conflict("F1", "f1")),
+ transformMessage(conflict("f1", "F1")));
+
+ assertThat(transformMessages(m)).containsAnyOf(
+ transformMessage(conflict("F1/ab", "f1/ab")),
+ transformMessage(conflict("f1/ab", "F1/ab")));
+
+ assertThat(transformMessages(m)).containsAnyOf(
+ transformMessage(
+ conflict("f2/sF1/aB", "f2/sF1/ab")),
+ transformMessage(
+ conflict("f2/sF1/ab", "f2/sF1/aB")));
+ }
+
+ @Test
+ public void testCheckRenaming() throws Exception {
+ RevCommit c = makeCommit(
+ createEmptyDirCacheEntries(INITIAL_PATHNAMES, testRepo), testRepo);
+ DirCacheEntry[] entries = new DirCacheEntry[INITIAL_PATHNAMES.size()];
+ for (int x = 0; x < INITIAL_PATHNAMES.size(); x++) {
+ // Rename files
+ entries[x] = createDirCacheEntry(INITIAL_PATHNAMES.get(x).toUpperCase(),
+ EMPTY_CONTENT, testRepo);
+ }
+ RevCommit c1 = makeCommit(entries, testRepo, c);
+ List<CommitValidationMessage> m = validator.performValidation(repo, c1);
+ assertThat(m).isEmpty();
+ }
+
+ @Test
+ public void validatorInactiveWhenConfigEmpty() {
+ assertThat(DuplicatePathnameValidator.isActive(EMPTY_PLUGIN_CONFIG))
+ .isFalse();
+ }
+
+ @Test
+ public void defaultLocale() {
+ assertThat(DuplicatePathnameValidator.getLocale(EMPTY_PLUGIN_CONFIG))
+ .isEqualTo(Locale.ENGLISH);
+ }
+
+ @Test
+ public void testGetParentFolder() {
+ assertThat(validator.allParentFolders(INITIAL_PATHNAMES))
+ .containsExactlyElementsIn(
+ ImmutableList.of("f1", "f2", "f2/sF1"));
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ListVisitedPathsFilter.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ListVisitedPathsFilter.java
new file mode 100644
index 0000000..282590b
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ListVisitedPathsFilter.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.uploadvalidator;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ListVisitedPathsFilter extends TreeFilter {
+ private List<String> visitedPaths = null;
+
+ public ListVisitedPathsFilter(List<String> visitedPaths) {
+ super();
+ this.visitedPaths = visitedPaths;
+ }
+
+ @Override
+ public boolean include(TreeWalk walker)
+ throws MissingObjectException, IncorrectObjectTypeException, IOException {
+ visitedPaths.add(walker.getPathString());
+ return true;
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return false;
+ }
+
+ @Override
+ public TreeFilter clone() {
+ return this;
+ }
+
+ public List<String> getVisitedPaths() {
+ return visitedPaths;
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java
index 9590bdb..0f102fa 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java
@@ -14,6 +14,7 @@
package com.googlesource.gerrit.plugins.uploadvalidator;
+import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
@@ -27,9 +28,12 @@
import org.eclipse.jgit.api.RmCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import java.io.File;
@@ -44,6 +48,17 @@
public static final PluginConfig EMPTY_PLUGIN_CONFIG =
new PluginConfig("", new Config());
+ protected static final byte[] EMPTY_CONTENT = "".getBytes(Charsets.UTF_8);
+
+ private static final Function<CommitValidationMessage, String> MESSAGE_TRANSFORMER =
+ new Function<CommitValidationMessage, String>() {
+ @Override
+ public String apply(CommitValidationMessage input) {
+ String pre = (input.isError()) ? "ERROR: " : "MSG: ";
+ return pre + input.getMessage();
+ }
+ };
+
public static final LoadingCache<String, Pattern> PATTERN_CACHE =
CacheBuilder.newBuilder().build(new PatternCacheModule.Loader());
@@ -110,15 +125,44 @@
return new File(repo.getDirectory().getParent(), name);
}
+ public static String transformMessage(CommitValidationMessage messages) {
+ return MESSAGE_TRANSFORMER.apply(messages);
+ }
+
public static List<String> transformMessages(
List<CommitValidationMessage> messages) {
- return Lists.transform(messages,
- new Function<CommitValidationMessage, String>() {
- @Override
- public String apply(CommitValidationMessage input) {
- String pre = (input.isError()) ? "ERROR: " : "MSG: ";
- return pre + input.getMessage();
- }
- });
+ return Lists.transform(messages, MESSAGE_TRANSFORMER);
+ }
+
+ public static DirCacheEntry[] createEmptyDirCacheEntries(
+ List<String> filenames, TestRepository<Repository> repo)
+ throws Exception {
+ DirCacheEntry[] entries = new DirCacheEntry[filenames.size()];
+ for (int x = 0; x < filenames.size(); x++) {
+ entries[x] = createDirCacheEntry(filenames.get(x), EMPTY_CONTENT, repo);
+ }
+ return entries;
+
+ }
+
+ public static DirCacheEntry createDirCacheEntry(String pathname,
+ byte[] content, TestRepository<Repository> repo)
+ throws Exception {
+ return repo.file(pathname, repo.blob(content));
+ }
+
+ public static RevCommit makeCommit(DirCacheEntry[] entries,
+ TestRepository<Repository> repo) throws Exception {
+ return makeCommit(entries, repo, (RevCommit[]) null);
+ }
+
+ public static RevCommit makeCommit(DirCacheEntry[] entries,
+ TestRepository<Repository> repo, RevCommit... parents)
+ throws Exception {
+ final RevTree ta = repo.tree(entries);
+ RevCommit c =
+ (parents == null) ? repo.commit(ta) : repo.commit(ta, parents);
+ repo.parseBody(c);
+ return c;
}
}