diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0628185 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/* +target +*.iml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 3072d1c..3991825 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,25 @@ The following example rejects all files matching the pattern **readme.md** when ![File Size Hook Configuration](screenshots/file-hooks-plugin-filename-hook-configuration.png) # Releases +5.0.4 (2022-07-28) + +Fix bug where refs were not collected properly + +5.0.3 (2022-07-27) + +Code improvements as suggested by Intellij + +5.0.2 (2022-07-22) + +Add logging support via logback, logs will now be written to atlassian-bitbucket.log when debug is enabled in the Bitbucket GUI + +5.0.1 (2022-07-20) + +Minor updates + +5.0.0 (2022-07-01) + +Updates to support Bitbucket 8.x 3.3.2 (2018-04-15) @@ -108,6 +127,32 @@ The following example rejects all files matching the pattern **readme.md** when * Reject commits containing files which exceed a configurable file size. Files can be identified by regular expressions. +# Developer Guide + +### Building the plugin + +The following tools are required to build the plugin: +``` +- java 1.8 +- atlassian sdk +``` +Instructions on how to install the Atlassian SDK can be found [here](https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-linux-or-mac-system/) + +Clone the repository: + +``` +git clone https://github.com/christiangalsterer/stash-filehooks-plugin.git +``` +Edit the pom.xml in the root of the checkout, and change the line (change to match the version of Bitbucket you want to build against): + +``` +7.13.0 +``` +Build the plugin +``` +atlas-mvn clean install +``` + # Roadmap diff --git a/pom.xml b/pom.xml index 75c7b4f..45ee654 100644 --- a/pom.xml +++ b/pom.xml @@ -2,8 +2,8 @@ 4.0.0 org.christiangalsterer - stash-filehooks-plugin - 3.3.2 + bitbucket-filehooks-plugin + 5.0.4 Christian Galsterer https://github.com/christiangalsterer/stash-filehooks-plugin @@ -13,6 +13,10 @@ Krzysztof Malinowski Motorola Solutions, Inc. + + John Lawlor + Openet Telecom Ltd. + @@ -25,9 +29,9 @@ atlassian-plugin - 5.4.1 + 8.1.1 ${bitbucket.version} - 6.3.0 + 8.0.0 1.2.3 3.3 3.1.0 @@ -41,6 +45,26 @@ HEAD + + + atlassian + https://packages.atlassian.com/maven-3rdparty/ + + + + atlassian-public + https://packages.atlassian.com/mvn/maven-external/ + + true + never + warn + + + true + warn + + + @@ -61,6 +85,30 @@ bitbucket-spi ${bitbucket.version} provided + + + com.atlassian.bitbucket.server + bitbucket-scm-common + ${bitbucket.version} + provided + + + com.atlassian.bitbucket.server + bitbucket-util + ${bitbucket.version} + provided + + + com.atlassian.bitbucket.server + bitbucket-page-objects + ${bitbucket.version} + provided + + + com.atlassian.sal + sal-api + 2.10.0 + provided @@ -71,6 +119,11 @@ sal-api provided + + com.atlassian.bitbucket.server + bitbucket-util + provided + com.atlassian.bitbucket.server bitbucket-api @@ -113,6 +166,21 @@ ${commons-lang.version} provided + + com.atlassian.utils + atlassian-processutils + 1.8.3 + + + com.google.guava + guava + r05 + + + com.google.code.findbugs + jsr305 + 3.0.2 + diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CachingResolver.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CachingResolver.java index 81fab7b..e12b2d1 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CachingResolver.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CachingResolver.java @@ -19,7 +19,7 @@ * @param type of value elements to be stored in cache */ public class CachingResolver { - private Map cache = new HashMap<>(); + private final Map cache = new HashMap<>(); /** * Resolves requested key to a previously cached value. diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CatFileBatchCheckHandler.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CatFileBatchCheckHandler.java index 07e3644..897c3bc 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CatFileBatchCheckHandler.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/CatFileBatchCheckHandler.java @@ -5,8 +5,10 @@ import com.atlassian.bitbucket.scm.CommandInputHandler; import com.atlassian.bitbucket.scm.CommandOutputHandler; import com.atlassian.utils.process.IOUtils; -import com.atlassian.utils.process.ProcessException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -23,6 +25,8 @@ public class CatFileBatchCheckHandler extends LineReaderOutputHandler implements this.changesets = changesets; } + private static final Logger LOGGER = LoggerFactory.getLogger(CatFileBatchCheckHandler.class); + @Override public Map getOutput() { return values; @@ -32,7 +36,7 @@ public Map getOutput() { public void complete() { try { super.complete(); - } catch (ProcessException e) { + } catch (IOException e) { throw new RuntimeException(e); } } @@ -40,7 +44,7 @@ public void complete() { @Override protected void processReader(LineReader reader) throws IOException { String line; - while ((line = resetWatchdogAndReadLine(reader)) != null) { + while ((line = reader.readLine()) != null) { String[] split = line.split(" "); // Only process blobs (ie files), ignore folders if (split.length == 3 && split[1].equals("blob")) { @@ -50,7 +54,7 @@ protected void processReader(LineReader reader) throws IOException { } @Override - public void process(OutputStream input) { + public void process(@Nonnull OutputStream input) { try { for (String c : changesets) { input.write(c.getBytes(StandardCharsets.UTF_8.name())); diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/ChangesetServiceImpl.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/ChangesetServiceImpl.java index 9881ad4..2606872 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/ChangesetServiceImpl.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/ChangesetServiceImpl.java @@ -3,17 +3,20 @@ import com.atlassian.bitbucket.commit.Changeset; import com.atlassian.bitbucket.commit.Commit; import com.atlassian.bitbucket.content.Change; -import com.atlassian.bitbucket.repository.Ref; -import com.atlassian.bitbucket.repository.RefChange; -import com.atlassian.bitbucket.repository.Repository; +import com.atlassian.bitbucket.repository.*; import com.atlassian.bitbucket.scm.ChangesetsCommandParameters; import com.atlassian.bitbucket.scm.CommitsCommandParameters; +import com.atlassian.bitbucket.scm.RefsCommandParameters; import com.atlassian.bitbucket.scm.ScmService; import com.atlassian.bitbucket.util.PageRequest; import com.atlassian.bitbucket.util.PageUtils; import com.atlassian.bitbucket.util.PagedIterable; import com.google.common.collect.Iterables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; +import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -23,6 +26,7 @@ public class ChangesetServiceImpl implements ChangesetService { private static final PageRequest PAGE_REQUEST = PageUtils.newRequest(0, PageRequest.MAX_PAGE_LIMIT); private static final int MAX_CHANGES_PER_COMMIT = PageRequest.MAX_PAGE_LIMIT; + private static final Logger LOGGER = LoggerFactory.getLogger(ChangesetServiceImpl.class); private final ScmService scmService; public ChangesetServiceImpl(ScmService scmService) { @@ -31,6 +35,7 @@ public ChangesetServiceImpl(ScmService scmService) { @Override public Iterable getChanges(Iterable refChanges, final Repository repository) { + LOGGER.info("Getting changes as iterable"); List changes = new ArrayList<>(); Iterable commits = getCommitsBetween(repository, refChanges); @@ -38,23 +43,27 @@ public Iterable getChanges(Iterable refChanges, final Reposit Iterables.addAll(changes, values); } + LOGGER.info("Returning iterable changes"); return changes; } @Override public Map> getChanges(final Repository repository, Iterable commits) { + LOGGER.info("Getting changes as map"); Map> changesByCommit = new HashMap<>(); Iterable changesets = getChangesets(repository, commits); for (Changeset changeset : changesets) { - changesByCommit.put(changeset.getToCommit(), changeset.getChanges().getValues()); + changesByCommit.put(changeset.getToCommit(), Objects.requireNonNull(changeset.getChanges()).getValues()); } - + LOGGER.info("Changes by commit : " + changesByCommit.size()); + LOGGER.info("Returning map of changes"); return changesByCommit; } @Override public Set getCommitsBetween(final Repository repository, Iterable refChanges) { + LOGGER.info("Getting commits between"); Set commits = new HashSet<>(); CommitsCommandParameters.Builder builder = new CommitsCommandParameters.Builder().withMessages(false); @@ -85,31 +94,44 @@ public Set getCommitsBetween(final Repository repository, Iterable getExistingRefs(final Repository repository) { + LOGGER.info("Getting refs"); Set refs = new HashSet<>(); - scmService.getCommandFactory(repository).heads(refs::add).call(); + RefCallback refCallback = new RefCallback() { + @Override + public boolean onRef(@Nonnull Ref ref) throws IOException { + refs.add(ref); + return true; + } + }; + scmService.getCommandFactory(repository).refs(new RefsCommandParameters.Builder().build(), refCallback).call(); + + LOGGER.info("Returning refs, count: " + refs.size()); return refs; } private Iterable getChangesets(final Repository repository, Iterable commits) { + LOGGER.info("Getting change sets"); Iterable changesets = new ArrayList<>(); final Collection commitIds = StreamSupport.stream(commits.spliterator(), false) .map(Commit::getId) .collect(Collectors.toSet()); - + LOGGER.info("Number of commit ids: " + commitIds.size()); if (!commitIds.isEmpty()) { - changesets = new PagedIterable<>(pageRequest -> scmService.getCommandFactory(repository).changesets( + changesets = new PagedIterable<>(pageRequest -> Objects.requireNonNull(scmService.getCommandFactory(repository).changesets( new ChangesetsCommandParameters.Builder() .commitIds(commitIds) .maxChangesPerCommit(MAX_CHANGES_PER_COMMIT) .maxMessageLength(0) .build(), - pageRequest).call(), PAGE_REQUEST); + pageRequest).call()), PAGE_REQUEST); } + LOGGER.info("Returning change sets"); return changesets; } } diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHook.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHook.java index fd57339..dbe4ba0 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHook.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHook.java @@ -1,48 +1,45 @@ package org.christiangalsterer.stash.filehooks.plugin.hook; -import com.atlassian.bitbucket.commit.Commit; -import com.atlassian.bitbucket.commit.CommitRequest; -import com.atlassian.bitbucket.commit.CommitService; -import com.atlassian.bitbucket.content.AbstractChangeCallback; -import com.atlassian.bitbucket.content.Change; -import com.atlassian.bitbucket.content.ChangeType; -import com.atlassian.bitbucket.content.ChangesRequest; -import com.atlassian.bitbucket.hook.HookResponse; -import com.atlassian.bitbucket.hook.repository.PreReceiveRepositoryHook; -import com.atlassian.bitbucket.hook.repository.RepositoryHookContext; -import com.atlassian.bitbucket.hook.repository.RepositoryMergeRequestCheck; -import com.atlassian.bitbucket.hook.repository.RepositoryMergeRequestCheckContext; -import com.atlassian.bitbucket.i18n.I18nService; -import com.atlassian.bitbucket.pull.PullRequest; -import com.atlassian.bitbucket.pull.PullRequestRef; -import com.atlassian.bitbucket.repository.RefChange; -import com.atlassian.bitbucket.repository.Repository; -import com.atlassian.bitbucket.scm.git.GitScmConfig; -import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory; -import com.atlassian.bitbucket.scm.pull.MergeRequest; -import com.atlassian.bitbucket.setting.RepositorySettingsValidator; -import com.atlassian.bitbucket.setting.Settings; -import com.atlassian.bitbucket.setting.SettingsValidationErrors; -import com.google.common.base.Strings; -import com.google.common.collect.*; - -import javax.annotation.Nonnull; - -import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Optional; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import static org.christiangalsterer.stash.filehooks.plugin.hook.Predicates.*; - +/** + * import com.atlassian.bitbucket.commit.Commit; + * import com.atlassian.bitbucket.commit.CommitRequest; + * import com.atlassian.bitbucket.commit.CommitService; + * import com.atlassian.bitbucket.content.AbstractChangeCallback; + * import com.atlassian.bitbucket.content.Change; + * import com.atlassian.bitbucket.content.ChangeType; + * import com.atlassian.bitbucket.content.ChangesRequest; + * import com.atlassian.bitbucket.hook.HookResponse; + * import com.atlassian.bitbucket.hook.repository.RepositoryHookContext; + * import com.atlassian.bitbucket.hook.repository.RepositoryMergeRequestCheckContext; + * import com.atlassian.bitbucket.i18n.I18nService; + * import com.atlassian.bitbucket.pull.PullRequest; + * import com.atlassian.bitbucket.pull.PullRequestRef; + * import com.atlassian.bitbucket.repository.RefChange; + * import com.atlassian.bitbucket.repository.Repository; + * import com.atlassian.bitbucket.scm.pull.MergeRequest; + * import com.atlassian.bitbucket.setting.Settings; + * import com.atlassian.bitbucket.setting.SettingsValidationErrors; + * import com.google.common.base.Strings; + * + * import javax.annotation.Nonnull; + * + * import java.io.IOException; + * import java.util.Collection; + * import java.util.HashSet; + * import java.util.Iterator; + * import java.util.Optional; + * import java.util.regex.Pattern; + * import java.util.regex.PatternSyntaxException; + * import java.util.stream.Collectors; + * import java.util.stream.StreamSupport; + * + * import static org.christiangalsterer.stash.filehooks.plugin.hook.Predicates.*; + */ /** * Checks the name and path of a file in the pre-receive phase and rejects the push when the changeset contains files which match the configured file name pattern. */ + +/** public class FileNameHook implements PreReceiveRepositoryHook, RepositorySettingsValidator, RepositoryMergeRequestCheck { private static final String SETTINGS_INCLUDE_PATTERN = "pattern"; @@ -51,14 +48,14 @@ public class FileNameHook implements PreReceiveRepositoryHook, RepositorySetting private final ChangesetService changesetService; private final I18nService i18n; - private final CommitService commitService; + private final CommitService commitService; private final MergeBaseResolver mergeBaseResolver; public FileNameHook(GitCommandBuilderFactory builderFactory, CommitService commitService, ChangesetService changesetService, I18nService i18n, GitScmConfig gitScmConfig) { this.changesetService = changesetService; this.i18n = i18n; this.commitService = commitService; - this.mergeBaseResolver = new MergeBaseResolver(builderFactory, gitScmConfig, commitService); + this.mergeBaseResolver = new MergeBaseResolver(builderFactory, gitScmConfig, commitService); } @Override @@ -136,28 +133,24 @@ public void validate(Settings settings, SettingsValidationErrors errors, Reposit } } } - - /** - * Callback, collecting all the paths, changed in the requested change - * range. - */ + private static class ChangedPathsCollector extends AbstractChangeCallback { private final Collection changedPaths = new HashSet<>(); - - @Override + + @Override public boolean onChange(Change change) throws IOException { - if (change.getType() != ChangeType.DELETE) { + if (change.getType() != ChangeType.DELETE) { changedPaths.add(change.getPath().toString()); - } - return true; - } - + } + return true; + } + Collection getChangedPaths() { - return changedPaths; - } - - } - + return changedPaths; + } + + } + private String getPullRequestError(Collection filteredFiles) { final StringBuilder sb = new StringBuilder(); final Iterator iter = filteredFiles.iterator(); @@ -169,38 +162,40 @@ private String getPullRequestError(Collection filteredFiles) { } return sb.toString(); } - - @Override - public void check(RepositoryMergeRequestCheckContext context) { - final MergeRequest request = context.getMergeRequest(); - final PullRequest pr = request.getPullRequest(); - final Commit prFrom = getChangeSet(pr.getFromRef()); - final Commit prTo = getChangeSet(pr.getToRef()); - final Commit base = mergeBaseResolver.findMergeBase(prFrom, prTo); - final FileNameHookSetting setting = getSettings(context.getSettings()); - - final ChangesRequest.Builder builder = new ChangesRequest.Builder(prFrom.getRepository(), prFrom.getId()); - if (base.getId() != null) { - builder.sinceId(base.getId()); - } - final ChangesRequest pathsRequest = builder.build(); + + @Override + public void check(RepositoryMergeRequestCheckContext context) { + final MergeRequest request = context.getMergeRequest(); + final PullRequest pr = request.getPullRequest(); + final Commit prFrom = getChangeSet(pr.getFromRef()); + final Commit prTo = getChangeSet(pr.getToRef()); + final Commit base = mergeBaseResolver.findMergeBase(prFrom, prTo); + final FileNameHookSetting setting = getSettings(context.getSettings()); + + final ChangesRequest.Builder builder = new ChangesRequest.Builder(prFrom.getRepository(), prFrom.getId()); + if (base.getId() != null) { + builder.sinceId(base.getId()); + } + final ChangesRequest pathsRequest = builder.build(); final ChangedPathsCollector pathsCallback = new ChangedPathsCollector(); commitService.streamChanges(pathsRequest, pathsCallback); - Collection filteredFiles = pathsCallback.getChangedPaths(); + Collection filteredFiles = pathsCallback.getChangedPaths(); filteredFiles = filteredFiles.stream().filter(setting.getIncludePattern().asPredicate()).collect(Collectors.toList()); - - if(setting.getExcludePattern().isPresent()) { - Pattern excludePattern = setting.getExcludePattern().get(); - filteredFiles = filteredFiles.stream().filter(excludePattern.asPredicate().negate()).collect(Collectors.toList()); - } - - if (filteredFiles.size() > 0) { - request.veto(i18n.getText("filename-hook.mergecheck.veto", "File Name Hook: The following files violate the file name pattern [{0}]:", setting.getIncludePattern().pattern()), getPullRequestError(filteredFiles)); - } - } - + + if(setting.getExcludePattern().isPresent()) { + Pattern excludePattern = setting.getExcludePattern().get(); + filteredFiles = filteredFiles.stream().filter(excludePattern.asPredicate().negate()).collect(Collectors.toList()); + } + + if (filteredFiles.size() > 0) { + request.veto(i18n.getText("filename-hook.mergecheck.veto", "File Name Hook: The following files violate the file name pattern [{0}]:", setting.getIncludePattern().pattern()), getPullRequestError(filteredFiles)); + } + } + private Commit getChangeSet(PullRequestRef prRef) { final CommitRequest.Builder builder = new CommitRequest.Builder(prRef.getRepository(), prRef.getLatestCommit()); return commitService.getCommit(builder.build()); } } + +*/ \ No newline at end of file diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookSetting.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookSetting.java index 4105784..feab7d8 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookSetting.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookSetting.java @@ -7,9 +7,9 @@ class FileNameHookSetting { - private Pattern includePattern; - private Optional excludePattern; - private Optional branchesPattern; + private final Pattern includePattern; + private final Optional excludePattern; + private final Optional branchesPattern; FileNameHookSetting(String includePattern, String excludePattern, String branchesPattern) { this.includePattern = Pattern.compile(includePattern); diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHook.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHook.java index dfc3a1a..5f2b476 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHook.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHook.java @@ -2,15 +2,15 @@ import com.atlassian.bitbucket.commit.Commit; import com.atlassian.bitbucket.content.Change; -import com.atlassian.bitbucket.hook.HookResponse; -import com.atlassian.bitbucket.hook.repository.PreReceiveRepositoryHook; -import com.atlassian.bitbucket.hook.repository.RepositoryHookContext; +import com.atlassian.bitbucket.hook.repository.*; import com.atlassian.bitbucket.repository.RefChange; import com.atlassian.bitbucket.repository.Repository; import com.atlassian.bitbucket.scm.Command; -import com.atlassian.bitbucket.scm.PluginCommandBuilderFactory; -import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory; +import com.atlassian.bitbucket.scm.ScmCommandBuilder; +import com.atlassian.bitbucket.scm.ScmService; import com.atlassian.bitbucket.setting.Settings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.util.*; @@ -25,25 +25,74 @@ /** * Checks the size of a file in the pre-receive phase and rejects the push when the changeset contains files which exceed the configured file size limit. */ -public class FileSizeHook implements PreReceiveRepositoryHook { +public class FileSizeHook implements PreRepositoryHook { + private static final Logger LOGGER = LoggerFactory.getLogger(FileSizeHook.class); private static final int MAX_SETTINGS = 5; private static final String SETTINGS_INCLUDE_PATTERN_PREFIX = "pattern-"; private static final String SETTINGS_EXCLUDE_PATTERN_PREFIX = "pattern-exclude-"; private static final String SETTINGS_SIZE_PREFIX = "size-"; private static final String SETTINGS_BRANCHES_PATTERN_PREFIX = "pattern-branches-"; - private final ChangesetService changesetService; - private final PluginCommandBuilderFactory commandFactory; + private final ScmService scmService; + - public FileSizeHook(ChangesetService changesetService, GitCommandBuilderFactory commandFactory) { + public FileSizeHook(ChangesetService changesetService, ScmService scmService) { this.changesetService = changesetService; - this.commandFactory = commandFactory; + this.scmService = scmService; + } + + + private List getSettings(Settings settings) { + LOGGER.info("Get hook configuration settings"); + List configurations = new ArrayList<>(); + String includeRegex; + Long size; + String excludeRegex; + String branchesRegex; + + for (int i = 1; i <= MAX_SETTINGS; i++) { + includeRegex = settings.getString(SETTINGS_INCLUDE_PATTERN_PREFIX + i); + if (includeRegex != null) { + excludeRegex = settings.getString(SETTINGS_EXCLUDE_PATTERN_PREFIX + i); + size = settings.getLong(SETTINGS_SIZE_PREFIX + i); + branchesRegex = settings.getString(SETTINGS_BRANCHES_PATTERN_PREFIX + i); + configurations.add(new FileSizeHookSetting(size, includeRegex, excludeRegex, branchesRegex)); + } + } + LOGGER.info("Return hook configuration settings"); + return configurations; + } + + + private Map getSizeForContentIds(final Repository repository, Iterable contentIds) { + LOGGER.info("Get size for content ids"); + ScmCommandBuilder scmCommandBuilder = scmService.createBuilder(repository); + CatFileBatchCheckHandler handler = new CatFileBatchCheckHandler(contentIds); + Command> cmd = scmCommandBuilder + .command("cat-file") + .argument("--batch-check") + .inputHandler(handler) + .build(handler); + return filterOutNullSizes(Objects.requireNonNull(cmd.call())); + } + + + private Map filterOutNullSizes(Map sizes) { + LOGGER.info("Filter null sizes"); + return sizes.entrySet() + .stream() + .filter(entry -> entry.getValue() != null) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + + @Nonnull @Override - public boolean onReceive(@Nonnull RepositoryHookContext context, @Nonnull Collection refChanges, @Nonnull HookResponse hookResponse) { - Repository repository = context.getRepository(); + public RepositoryHookResult preUpdate(@Nonnull PreRepositoryHookContext context, + @Nonnull RepositoryHookRequest request) { + LOGGER.info("Start of preUpdate hook event"); + Repository repository = request.getRepository(); List settings = getSettings(context.getSettings()); FlatteningCachingResolver changesByCommit = new FlatteningCachingResolver<>(); @@ -57,7 +106,7 @@ public boolean onReceive(@Nonnull RepositoryHookContext context, @Nonnull Collec Long maxFileSize = setting.getSize(); Optional branchesPattern = setting.getBranchesPattern(); - Stream filteredRefChanges = refChanges.stream(); + Stream filteredRefChanges = request.getRefChanges().stream(); if (branchesPattern.isPresent()) { filteredRefChanges = filteredRefChanges @@ -66,6 +115,7 @@ public boolean onReceive(@Nonnull RepositoryHookContext context, @Nonnull Collec Set commits = changesetService.getCommitsBetween(repository, filteredRefChanges.collect(Collectors.toSet())); + LOGGER.info("Number of commits: " + commits.size()); Set filteredChanges = changesByCommit.flatBatchResolve(commits, x -> changesetService.getChanges(repository, x)).stream() @@ -77,7 +127,7 @@ public boolean onReceive(@Nonnull RepositoryHookContext context, @Nonnull Collec || !setting.getExcludePattern().get().matcher(fullPath).find()); }) .collect(Collectors.toSet()); - + LOGGER.info("Number of filtered changes: " + filteredChanges.size()); // Pre-populate cache by resolving all required changes at once sizesByContentId.batchResolve( filteredChanges.stream().map(Change::getContentId).collect(Collectors.toSet()), @@ -87,69 +137,38 @@ public boolean onReceive(@Nonnull RepositoryHookContext context, @Nonnull Collec .filter(change -> sizesByContentId.resolve(change.getContentId()) > maxFileSize) .map(change -> change.getPath().toString()) .collect(Collectors.toList()); - + LOGGER.info("Violating paths: " + violatingPaths.size() + ", filtered paths: " + filteredPaths.size()); addAll(violatingPaths, filteredPaths); if (pathAndSizes.containsKey(maxFileSize)) addAll(violatingPaths, pathAndSizes.get(maxFileSize)); pathAndSizes.put(maxFileSize, violatingPaths); + + for (Map.Entry> entry : pathAndSizes.entrySet()) { + LOGGER.info("File size : " + entry.getKey() + ", file path: " + entry.getValue()); + } } + ArrayList resultList = new ArrayList<>(); boolean hookPassed = true; for (Long maxFileSize : pathAndSizes.keySet()) { Collection paths = pathAndSizes.get(maxFileSize); if (paths.size() > 0) { hookPassed = false; - hookResponse.out().println("=== File Size Hook ==="); - hookResponse.out().println(""); - for (String path : paths) { - hookResponse.out().println(String.format("File [%s] is too large. Maximum allowed file size is %s bytes.", path, maxFileSize)); - } - hookResponse.out().println(""); - hookResponse.out().println("You may to consider to use Git Large File Storage in Bitbucket, see https://confluence.atlassian.com/bitbucket/git-large-file-storage-in-bitbucket-829078514.html"); - hookResponse.out().println("======================"); + for (String path : paths) + resultList.add(String.format("File [%s] is too large. Maximum allowed file size is %s bytes.", path, maxFileSize)); } } + LOGGER.info("End of preUpdate repo hook event, hook passed: " + hookPassed); - return hookPassed; - } - - private List getSettings(Settings settings) { - List configurations = new ArrayList<>(); - String includeRegex; - Long size; - String excludeRegex; - String branchesRegex; - - for (int i = 1; i <= MAX_SETTINGS; i++) { - includeRegex = settings.getString(SETTINGS_INCLUDE_PATTERN_PREFIX + i); - if (includeRegex != null) { - excludeRegex = settings.getString(SETTINGS_EXCLUDE_PATTERN_PREFIX + i); - size = settings.getLong(SETTINGS_SIZE_PREFIX + i); - branchesRegex = settings.getString(SETTINGS_BRANCHES_PATTERN_PREFIX + i); - configurations.add(new FileSizeHookSetting(size, includeRegex, excludeRegex, branchesRegex)); - } + if (hookPassed) { + return RepositoryHookResult.accepted(); + } else { + return RepositoryHookResult.rejected("Files are too large", Arrays.toString(resultList.toArray())); } - return configurations; } - private Map getSizeForContentIds(final Repository repository, Iterable contentIds) { - CatFileBatchCheckHandler handler = new CatFileBatchCheckHandler(contentIds); - Command> cmd = commandFactory.builder(repository) - .command("cat-file") - .argument("--batch-check") - .inputHandler(handler) - .build(handler); - return filterOutNullSizes(cmd.call()); - } - - private Map filterOutNullSizes(Map sizes) { - return sizes.entrySet() - .stream() - .filter(entry -> entry.getValue() != null) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } } diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookSetting.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookSetting.java index 7da53a4..d1d89c1 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookSetting.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookSetting.java @@ -7,8 +7,8 @@ class FileSizeHookSetting { - private Long size; - private Pattern includePattern; + private final Long size; + private final Pattern includePattern; private Optional excludePattern; private Optional branchesPattern; diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookValidator.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookValidator.java index c934552..5cd248d 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookValidator.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileSizeHookValidator.java @@ -1,18 +1,21 @@ package org.christiangalsterer.stash.filehooks.plugin.hook; import com.atlassian.bitbucket.i18n.I18nService; -import com.atlassian.bitbucket.repository.Repository; -import com.atlassian.bitbucket.setting.RepositorySettingsValidator; +import com.atlassian.bitbucket.scope.Scope; import com.atlassian.bitbucket.setting.Settings; import com.atlassian.bitbucket.setting.SettingsValidationErrors; +import com.atlassian.bitbucket.setting.SettingsValidator; import com.google.common.base.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; +import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -public class FileSizeHookValidator implements RepositorySettingsValidator { +public class FileSizeHookValidator implements SettingsValidator { private static final int MAX_SETTINGS = 5; private static final String SETTINGS_INCLUDE_PATTERN_PREFIX = "pattern-"; @@ -20,6 +23,7 @@ public class FileSizeHookValidator implements RepositorySettingsValidator { private static final String SETTINGS_SIZE_PREFIX = "size-"; private static final String SETTINGS_BRANCHES_PATTERN_PREFIX = "pattern-branches-"; + private static final Logger LOGGER = LoggerFactory.getLogger(FileSizeHookValidator.class); private final I18nService i18n; public FileSizeHookValidator(I18nService i18n) { @@ -27,15 +31,13 @@ public FileSizeHookValidator(I18nService i18n) { } @Override - public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErrors errors, @Nonnull Repository repository) { - + public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErrors errors, @Nonnull Scope scope) { int patternParams = 0; final Set params = settings.asMap().keySet(); for (String param : params) { if (param.matches(SETTINGS_INCLUDE_PATTERN_PREFIX + "[1-5]$")) { patternParams++; - continue; } } @@ -64,7 +66,7 @@ public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErro if (!Strings.isNullOrEmpty(settings.getString(SETTINGS_EXCLUDE_PATTERN_PREFIX + i))) { try { - Pattern.compile(settings.getString(SETTINGS_EXCLUDE_PATTERN_PREFIX + i)); + Pattern.compile(Objects.requireNonNull(settings.getString(SETTINGS_EXCLUDE_PATTERN_PREFIX + i))); } catch (PatternSyntaxException e) { errors.addFieldError(SETTINGS_EXCLUDE_PATTERN_PREFIX + i, i18n.getText("filesize-hook.error.pattern", "Pattern is not a valid regular expression")); } @@ -72,7 +74,7 @@ public void validate(@Nonnull Settings settings, @Nonnull SettingsValidationErro if (!Strings.isNullOrEmpty(settings.getString(SETTINGS_BRANCHES_PATTERN_PREFIX + i))) { try { - Pattern.compile(settings.getString(SETTINGS_BRANCHES_PATTERN_PREFIX + i)); + Pattern.compile(Objects.requireNonNull(settings.getString(SETTINGS_BRANCHES_PATTERN_PREFIX + i))); } catch (PatternSyntaxException e) { errors.addFieldError(SETTINGS_BRANCHES_PATTERN_PREFIX + i, i18n.getText("filesize-hook.error.pattern", "Pattern is not a valid regular expression")); } diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FirstLineOutputHandler.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FirstLineOutputHandler.java index 2e269db..dc5e7c7 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FirstLineOutputHandler.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/FirstLineOutputHandler.java @@ -1,21 +1,22 @@ package org.christiangalsterer.stash.filehooks.plugin.hook; +import com.atlassian.bitbucket.io.LineReader; +import com.atlassian.bitbucket.io.LineReaderOutputHandler; +import com.atlassian.bitbucket.scm.CommandOutputHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; import java.io.IOException; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; - -import javax.annotation.Nullable; - -import com.atlassian.bitbucket.io.LineReader; -import com.atlassian.bitbucket.io.LineReaderOutputHandler; -import com.atlassian.bitbucket.scm.CommandOutputHandler; /** * Returns the first line of output provided by the git process. */ public class FirstLineOutputHandler extends LineReaderOutputHandler implements - CommandOutputHandler { - + CommandOutputHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(FirstLineOutputHandler.class); private String sha; FirstLineOutputHandler() { diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/Functions.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/Functions.java index b32f97b..aa00558 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/Functions.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/Functions.java @@ -1,16 +1,14 @@ package org.christiangalsterer.stash.filehooks.plugin.hook; -import com.atlassian.bitbucket.content.Change; import com.atlassian.bitbucket.commit.Commit; -import com.atlassian.bitbucket.commit.Changeset; -import com.atlassian.bitbucket.repository.RefChange; -import com.atlassian.bitbucket.repository.RefChangeType; +import com.atlassian.bitbucket.commit.MinimalCommit; +import com.atlassian.bitbucket.content.Change; import java.util.function.Function; class Functions { - static final Function COMMIT_TO_COMMIT_ID = commit -> commit.getId(); + static final Function COMMIT_TO_COMMIT_ID = MinimalCommit::getId; static final Function CHANGE_TO_PATH = change -> change.getPath().toString(); } diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/GitUtils.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/GitUtils.java index bfede65..acfff9a 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/GitUtils.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/GitUtils.java @@ -2,8 +2,8 @@ import com.atlassian.bitbucket.repository.Repository; import com.atlassian.bitbucket.scm.CommandBuilderSupport; -import com.atlassian.bitbucket.scm.git.GitScmConfig; +/* class GitUtils { private GitUtils() { @@ -14,4 +14,5 @@ static void setAlternateIfCrossRepository(CommandBuilderSupport builder, Repo builder.withEnvironment("GIT_ALTERNATE_OBJECT_DIRECTORIES", config.getObjectsDir(secondRepository).getAbsolutePath()); } } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/MergeBaseResolver.java b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/MergeBaseResolver.java index 22c68f9..a71509e 100644 --- a/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/MergeBaseResolver.java +++ b/src/main/java/org/christiangalsterer/stash/filehooks/plugin/hook/MergeBaseResolver.java @@ -1,15 +1,16 @@ package org.christiangalsterer.stash.filehooks.plugin.hook; + import com.atlassian.bitbucket.commit.Commit; import com.atlassian.bitbucket.commit.CommitRequest; import com.atlassian.bitbucket.commit.CommitService; -import com.atlassian.bitbucket.scm.git.GitScmConfig; -import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory; -import com.atlassian.bitbucket.scm.git.command.merge.GitMergeBaseBuilder; + /** * Determines the merge base of a pair of commits. */ + +/** class MergeBaseResolver { private final GitCommandBuilderFactory builderFactory; @@ -37,4 +38,6 @@ Commit findMergeBase(Commit a, Commit b) { return commitService.getCommit(commitRequest); } -} \ No newline at end of file +} + +*/ \ No newline at end of file diff --git a/src/main/resources/atlassian-plugin.xml b/src/main/resources/atlassian-plugin.xml index 4a2e158..a6895ce 100644 --- a/src/main/resources/atlassian-plugin.xml +++ b/src/main/resources/atlassian-plugin.xml @@ -10,9 +10,7 @@ - - org.christiangalsterer.stash.filehooks.plugin.hook.ChangesetService @@ -46,6 +44,7 @@ org.christiangalsterer.stash.filehooks.plugin.hook.FileSizeHookValidator + \ No newline at end of file diff --git a/src/test/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookTest.java b/src/test/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookTest.java index 7c0a037..9153f14 100644 --- a/src/test/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookTest.java +++ b/src/test/java/org/christiangalsterer/stash/filehooks/plugin/hook/FileNameHookTest.java @@ -1,19 +1,18 @@ package org.christiangalsterer.stash.filehooks.plugin.hook; +/* import com.atlassian.bitbucket.commit.CommitService; import com.atlassian.bitbucket.i18n.I18nService; import com.atlassian.bitbucket.repository.Repository; -import com.atlassian.bitbucket.scm.git.GitScmConfig; import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory; import com.atlassian.bitbucket.setting.Settings; - import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import static org.junit.Assert.*; -import static org.mockito.MockitoAnnotations.initMocks; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.MockitoAnnotations.initMocks; public class FileNameHookTest { @@ -60,4 +59,6 @@ public void testValidate() throws Exception { private boolean push() { return true; } -} \ No newline at end of file +} + +*/ \ No newline at end of file