commit 0a6e97b978b0d48cb9e0c6338a38f1913325ac01 Author: Luke Granger-Brown Date: Mon Jul 8 19:04:51 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9956b30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +result* diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 0000000..08d558e --- /dev/null +++ b/.reuse/dep5 @@ -0,0 +1,18 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: nix-gerrit +Upstream-Contact: The nix-gerrit Authors +Source: https://git.lix.systems/lukegb/nix-gerrit + +Files: AUTHORS +Copyright: The nix-gerrit Authors +License: MIT + +# Computer generated files +Files: flake.lock +Copyright: NONE +License: MIT + +# Patches are licensed under the same as their parent +Files: gerrit/*.patch plugins/code-owners/*.patch +Copyright: The Android Open Source Project +License: Apache-2.0 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..96d659d --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Luke Granger-Brown diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..50aba87 --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Copyright (c) 2024 The nix-gerrit Authors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 0000000..137069b --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +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. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..505c4b3 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# nix-gerrit + + + +[Lix](https://lix.systems) expressions for building [Gerrit Code +Review](https://gerritcodereview.com) + +Note that this is _not_ and is _not_ intended to be a fully vanilla Gerrit +builder. This set of Gerrit expressions contains some patches that deviate from +upstream. Be warned and review carefully! + +## Gerrit + +Gerrit can be built with + +``` +nix-build -A gerrit +# or, if you're feeling flake-y: +nix build +``` + +## Gerrit Plugins + +### OAuth + +The out-of-tree [Gerrit OAuth2 plugin](https://gerrit.googlesource.com/plugins/oauth/) is available. + +``` +nix-build -A plugins.oauth +# or +nix build '.#plugins.oauth' +``` + +### Code-Owners + +The out-of-tree [Gerrit Code-Owners plugin](https://gerrit.googlesource.com/plugins/code-owners/) is available. + +``` +nix-build -A plugins.code-owners +# or +nix build '.#plugins.code-owners' +``` + +## Building everything at once + +Everything in the tree can be built at once using the `ci` expressiong: + +``` +nix-build -A ci +# or +nix build '.#ci' +``` diff --git a/buildBazelPackageNG/bazelRulesJavaHook/default.nix b/buildBazelPackageNG/bazelRulesJavaHook/default.nix new file mode 100644 index 0000000..a520c37 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesJavaHook/default.nix @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ makeSetupHook }: + +makeSetupHook { + name = "rules_java_bazel_hook"; + substitutions = { + local_java = ./local_java; + }; +} ./setup-hook.sh diff --git a/buildBazelPackageNG/bazelRulesJavaHook/local_java/BUILD.bazel b/buildBazelPackageNG/bazelRulesJavaHook/local_java/BUILD.bazel new file mode 100644 index 0000000..e29fece --- /dev/null +++ b/buildBazelPackageNG/bazelRulesJavaHook/local_java/BUILD.bazel @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +alias(name = "jdk", actual = "@local_jdk//:jdk") +alias(name = "toolchain", actual = "@local_jdk//:toolchain") +alias(name = "bootstrap_runtime_toolchain", actual = "@local_jdk//:bootstrap_runtime_toolchain") diff --git a/buildBazelPackageNG/bazelRulesJavaHook/local_java/WORKSPACE b/buildBazelPackageNG/bazelRulesJavaHook/local_java/WORKSPACE new file mode 100644 index 0000000..4564e6b --- /dev/null +++ b/buildBazelPackageNG/bazelRulesJavaHook/local_java/WORKSPACE @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +workspace(name = "local_java") diff --git a/buildBazelPackageNG/bazelRulesJavaHook/setup-hook.sh b/buildBazelPackageNG/bazelRulesJavaHook/setup-hook.sh new file mode 100644 index 0000000..7e778eb --- /dev/null +++ b/buildBazelPackageNG/bazelRulesJavaHook/setup-hook.sh @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +prePatchHooks+=(_setupLocalJavaRepo) +shellHooks+=(_setupLocalJavaRepo) + +javaVersions=(11 17 21) +javaPlatforms=( + "linux" "linux_aarch64" "linux_ppc64le" "linux_s390x" + "macos" "macos_aarch64" + "win" "win_arm64") + +_setupLocalJavaRepo() { + for javaVersion in ${javaVersions[@]}; do + for javaPlatform in ${javaPlatforms[@]}; do + bazelFlagsArray+=( + "--override_repository=remotejdk${javaVersion}_${javaPlatform}=@local_java@" + ) + done + done +} diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/default.nix b/buildBazelPackageNG/bazelRulesNodeJS5Hook/default.nix new file mode 100644 index 0000000..bf24a88 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/default.nix @@ -0,0 +1,103 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ stdenvNoCC +, lib +, makeSetupHook +, fetchFromGitHub +, coreutils +, gnugrep +, nodejs +, yarn +, git +, cacert +}: +let + rulesNodeJS = stdenvNoCC.mkDerivation rec { + pname = "bazelbuild-rules_nodejs"; + version = "5.8.5"; + + src = fetchFromGitHub { + owner = "bazelbuild"; + repo = "rules_nodejs"; + rev = version; + hash = "sha256-6UbYRrOnS93+pK4VI016gQZv2jLCzkJn6wJ4vZNCNjY="; + }; + + dontBuild = true; + + postPatch = '' + shopt -s globstar + for i in **/*.bzl **/*.sh **/*.cjs; do + substituteInPlace "$i" \ + --replace-quiet '#!/usr/bin/env bash' '#!${stdenvNoCC.shell}' \ + --replace-quiet '#!/bin/bash' '#!${stdenvNoCC.shell}' + done + sed -i '/^#!/a export PATH=${lib.makeBinPath [ coreutils gnugrep ]}:$PATH' internal/node/launcher.sh + ''; + + installPhase = '' + cp -R . $out + ''; + }; + + localNode = stdenvNoCC.mkDerivation rec { + name = "bazelbuild-rules_nodejs-local_node"; + + src = ./local_node; + + inherit nodejs; + + buildPhase = '' + runHook preBuild + + substituteInPlace BUILD WORKSPACE bin/* \ + --subst-var nodejs + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + cp -R . $out + chmod +x $out/bin/* + runHook postInstall + ''; + }; + localYarn = stdenvNoCC.mkDerivation rec { + name = "bazelbuild-rules_nodejs-local_yarn"; + + src = ./local_yarn; + + inherit yarn; + + buildPhase = '' + runHook preBuild + + substituteInPlace BUILD WORKSPACE bin/* \ + --subst-var yarn + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + cp -R . $out + chmod +x $out/bin/* + runHook postInstall + ''; + }; +in makeSetupHook { + name = "bazelbuild-rules_nodejs-5-hook"; + propagatedBuildInputs = [ + nodejs + yarn + git + cacert + ]; + substitutions = { + inherit nodejs yarn cacert rulesNodeJS; + local_node = localNode; + local_yarn = localYarn; + }; +} ./setup-hook.sh diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/BUILD b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/BUILD new file mode 100644 index 0000000..a5b7d85 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/BUILD @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +load("@build_bazel_rules_nodejs//nodejs:toolchain.bzl", _node_toolchain = "node_toolchain") + +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "bin/node", + "bin/npm", +]) + +_node_toolchain( + name = "node_toolchain", + target_tool_path = "@nodejs@/bin/node", + npm_path = "@nodejs@/bin/npm", +) + +toolchain( + name = "nodejs", + toolchain = ":node_toolchain", + toolchain_type = "@build_bazel_rules_nodejs//nodejs:toolchain_type", +) diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/WORKSPACE b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/WORKSPACE new file mode 100644 index 0000000..6566d95 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/WORKSPACE @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +workspace(name = "nodejs") diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/bin/node b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/bin/node new file mode 100644 index 0000000..620b3ca --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/bin/node @@ -0,0 +1,6 @@ +#!/bin/sh + +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +exec "@nodejs@/bin/node" "$@" diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/bin/npm b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/bin/npm new file mode 100644 index 0000000..d5cf820 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_node/bin/npm @@ -0,0 +1,6 @@ +#!/bin/sh + +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +exec "@nodejs@/bin/npm" "$@" diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/BUILD b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/BUILD new file mode 100644 index 0000000..44d80aa --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/BUILD @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/WORKSPACE b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/WORKSPACE new file mode 100644 index 0000000..8620848 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/WORKSPACE @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +workspace(name = "yarn") diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/bin/yarn b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/bin/yarn new file mode 100644 index 0000000..04691f6 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/local_yarn/bin/yarn @@ -0,0 +1,6 @@ +#!/bin/sh + +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +exec "@yarn@/bin/yarn" "$@" diff --git a/buildBazelPackageNG/bazelRulesNodeJS5Hook/setup-hook.sh b/buildBazelPackageNG/bazelRulesNodeJS5Hook/setup-hook.sh new file mode 100644 index 0000000..342e3b2 --- /dev/null +++ b/buildBazelPackageNG/bazelRulesNodeJS5Hook/setup-hook.sh @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +prePatchHooks+=(_setupLocalNodeRepos) +preBuildHooks+=(_setupYarnCache) + +case "$bazelPhase" in + deps) + postInstallHooks+=(_copyYarnCache) + ;; + build) + preBuildHooks+=(_linkYarnCache) + ;; + shell) + shellHooks+=(_setupLocalNodeRepos) + ;; + *) + echo "Unexpected bazelPhase '$bazelPhase' (want deps, build or shell)" >&2 + exit 1 + ;; +esac + + +_setupLocalNodeRepos() { + bazelFlagsArray+=( + "--override_repository=build_bazel_rules_nodejs=@rulesNodeJS@" + + "--override_repository=nodejs_linux_amd64=@local_node@" + "--override_repository=nodejs_linux_arm64=@local_node@" + "--override_repository=nodejs_linux_s390x=@local_node@" + "--override_repository=nodejs_linux_ppc64le=@local_node@" + "--override_repository=nodejs_darwin_amd64=@local_node@" + "--override_repository=nodejs_darwin_arm64=@local_node@" + "--override_repository=nodejs_windows_amd64=@local_node@" + "--override_repository=nodejs_windows_arm64=@local_node@" + "--override_repository=nodejs=@local_node@" + + "--override_repository=yarn=@local_yarn@" + ) +} + +_setupYarnCache() { + @yarn@/bin/yarn config set cafile "@cacert@/etc/ssl/certs/ca-bundle.crt" + @yarn@/bin/yarn config set yarn-offline-mirror "$NIXBAZEL_CACHE_ROOT/yarn-offline-mirror" +} + +_copyYarnCache() { + cp -R "$NIXBAZEL_CACHE_ROOT/yarn-offline-mirror" "$out/yarn-offline-mirror" +} + +_linkYarnCache() { + ln -sf "$deps/yarn-offline-mirror" "$NIXBAZEL_CACHE_ROOT/yarn-offline-mirror" +} diff --git a/buildBazelPackageNG/default.nix b/buildBazelPackageNG/default.nix new file mode 100644 index 0000000..33258aa --- /dev/null +++ b/buildBazelPackageNG/default.nix @@ -0,0 +1,160 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ stdenv +, lib +, pkgs +, coreutils +, mkShell +, writeShellApplication +}: + +let + hooks = { + bazelRulesJavaHook = pkgs.callPackage ./bazelRulesJavaHook { }; + bazelRulesNodeJS5Hook = pkgs.callPackage ./bazelRulesNodeJS5Hook { }; + }; + + builder = { + name ? "${baseAttrs.pname}-${baseAttrs.version}" + , bazelTargets + , bazel ? pkgs.bazel + , depsHash + , extraCacheInstall ? "" + , extraBuildSetup ? "" + , extraBuildInstall ? "" + , ... + }@baseAttrs: + + let + cleanAttrs = lib.flip removeAttrs [ + "bazelTargets" "depsHash" "extraCacheInstall" "extraBuildSetup" "extraBuildInstall" + ]; + attrs = cleanAttrs baseAttrs; + + base = stdenv.mkDerivation (attrs // { + nativeBuildInputs = (attrs.nativeBuildInputs or []) ++ [ + bazel + ]; + + preUnpack = '' + if [[ ! -d $HOME ]]; then + export HOME=$NIX_BUILD_TOP/home + mkdir -p $HOME + fi + export NIXBAZEL_CACHE_ROOT=$NIX_BUILD_TOP/nixbazel-cache + mkdir -p $NIXBAZEL_CACHE_ROOT + ''; + + bazelTargetNames = builtins.attrNames bazelTargets; + }); + + deps = base.overrideAttrs (base: { + name = "${name}-deps"; + + bazelPhase = "deps"; + + buildPhase = '' + runHook preBuild + + bazel sync --repository_cache=$NIXBAZEL_CACHE_ROOT/repository-cache $bazelFlags "''${bazelFlagsArray[@]}" + bazel build --repository_cache=$NIXBAZEL_CACHE_ROOT/repository-cache --nobuild $bazelFlags "''${bazelFlagsArray[@]}" $bazelTargetNames + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir $out + echo "${bazel.version}" > $out/bazel_version + cp -R $NIXBAZEL_CACHE_ROOT/repository-cache $out/repository-cache + ${extraCacheInstall} + + runHook postInstall + ''; + + outputHashMode = "recursive"; + outputHash = depsHash; + }); + + build = base.overrideAttrs (base: { + bazelPhase = "build"; + + inherit deps; + passthru.shell = shell; + + nativeBuildInputs = (base.nativeBuildInputs or []) ++ [ + coreutils + ]; + + buildPhase = '' + runHook preBuild + + ${extraBuildSetup} + bazel build --repository_cache=$deps/repository-cache $bazelFlags "''${bazelFlagsArray[@]}" $bazelTargetNames + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + ${builtins.concatStringsSep "\n" (lib.mapAttrsToList (target: outPath: lib.optionalString (outPath != null) '' + TARGET_OUTPUTS="$(bazel cquery --repository_cache=$deps/repository-cache $bazelFlags "''${bazelFlagsArray[@]}" --output=files "${target}")" + if [[ "$(echo "$TARGET_OUTPUTS" | wc -l)" -gt 1 ]]; then + echo "Installing ${target}'s outputs ($TARGET_OUTPUTS) into ${outPath} as a directory" + mkdir -p "${outPath}" + cp $TARGET_OUTPUTS "${outPath}" + else + echo "Installing ${target}'s output ($TARGET_OUTPUTS) to ${outPath}" + mkdir -p "${dirOf outPath}" + cp "$TARGET_OUTPUTS" "${outPath}" + fi + '') bazelTargets)} + ${extraBuildInstall} + + runHook postInstall + ''; + }); + + bazelShellLauncher = writeShellApplication { + name = "bazel"; + + runtimeInputs = [ bazel ]; + + text = '' + exec bazel "--bazelrc=$NIXBAZEL_SHELL_BAZELRC" "$@" + ''; + }; + + shell = mkShell rec { + name = if baseAttrs ? name then "${baseAttrs.name}-shell" else "${baseAttrs.pname}-shell"; + + bazelPhase = "shell"; + + inputsFrom = [ build ]; + packages = [ bazelShellLauncher ]; + + postHook = '' + shellHooks+=(_finalShellSetup) + _finalShellSetup() { + # This is set up in postHook because we need to make sure that this shell hook happens + # _after_ every other shell hook. + + for arg in "''${bazelFlagsArray[@]}"; do + echo "common ''${arg}" + done > "$NIXBAZEL_SHELL_BAZELRC" + } + ''; + + shellHook = '' + export NIXBAZEL_CACHE_ROOT=$NIX_BUILD_TOP/cache + mkdir -p $NIXBAZEL_CACHE_ROOT + export NIXBAZEL_SHELL_BAZELRC="$NIXBAZEL_CACHE_ROOT/${name}-bazelrc" + ''; + }; + in build; +in hooks // { + __functor = self: lib.makeOverridable builder; +} diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..72f60bc --- /dev/null +++ b/default.nix @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ nixpkgs ? +, pkgs ? import nixpkgs { } +, lib ? pkgs.lib +}: + +lib.makeScope pkgs.newScope (self: { + buildBazelPackageNG = self.callPackage ./buildBazelPackageNG { }; + gerrit = self.callPackage ./gerrit { }; + + buildGerritBazelPlugin = self.callPackage ./plugins/builder.nix { }; + plugins = { + code-owners = self.callPackage ./plugins/code-owners { }; + oauth = self.callPackage ./plugins/oauth { }; + }; + + ci = pkgs.linkFarm "gerrit-ci" [ + { name = "gerrit"; path = self.gerrit; } + { name = "code-owners.jar"; path = self.plugins.code-owners; } + { name = "oauth.jar"; path = self.plugins.oauth; } + ]; +}) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ed5e17d --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1720031269, + "narHash": "sha256-rwz8NJZV+387rnWpTYcXaRNvzUSnnF9aHONoJIYmiUQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "9f4128e00b0ae8ec65918efeba59db998750ead6", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..70c48c7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ + description = "Gerrit, a code-review service for Git"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { self, nixpkgs }: let + systems = [ "x86_64-linux" "aarch64-linux" ]; + forAllSystems = nixpkgs.lib.genAttrs systems; + in { + packages = forAllSystems (system: let + pkgSet = import ./default.nix { pkgs = nixpkgs.legacyPackages."${system}"; }; + in pkgSet // { default = pkgSet.gerrit; }); + devShells = forAllSystems (system: { + default = import ./shell.nix { pkgs = nixpkgs.legacyPackages."${system}"; }; + }); + }; +} diff --git a/gerrit/0001-Syntax-highlight-nix.patch b/gerrit/0001-Syntax-highlight-nix.patch new file mode 100644 index 0000000..d49fa04 --- /dev/null +++ b/gerrit/0001-Syntax-highlight-nix.patch @@ -0,0 +1,37 @@ +From 216843cff4a8e41ad9887118751a412c1a22ce72 Mon Sep 17 00:00:00 2001 +From: The nix-gerrit Authors +Date: Thu, 2 Jul 2020 23:02:32 +0100 +Subject: [PATCH 1/3] Syntax highlight nix + +--- + .../app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts | 1 + + resources/com/google/gerrit/server/mime/mime-types.properties | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts +index 50742903de..d1e89920cc 100644 +--- a/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts ++++ b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts +@@ -98,6 +98,7 @@ const LANGUAGE_MAP = new Map([ + ['text/x-vhdl', 'vhdl'], + ['text/x-yaml', 'yaml'], + ['text/vbscript', 'vbscript'], ++ ['text/x-nix', 'nix'], + ]); + + const CLASS_PREFIX = 'gr-syntax gr-syntax-'; +diff --git a/resources/com/google/gerrit/server/mime/mime-types.properties b/resources/com/google/gerrit/server/mime/mime-types.properties +index 642ef474a5..97f1ff835b 100644 +--- a/resources/com/google/gerrit/server/mime/mime-types.properties ++++ b/resources/com/google/gerrit/server/mime/mime-types.properties +@@ -154,6 +154,7 @@ msgenny = text/x-msgenny + mts = application/typescript + nb = text/x-mathematica + nginx.conf = text/x-nginx-conf ++nix = text/x-nix + nsh = text/x-nsis + nsi = text/x-nsis + nt = text/n-triples +-- +2.45.1 + diff --git a/gerrit/0002-Syntax-highlight-rules.pl.patch b/gerrit/0002-Syntax-highlight-rules.pl.patch new file mode 100644 index 0000000..93714cc --- /dev/null +++ b/gerrit/0002-Syntax-highlight-rules.pl.patch @@ -0,0 +1,37 @@ +From 63f1ff6ea749ae2af29a53463bca81bc3f4bf25b Mon Sep 17 00:00:00 2001 +From: The nix-gerrit Authors +Date: Thu, 2 Jul 2020 23:02:43 +0100 +Subject: [PATCH 2/3] Syntax highlight rules.pl + +--- + .../app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts | 1 + + resources/com/google/gerrit/server/mime/mime-types.properties | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts +index d1e89920cc..5d62af1c64 100644 +--- a/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts ++++ b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts +@@ -72,6 +72,7 @@ const LANGUAGE_MAP = new Map([ + ['text/x-perl', 'perl'], + ['text/x-pgsql', 'pgsql'], // postgresql + ['text/x-php', 'php'], ++ ['text/x-prolog', 'prolog'], + ['text/x-properties', 'properties'], + ['text/x-protobuf', 'protobuf'], + ['text/x-puppet', 'puppet'], +diff --git a/resources/com/google/gerrit/server/mime/mime-types.properties b/resources/com/google/gerrit/server/mime/mime-types.properties +index 97f1ff835b..85d630340f 100644 +--- a/resources/com/google/gerrit/server/mime/mime-types.properties ++++ b/resources/com/google/gerrit/server/mime/mime-types.properties +@@ -208,6 +208,7 @@ rq = application/sparql-query + rs = text/x-rustsrc + rss = application/xml + rst = text/x-rst ++rules.pl = text/x-prolog + README.md = text/x-gfm + s = text/x-gas + sas = text/x-sas +-- +2.45.1 + diff --git a/gerrit/0003-Add-titles-to-CLs-over-HTTP.patch b/gerrit/0003-Add-titles-to-CLs-over-HTTP.patch new file mode 100644 index 0000000..8f22484 --- /dev/null +++ b/gerrit/0003-Add-titles-to-CLs-over-HTTP.patch @@ -0,0 +1,214 @@ +From ca2df6d7f53441d443d42908e30bf60fbfc15392 Mon Sep 17 00:00:00 2001 +From: The nix-gerrit Authors +Date: Thu, 2 Jul 2020 23:03:02 +0100 +Subject: [PATCH 3/3] Add titles to CLs over HTTP + +--- + .../gerrit/httpd/raw/IndexHtmlUtil.java | 12 +++- + .../google/gerrit/httpd/raw/IndexServlet.java | 8 ++- + .../google/gerrit/httpd/raw/StaticModule.java | 5 +- + .../gerrit/httpd/raw/TitleComputer.java | 67 +++++++++++++++++++ + .../gerrit/httpd/raw/PolyGerritIndexHtml.soy | 4 +- + 5 files changed, 88 insertions(+), 8 deletions(-) + create mode 100644 java/com/google/gerrit/httpd/raw/TitleComputer.java + +diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java +index a92dd18f04..f87c46d321 100644 +--- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java ++++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java +@@ -41,6 +41,7 @@ import java.util.Collections; + import java.util.HashMap; + import java.util.HashSet; + import java.util.Map; ++import java.util.Optional; + import java.util.Set; + import java.util.function.Function; + +@@ -63,13 +64,14 @@ public class IndexHtmlUtil { + String faviconPath, + Map urlParameterMap, + Function urlInScriptTagOrdainer, +- String requestedURL) ++ String requestedURL, ++ TitleComputer titleComputer) + throws URISyntaxException, RestApiException { + ImmutableMap.Builder data = ImmutableMap.builder(); + data.putAll( + staticTemplateData( + canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer)) +- .putAll(dynamicTemplateData(gerritApi, requestedURL, canonicalURL)); ++ .putAll(dynamicTemplateData(gerritApi, requestedURL, canonicalURL, titleComputer)); + Set enabledExperiments = new HashSet<>(); + enabledExperiments.addAll(experimentFeatures.getEnabledExperimentFeatures()); + // Add all experiments enabled through url +@@ -82,7 +84,7 @@ public class IndexHtmlUtil { + + /** Returns dynamic parameters of {@code index.html}. */ + public static ImmutableMap dynamicTemplateData( +- GerritApi gerritApi, String requestedURL, String canonicalURL) ++ GerritApi gerritApi, String requestedURL, String canonicalURL, TitleComputer titleComputer) + throws RestApiException, URISyntaxException { + ImmutableMap.Builder data = ImmutableMap.builder(); + Map initialData = new HashMap<>(); +@@ -141,6 +143,10 @@ public class IndexHtmlUtil { + } + + data.put("gerritInitialData", initialData); ++ ++ Optional title = titleComputer.computeTitle(requestedURL); ++ title.ifPresent(s -> data.put("title", s)); ++ + return data.build(); + } + +diff --git a/java/com/google/gerrit/httpd/raw/IndexServlet.java b/java/com/google/gerrit/httpd/raw/IndexServlet.java +index fcb821e5ae..e1464b992b 100644 +--- a/java/com/google/gerrit/httpd/raw/IndexServlet.java ++++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java +@@ -48,13 +48,15 @@ public class IndexServlet extends HttpServlet { + private final ExperimentFeatures experimentFeatures; + private final SoySauce soySauce; + private final Function urlOrdainer; ++ private TitleComputer titleComputer; + + IndexServlet( + @Nullable String canonicalUrl, + @Nullable String cdnPath, + @Nullable String faviconPath, + GerritApi gerritApi, +- ExperimentFeatures experimentFeatures) { ++ ExperimentFeatures experimentFeatures, ++ TitleComputer titleComputer) { + this.canonicalUrl = canonicalUrl; + this.cdnPath = cdnPath; + this.faviconPath = faviconPath; +@@ -69,6 +71,7 @@ public class IndexServlet extends HttpServlet { + (s) -> + UnsafeSanitizedContentOrdainer.ordainAsSafe( + s, SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI); ++ this.titleComputer = titleComputer; + } + + @Override +@@ -86,7 +89,8 @@ public class IndexServlet extends HttpServlet { + faviconPath, + parameterMap, + urlOrdainer, +- getRequestUrl(req)); ++ getRequestUrl(req), ++ titleComputer); + renderer = soySauce.renderTemplate("com.google.gerrit.httpd.raw.Index").setData(templateData); + } catch (URISyntaxException | RestApiException e) { + throw new IOException(e); +diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java +index b00294f73e..f1c1aae12c 100644 +--- a/java/com/google/gerrit/httpd/raw/StaticModule.java ++++ b/java/com/google/gerrit/httpd/raw/StaticModule.java +@@ -224,10 +224,11 @@ public class StaticModule extends ServletModule { + @CanonicalWebUrl @Nullable String canonicalUrl, + @GerritServerConfig Config cfg, + GerritApi gerritApi, +- ExperimentFeatures experimentFeatures) { ++ ExperimentFeatures experimentFeatures, ++ TitleComputer titleComputer) { + String cdnPath = options.devCdn().orElseGet(() -> cfg.getString("gerrit", null, "cdnPath")); + String faviconPath = cfg.getString("gerrit", null, "faviconPath"); +- return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, experimentFeatures); ++ return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, experimentFeatures, titleComputer); + } + + @Provides +diff --git a/java/com/google/gerrit/httpd/raw/TitleComputer.java b/java/com/google/gerrit/httpd/raw/TitleComputer.java +new file mode 100644 +index 0000000000..8fd2053ad0 +--- /dev/null ++++ b/java/com/google/gerrit/httpd/raw/TitleComputer.java +@@ -0,0 +1,67 @@ ++package com.google.gerrit.httpd.raw; ++ ++import com.google.common.flogger.FluentLogger; ++import com.google.gerrit.entities.Change; ++import com.google.gerrit.extensions.restapi.ResourceConflictException; ++import com.google.gerrit.extensions.restapi.ResourceNotFoundException; ++import com.google.gerrit.server.change.ChangeResource; ++import com.google.gerrit.server.permissions.PermissionBackendException; ++import com.google.gerrit.server.restapi.change.ChangesCollection; ++import com.google.inject.Inject; ++import com.google.inject.Provider; ++import com.google.inject.Singleton; ++ ++import java.net.MalformedURLException; ++import java.net.URL; ++import java.util.Optional; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++@Singleton ++public class TitleComputer { ++ private static final FluentLogger logger = FluentLogger.forEnclosingClass(); ++ ++ @Inject ++ public TitleComputer(Provider changes) { ++ this.changes = changes; ++ } ++ ++ public Optional computeTitle(String requestedURI) { ++ URL url = null; ++ try { ++ url = new URL(requestedURI); ++ } catch (MalformedURLException e) { ++ logger.atWarning().log("Failed to turn %s into a URL.", requestedURI); ++ return Optional.empty(); ++ } ++ ++ // Try to turn this into a change. ++ Optional changeId = tryExtractChange(url.getPath()); ++ if (changeId.isPresent()) { ++ return titleFromChangeId(changeId.get()); ++ } ++ ++ return Optional.empty(); ++ } ++ ++ private static final Pattern extractChangeIdRegex = Pattern.compile("^/(?:c/.*/\\+/)?(?[0-9]+)(?:/[0-9]+)?(?:/.*)?$"); ++ private final Provider changes; ++ ++ private Optional tryExtractChange(String path) { ++ Matcher m = extractChangeIdRegex.matcher(path); ++ if (!m.matches()) { ++ return Optional.empty(); ++ } ++ return Change.Id.tryParse(m.group("changeId")); ++ } ++ ++ private Optional titleFromChangeId(Change.Id changeId) { ++ ChangesCollection changesCollection = changes.get(); ++ try { ++ ChangeResource changeResource = changesCollection.parse(changeId); ++ return Optional.of(changeResource.getChange().getSubject()); ++ } catch (ResourceConflictException | ResourceNotFoundException | PermissionBackendException e) { ++ return Optional.empty(); ++ } ++ } ++} +diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy +index 5ff1822cd9..81c3cdf0e1 100644 +--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy ++++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy +@@ -33,10 +33,12 @@ + {@param? defaultDashboardHex: ?} + {@param? dashboardQuery: ?} + {@param? userIsAuthenticated: ?} ++ {@param? title: ?} + {\n} + {\n} + {\n} +- {\n} ++ {if $title}{$title} · Gerrit Code Review{\n}{/if} ++ {\n} + {\n} + {\n} + +-- +2.45.1 + diff --git a/gerrit/bazelrc b/gerrit/bazelrc new file mode 100644 index 0000000..ff101a3 --- /dev/null +++ b/gerrit/bazelrc @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +# Not using common --repository_cache because Gerrit's bazelrc overrides this... +build --action_env=SSL_CERT_FILE +build --action_env=GERRIT_CACHE_HOME +build --tool_java_runtime_version=local_jdk --java_runtime_version=local_jdk +build --workspace_status_command="cat .version" + +# Disable errorprone +build --javacopt="-XepDisableAllChecks" diff --git a/gerrit/default.nix b/gerrit/default.nix new file mode 100644 index 0000000..002b5d5 --- /dev/null +++ b/gerrit/default.nix @@ -0,0 +1,117 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ buildBazelPackageNG +, lib +, fetchgit +, bazel_7 +, python3 +, openjdk21_headless +, curl +, unzip +, extraBazelPackageAttrs ? {} +}: + +let + inherit (buildBazelPackageNG) bazelRulesJavaHook bazelRulesNodeJS5Hook; +in +(buildBazelPackageNG rec { + pname = "gerrit"; + version = "3.10.0"; + + bazel = bazel_7; + + src = (fetchgit { + url = "https://gerrit.googlesource.com/gerrit"; + rev = "v${version}"; + fetchSubmodules = true; + deepClone = true; + hash = "sha256-FpKuzityHuHNYBIOL8YUjCLlkuVBfxjvHECb26NsZNE="; + }).overrideAttrs (_: { + env.NIX_PREFETCH_GIT_CHECKOUT_HOOK = '' + pushd "$dir" >/dev/null + ${python3}/bin/python tools/workspace_status_release.py | sort > .version + popd >/dev/null + + # delete all the .git; we can't do this using fetchgit if deepClone is on, + # but our mischief has already been achieved by the python command above :) + find "$dir" -name .git -print0 | xargs -0 rm -rf + ''; + }); + depsHash = "sha256-mp2RhOvDh+0CeLQhCjPp57N2QB816k4AWMeAhvU2u38="; + + patches = [ + ./0001-Syntax-highlight-nix.patch + ./0002-Syntax-highlight-rules.pl.patch + ./0003-Add-titles-to-CLs-over-HTTP.patch + + ./gerrit-cl-431977-bump-sshd.patch + ./gerrit-cl-431977-part-2-bump-bouncycastle.patch + ]; + + nativeBuildInputs = [ + bazelRulesJavaHook + bazelRulesNodeJS5Hook + + curl + openjdk21_headless + python3 + unzip + ]; + + prePatch = '' + rm .bazelversion + + ln -sf ${./bazelrc} user.bazelrc + + ln -sf ${./workspace_overrides.bzl} workspace_overrides.bzl + substituteInPlace WORKSPACE \ + --replace-fail 'load("@io_bazel_rules_webtesting//web:repositories.bzl"' 'load("//:workspace_overrides.bzl"' \ + --replace-fail 'load("@io_bazel_rules_webtesting//web/versioned:browsers-0.3.3.bzl"' 'load("//:workspace_overrides.bzl"' + + patchShebangs Documentation/replace_macros.py + ''; + + postPatch = '' + sed -Ei 's,^(STABLE_BUILD_GERRIT_LABEL.*)$,\1-dirty-nix,' .version + ''; + + preBuild = '' + export GERRIT_CACHE_HOME=$NIXBAZEL_CACHE_ROOT/gerrit-cache + ''; + + extraCacheInstall = '' + cp -R $GERRIT_CACHE_HOME $out/gerrit-cache + ''; + + extraBuildSetup = '' + ln -sf $deps/gerrit-cache $GERRIT_CACHE_HOME + ''; + extraBuildInstall = '' + mkdir -p "$out"/share/api/ + unzip bazel-bin/api-skip-javadoc.zip -d "$out"/share/api + ''; + + bazelTargets = { + "//:release" = "$out/webapps/gerrit-${version}.war"; + "//:api-skip-javadoc" = null; + }; + + passthru = { + # A list of plugins that are part of the gerrit.war file. + # Use `java -jar gerrit.war ls | grep -Po '(?<=plugins/)[^.]+' | sed -e 's,^,",' -e 's,$,",' | sort` to generate that list. + plugins = [ + "codemirror-editor" + "commit-message-length-validator" + "delete-project" + "download-commands" + "gitiles" + "hooks" + "plugin-manager" + "replication" + "reviewnotes" + "singleusergroup" + "webhooks" + ]; + }; +}).override extraBazelPackageAttrs diff --git a/gerrit/gerrit-cl-431977-bump-sshd.patch b/gerrit/gerrit-cl-431977-bump-sshd.patch new file mode 100644 index 0000000..3a08df2 --- /dev/null +++ b/gerrit/gerrit-cl-431977-bump-sshd.patch @@ -0,0 +1,40 @@ +Bump SSHD version to 2.13.1 + +Release-Notes: Update SSHD version to 2.13.1 +Change-Id: Ib7bc185bfd9e7eda0cc04230da8bd87ee1bb2358 + +diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl +index 91caf31..d9b90d8 100644 +--- a/tools/nongoogle.bzl ++++ b/tools/nongoogle.bzl +@@ -137,18 +137,18 @@ + sha1 = "cb2f351bf4463751201f43bb99865235d5ba07ca", + ) + +- SSHD_VERS = "2.12.0" ++ SSHD_VERS = "2.13.1" + + maven_jar( + name = "sshd-osgi", + artifact = "org.apache.sshd:sshd-osgi:" + SSHD_VERS, +- sha1 = "32b8de1cbb722ba75bdf9898e0c41d42af00ce57", ++ sha1 = "50958cc44076749e790d7332021cff546707624c", + ) + + maven_jar( + name = "sshd-sftp", + artifact = "org.apache.sshd:sshd-sftp:" + SSHD_VERS, +- sha1 = "0f96f00a07b186ea62838a6a4122e8f4cad44df6", ++ sha1 = "e1b6da4ef604718e32cad59ef32618610da7a170", + ) + + maven_jar( +@@ -166,7 +166,7 @@ + maven_jar( + name = "sshd-mina", + artifact = "org.apache.sshd:sshd-mina:" + SSHD_VERS, +- sha1 = "8b202f7d4c0d7b714fd0c93a1352af52aa031149", ++ sha1 = "ff4a9fac41a111d806f6a058d23278b0819da7ce", + ) + + maven_jar( diff --git a/gerrit/gerrit-cl-431977-part-2-bump-bouncycastle.patch b/gerrit/gerrit-cl-431977-part-2-bump-bouncycastle.patch new file mode 100644 index 0000000..ddf91cd --- /dev/null +++ b/gerrit/gerrit-cl-431977-part-2-bump-bouncycastle.patch @@ -0,0 +1,43 @@ +diff --git a/tools/deps.bzl b/tools/deps.bzl +index d056483891...c7b88e94b8 100644 +--- a/tools/deps.bzl ++++ b/tools/deps.bzl +@@ -21,7 +21,7 @@ + GITILES_REPO = GERRIT + + # When updating Bouncy Castle, also update it in bazlets. +-BC_VERS = "1.72" ++BC_VERS = "1.78.1" + HTTPCOMP_VERS = "4.5.14" + JETTY_VERS = "9.4.53.v20231009" + BYTE_BUDDY_VERSION = "1.14.9" +@@ -423,25 +423,25 @@ + maven_jar( + name = "bcprov", + artifact = "org.bouncycastle:bcprov-jdk18on:" + BC_VERS, +- sha1 = "d8dc62c28a3497d29c93fee3e71c00b27dff41b4", ++ sha1 = "39e9e45359e20998eb79c1828751f94a818d25f8", + ) + + maven_jar( + name = "bcpg", + artifact = "org.bouncycastle:bcpg-jdk18on:" + BC_VERS, +- sha1 = "1a36a1740d07869161f6f0d01fae8d72dd1d8320", ++ sha1 = "6c8dbcec20355278ec54840e735f63db2479150e", + ) + + maven_jar( + name = "bcpkix", + artifact = "org.bouncycastle:bcpkix-jdk18on:" + BC_VERS, +- sha1 = "bb3fdb5162ccd5085e8d7e57fada4d8eaa571f5a", ++ sha1 = "17b3541f736df97465f87d9f5b5dfa4991b37bb3", + ) + + maven_jar( + name = "bcutil", + artifact = "org.bouncycastle:bcutil-jdk18on:" + BC_VERS, +- sha1 = "41f19a69ada3b06fa48781120d8bebe1ba955c77", ++ sha1 = "5353ca39fe2f148dab9ca1d637a43d0750456254", + ) + + maven_jar( diff --git a/gerrit/workspace_overrides.bzl b/gerrit/workspace_overrides.bzl new file mode 100644 index 0000000..68e2d8b --- /dev/null +++ b/gerrit/workspace_overrides.bzl @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +def web_test_repositories(): + pass + +def browser_repositories(*args, **kwargs): + pass diff --git a/plugins/builder.nix b/plugins/builder.nix new file mode 100644 index 0000000..61fed1a --- /dev/null +++ b/plugins/builder.nix @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ gerrit +, runCommandLocal +, lib +}: + +{ name +, version +, src +, depsHash ? null +, overlayPluginCmd ? '' + cp -R "${src}" "$out/plugins/${name}" + echo "STABLE_BUILD_${lib.toUpper name}_LABEL v${version}-nix${if patches != [] then "-dirty" else ""}" >> $out/.version + '' +, postOverlayPlugin ? "" +, postPatch ? "" +, patches ? [ ] +}: ((gerrit.override { + extraBazelPackageAttrs = (old: { + name = "${name}.jar"; + + src = runCommandLocal "${name}-src" { } '' + cp -R "${gerrit.src}" "$out" + chmod -R +w "$out" + ${overlayPluginCmd} + ${postOverlayPlugin} + ''; + depsHash = (if depsHash != null then depsHash else old.depsHash); + + bazelTargets = { + "//plugins/${name}" = "$out"; + }; + + extraBuildInstall = ""; + }); +}).overrideAttrs (super: { + postPatch = '' + ${super.postPatch or ""} + pushd "plugins/${name}" + ${lib.concatMapStringsSep "\n" (patch: '' + patch -p1 < ${patch} + '') patches} + popd + ${postPatch} + ''; +})) diff --git a/plugins/code-owners/default.nix b/plugins/code-owners/default.nix new file mode 100644 index 0000000..a6129d9 --- /dev/null +++ b/plugins/code-owners/default.nix @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ buildGerritBazelPlugin, fetchgit }: + +buildGerritBazelPlugin rec { + name = "code-owners"; + version = "7de40d8"; + src = fetchgit { + url = "https://gerrit.googlesource.com/plugins/code-owners"; + rev = "7de40d8b30e55eb64316b6fc3d0d00da9caddade"; + hash = "sha256-0sLwUcG9RN1o9vZGW8ErwL7UgJapgYoo8XMGsWLO25Q="; + }; + patches = [ + ./using-usernames.patch + ]; +} diff --git a/plugins/code-owners/using-usernames.patch b/plugins/code-owners/using-usernames.patch new file mode 100644 index 0000000..061d1c9 --- /dev/null +++ b/plugins/code-owners/using-usernames.patch @@ -0,0 +1,472 @@ +commit 29ace6c38ac513f7ec56ca425230d5712c081043 +Author: The nix-gerrit Authors +Date: Wed Sep 21 03:15:38 2022 +0100 + + Add support for usernames and groups + + Change-Id: I3ba8527f66216d08e555a6ac4451fe0d1e090de5 + +diff --git a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java +index 70009591..6dc596c9 100644 +--- a/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java ++++ b/java/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolver.java +@@ -17,6 +17,8 @@ package com.google.gerrit.plugins.codeowners.backend; + import static com.google.common.base.Preconditions.checkState; + import static com.google.common.collect.ImmutableMap.toImmutableMap; + import static com.google.common.collect.ImmutableSet.toImmutableSet; ++import static com.google.common.collect.ImmutableSetMultimap.flatteningToImmutableSetMultimap; ++import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap; + import static com.google.gerrit.plugins.codeowners.backend.CodeOwnersInternalServerErrorException.newInternalServerError; + import static java.util.Objects.requireNonNull; + +@@ -25,6 +27,7 @@ import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.ImmutableMultimap; + import com.google.common.collect.ImmutableSet; ++import com.google.common.collect.ImmutableSetMultimap; + import com.google.common.collect.Iterables; + import com.google.common.collect.Streams; + import com.google.common.flogger.FluentLogger; +@@ -33,17 +36,24 @@ import com.google.gerrit.entities.Project; + import com.google.gerrit.metrics.Timer0; + import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration; + import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics; ++import com.google.gerrit.server.AnonymousUser; + import com.google.gerrit.server.CurrentUser; + import com.google.gerrit.server.IdentifiedUser; + import com.google.gerrit.server.account.AccountCache; + import com.google.gerrit.server.account.AccountControl; + import com.google.gerrit.server.account.AccountState; ++import com.google.gerrit.server.account.GroupBackend; ++import com.google.gerrit.server.account.GroupBackends; ++import com.google.gerrit.server.account.InternalGroupBackend; + import com.google.gerrit.server.account.externalids.ExternalId; + import com.google.gerrit.server.account.externalids.ExternalIdCache; + import com.google.gerrit.server.permissions.GlobalPermission; + import com.google.gerrit.server.permissions.PermissionBackend; + import com.google.gerrit.server.permissions.PermissionBackendException; ++import com.google.gerrit.server.util.RequestContext; ++import com.google.gerrit.server.util.ThreadLocalRequestContext; + import com.google.inject.Inject; ++import com.google.inject.OutOfScopeException; + import com.google.inject.Provider; + import java.io.IOException; + import java.nio.file.Path; +@@ -102,6 +112,8 @@ public class CodeOwnerResolver { + + @VisibleForTesting public static final String ALL_USERS_WILDCARD = "*"; + ++ public static final String GROUP_PREFIX = "group:"; ++ + private final CodeOwnersPluginConfiguration codeOwnersPluginConfiguration; + private final PermissionBackend permissionBackend; + private final Provider currentUser; +@@ -112,6 +124,8 @@ public class CodeOwnerResolver { + private final CodeOwnerMetrics codeOwnerMetrics; + private final UnresolvedImportFormatter unresolvedImportFormatter; + private final TransientCodeOwnerCache transientCodeOwnerCache; ++ private final InternalGroupBackend groupBackend; ++ private final ThreadLocalRequestContext context; + + // Enforce visibility by default. + private boolean enforceVisibility = true; +@@ -132,7 +146,9 @@ public class CodeOwnerResolver { + PathCodeOwners.Factory pathCodeOwnersFactory, + CodeOwnerMetrics codeOwnerMetrics, + UnresolvedImportFormatter unresolvedImportFormatter, +- TransientCodeOwnerCache transientCodeOwnerCache) { ++ TransientCodeOwnerCache transientCodeOwnerCache, ++ InternalGroupBackend groupBackend, ++ ThreadLocalRequestContext context) { + this.codeOwnersPluginConfiguration = codeOwnersPluginConfiguration; + this.permissionBackend = permissionBackend; + this.currentUser = currentUser; +@@ -143,6 +159,8 @@ public class CodeOwnerResolver { + this.codeOwnerMetrics = codeOwnerMetrics; + this.unresolvedImportFormatter = unresolvedImportFormatter; + this.transientCodeOwnerCache = transientCodeOwnerCache; ++ this.groupBackend = groupBackend; ++ this.context = context; + } + + /** +@@ -361,6 +379,12 @@ public class CodeOwnerResolver { + "cannot resolve code owner email %s: no account with this email exists", + CodeOwnerResolver.ALL_USERS_WILDCARD)); + } ++ if (codeOwnerReference.email().startsWith(GROUP_PREFIX)) { ++ return OptionalResultWithMessages.createEmpty( ++ String.format( ++ "cannot resolve code owner email %s: this is a group", ++ codeOwnerReference.email())); ++ } + + ImmutableList.Builder messageBuilder = ImmutableList.builder(); + AtomicBoolean ownedByAllUsers = new AtomicBoolean(false); +@@ -405,9 +429,53 @@ public class CodeOwnerResolver { + ImmutableMultimap annotations) { + requireNonNull(codeOwnerReferences, "codeOwnerReferences"); + ++ ImmutableSet groupsToResolve = ++ codeOwnerReferences.stream() ++ .map(CodeOwnerReference::email) ++ .filter(ref -> ref.startsWith(GROUP_PREFIX)) ++ .map(ref -> ref.substring(GROUP_PREFIX.length())) ++ .collect(toImmutableSet()); ++ ++ // When we call GroupBackends.findExactSuggestion we need to ensure that we ++ // have a user in context. This is because the suggestion backend is ++ // likely to want to try to check that we can actually see the group it's ++ // returning (which we also check for explicitly, because I have trust ++ // issues). ++ RequestContext oldCtx = context.getContext(); ++ // Check if we have a user in the context at all... ++ try { ++ oldCtx.getUser(); ++ } catch (OutOfScopeException | NullPointerException e) { ++ // Nope. ++ RequestContext newCtx = () -> { ++ return new AnonymousUser(); ++ }; ++ context.setContext(newCtx); ++ } ++ ImmutableSetMultimap resolvedGroups = null; ++ try { ++ resolvedGroups = ++ groupsToResolve.stream() ++ .map(groupName -> GroupBackends.findExactSuggestion(groupBackend, groupName)) ++ .filter(groupRef -> groupRef != null) ++ .filter(groupRef -> groupBackend.isVisibleToAll(groupRef.getUUID())) ++ .map(groupRef -> groupBackend.get(groupRef.getUUID())) ++ .collect(flatteningToImmutableSetMultimap( ++ groupRef -> GROUP_PREFIX + groupRef.getName(), ++ groupRef -> accountCache ++ .get(ImmutableSet.copyOf(groupRef.getMembers())) ++ .values().stream() ++ .map(accountState -> CodeOwner.create(accountState.account().id())))); ++ } finally { ++ context.setContext(oldCtx); ++ } ++ ImmutableSetMultimap usersToGroups = ++ resolvedGroups.inverse(); ++ + ImmutableSet emailsToResolve = + codeOwnerReferences.stream() + .map(CodeOwnerReference::email) ++ .filter(ref -> !ref.startsWith(GROUP_PREFIX)) + .filter(filterOutAllUsersWildCard(ownedByAllUsers)) + .collect(toImmutableSet()); + +@@ -442,7 +510,8 @@ public class CodeOwnerResolver { + ImmutableMap codeOwnersByEmail = + accountsByEmail.map(mapToCodeOwner()).collect(toImmutableMap(Pair::key, Pair::value)); + +- if (codeOwnersByEmail.keySet().size() < emailsToResolve.size()) { ++ if (codeOwnersByEmail.keySet().size() < emailsToResolve.size() || ++ resolvedGroups.keySet().size() < groupsToResolve.size()) { + hasUnresolvedCodeOwners.set(true); + } + +@@ -456,7 +525,9 @@ public class CodeOwnerResolver { + cachedCodeOwnersByEmail.entrySet().stream() + .filter(e -> e.getValue().isPresent()) + .map(e -> Pair.of(e.getKey(), e.getValue().get())); +- Streams.concat(newlyResolvedCodeOwnersStream, cachedCodeOwnersStream) ++ Stream> resolvedGroupsCodeOwnersStream = ++ resolvedGroups.entries().stream().map(e -> Pair.of(e.getKey(), e.getValue())); ++ Streams.concat(Streams.concat(newlyResolvedCodeOwnersStream, cachedCodeOwnersStream), resolvedGroupsCodeOwnersStream) + .forEach( + p -> { + ImmutableSet.Builder annotationBuilder = ImmutableSet.builder(); +@@ -467,6 +538,12 @@ public class CodeOwnerResolver { + annotationBuilder.addAll( + annotations.get(CodeOwnerReference.create(ALL_USERS_WILDCARD))); + ++ // annotations for the groups this user is in apply as well ++ for (String group : usersToGroups.get(p.value())) { ++ annotationBuilder.addAll( ++ annotations.get(CodeOwnerReference.create(group))); ++ } ++ + if (!codeOwnersWithAnnotations.containsKey(p.value())) { + codeOwnersWithAnnotations.put(p.value(), new HashSet<>()); + } +@@ -570,7 +647,7 @@ public class CodeOwnerResolver { + } + + messages.add(String.format("email %s has no domain", email)); +- return false; ++ return true; // TVL: we allow domain-less strings which are treated as usernames. + } + + /** +@@ -585,11 +662,29 @@ public class CodeOwnerResolver { + */ + private ImmutableMap> lookupExternalIds( + ImmutableList.Builder messages, ImmutableSet emails) { ++ String[] actualEmails = emails.stream() ++ .filter(email -> email.contains("@")) ++ .toArray(String[]::new); ++ ImmutableSet usernames = emails.stream() ++ .filter(email -> !email.contains("@")) ++ .collect(ImmutableSet.toImmutableSet()); + try { +- ImmutableMap> extIdsByEmail = +- externalIdCache.byEmails(emails.toArray(new String[0])).asMap(); ++ ImmutableMap> extIds = ++ new ImmutableMap.Builder>() ++ .putAll(externalIdCache.byEmails(actualEmails).asMap()) ++ .putAll(externalIdCache.allByAccount().entries().stream() ++ .map(entry -> entry.getValue()) ++ .filter(externalId -> ++ externalId.key().scheme() != null && ++ externalId.key().isScheme(ExternalId.SCHEME_USERNAME) && ++ usernames.contains(externalId.key().id())) ++ .collect(toImmutableSetMultimap( ++ externalId -> externalId.key().id(), ++ externalId -> externalId)) ++ .asMap()) ++ .build(); + emails.stream() +- .filter(email -> !extIdsByEmail.containsKey(email)) ++ .filter(email -> !extIds.containsKey(email)) + .forEach( + email -> { + transientCodeOwnerCache.cacheNonResolvable(email); +@@ -598,7 +693,7 @@ public class CodeOwnerResolver { + "cannot resolve code owner email %s: no account with this email exists", + email)); + }); +- return extIdsByEmail; ++ return extIds; + } catch (IOException e) { + throw newInternalServerError( + String.format("cannot resolve code owner emails: %s", emails), e); +@@ -815,6 +910,15 @@ public class CodeOwnerResolver { + user != null ? user.getLoggableName() : currentUser.get().getLoggableName())); + return true; + } ++ if (!email.contains("@")) { ++ // the email is the username of the account, or a group, or something else. ++ messages.add( ++ String.format( ++ "account %s is visible to user %s", ++ accountState.account().id(), ++ user != null ? user.getLoggableName() : currentUser.get().getLoggableName())); ++ return true; ++ } + + if (user != null) { + if (user.hasEmailAddress(email)) { +diff --git a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java +index 5f350998..7977ba55 100644 +--- a/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java ++++ b/java/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParser.java +@@ -149,7 +149,8 @@ public class FindOwnersCodeOwnerConfigParser implements CodeOwnerConfigParser { + private static final String EOL = "[\\s]*(#.*)?$"; // end-of-line + private static final String GLOB = "[^\\s,=]+"; // a file glob + +- private static final String EMAIL_OR_STAR = "([^\\s<>@,]+@[^\\s<>@#,]+|\\*)"; ++ // Also allows usernames, and group:$GROUP_NAME. ++ private static final String EMAIL_OR_STAR = "([^\\s<>@,]+@[^\\s<>@#,]+?|\\*|[a-zA-Z0-9_\\-]+|group:[a-zA-Z0-9_\\-]+)"; + private static final String EMAIL_LIST = + "(" + EMAIL_OR_STAR + "(" + COMMA + EMAIL_OR_STAR + ")*)"; + +diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/AbstractFileBasedCodeOwnerBackendTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/AbstractFileBasedCodeOwnerBackendTest.java +index 7ec92959..59cf7e05 100644 +--- a/javatests/com/google/gerrit/plugins/codeowners/backend/AbstractFileBasedCodeOwnerBackendTest.java ++++ b/javatests/com/google/gerrit/plugins/codeowners/backend/AbstractFileBasedCodeOwnerBackendTest.java +@@ -424,7 +424,7 @@ public abstract class AbstractFileBasedCodeOwnerBackendTest extends AbstractCode + .commit() + .parent(head) + .message("Add invalid test code owner config") +- .add(JgitPath.of(codeOwnerConfigKey.filePath(getFileName())).get(), "INVALID")); ++ .add(JgitPath.of(codeOwnerConfigKey.filePath(getFileName())).get(), "INVALID!")); + } + + // Try to update the code owner config. +diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java +index 6171aca9..37699012 100644 +--- a/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java ++++ b/javatests/com/google/gerrit/plugins/codeowners/backend/CodeOwnerResolverTest.java +@@ -24,8 +24,10 @@ import com.google.gerrit.acceptance.TestAccount; + import com.google.gerrit.acceptance.TestMetricMaker; + import com.google.gerrit.acceptance.config.GerritConfig; + import com.google.gerrit.acceptance.testsuite.account.AccountOperations; ++import com.google.gerrit.acceptance.testsuite.group.GroupOperations; + import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; + import com.google.gerrit.entities.Account; ++import com.google.gerrit.entities.AccountGroup; + import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest; + import com.google.gerrit.server.ServerInitiated; + import com.google.gerrit.server.account.AccountsUpdate; +@@ -51,6 +53,7 @@ public class CodeOwnerResolverTest extends AbstractCodeOwnersTest { + @Inject private RequestScopeOperations requestScopeOperations; + @Inject @ServerInitiated private Provider accountsUpdate; + @Inject private AccountOperations accountOperations; ++ @Inject private GroupOperations groupOperations; + @Inject private ExternalIdNotes.Factory externalIdNotesFactory; + @Inject private TestMetricMaker testMetricMaker; + @Inject private ExternalIdFactory externalIdFactory; +@@ -112,6 +115,18 @@ public class CodeOwnerResolverTest extends AbstractCodeOwnersTest { + .contains(String.format("account %s is visible to user %s", admin.id(), admin.username())); + } + ++ @Test ++ public void resolveCodeOwnerReferenceForUsername() throws Exception { ++ OptionalResultWithMessages result = ++ codeOwnerResolverProvider ++ .get() ++ .resolveWithMessages(CodeOwnerReference.create(admin.username())); ++ assertThat(result.get()).hasAccountIdThat().isEqualTo(admin.id()); ++ assertThat(result) ++ .hasMessagesThat() ++ .contains(String.format("account %s is visible to user %s", admin.id(), admin.username())); ++ } ++ + @Test + public void cannotResolveCodeOwnerReferenceForStarAsEmail() throws Exception { + OptionalResultWithMessages result = +@@ -127,6 +142,18 @@ public class CodeOwnerResolverTest extends AbstractCodeOwnersTest { + CodeOwnerResolver.ALL_USERS_WILDCARD)); + } + ++ @Test ++ public void cannotResolveCodeOwnerReferenceForGroup() throws Exception { ++ OptionalResultWithMessages result = ++ codeOwnerResolverProvider ++ .get() ++ .resolveWithMessages(CodeOwnerReference.create("group:Administrators")); ++ assertThat(result).isEmpty(); ++ assertThat(result) ++ .hasMessagesThat() ++ .contains("cannot resolve code owner email group:Administrators: this is a group"); ++ } ++ + @Test + public void resolveCodeOwnerReferenceForAmbiguousEmailIfOtherAccountIsInactive() + throws Exception { +@@ -397,6 +424,64 @@ public class CodeOwnerResolverTest extends AbstractCodeOwnersTest { + assertThat(result.hasUnresolvedCodeOwners()).isFalse(); + } + ++ @Test ++ public void resolvePathCodeOwnersWhenNonVisibleGroupIsUsed() throws Exception { ++ CodeOwnerConfig codeOwnerConfig = ++ CodeOwnerConfig.builder(CodeOwnerConfig.Key.create(project, "master", "/"), TEST_REVISION) ++ .addCodeOwnerSet( ++ CodeOwnerSet.createWithoutPathExpressions("group:Administrators")) ++ .build(); ++ ++ CodeOwnerResolverResult result = ++ codeOwnerResolverProvider ++ .get() ++ .resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md")); ++ assertThat(result.codeOwnersAccountIds()).isEmpty(); ++ assertThat(result.ownedByAllUsers()).isFalse(); ++ assertThat(result.hasUnresolvedCodeOwners()).isTrue(); ++ } ++ ++ @Test ++ public void resolvePathCodeOwnersWhenVisibleGroupIsUsed() throws Exception { ++ AccountGroup.UUID createdGroupUUID = groupOperations ++ .newGroup() ++ .name("VisibleGroup") ++ .visibleToAll(true) ++ .addMember(admin.id()) ++ .create(); ++ ++ CodeOwnerConfig codeOwnerConfig = ++ CodeOwnerConfig.builder(CodeOwnerConfig.Key.create(project, "master", "/"), TEST_REVISION) ++ .addCodeOwnerSet( ++ CodeOwnerSet.createWithoutPathExpressions("group:VisibleGroup")) ++ .build(); ++ ++ CodeOwnerResolverResult result = ++ codeOwnerResolverProvider ++ .get() ++ .resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md")); ++ assertThat(result.codeOwnersAccountIds()).containsExactly(admin.id()); ++ assertThat(result.ownedByAllUsers()).isFalse(); ++ assertThat(result.hasUnresolvedCodeOwners()).isFalse(); ++ } ++ ++ @Test ++ public void resolvePathCodeOwnersWhenUsernameIsUsed() throws Exception { ++ CodeOwnerConfig codeOwnerConfig = ++ CodeOwnerConfig.builder(CodeOwnerConfig.Key.create(project, "master", "/"), TEST_REVISION) ++ .addCodeOwnerSet( ++ CodeOwnerSet.createWithoutPathExpressions(admin.username())) ++ .build(); ++ ++ CodeOwnerResolverResult result = ++ codeOwnerResolverProvider ++ .get() ++ .resolvePathCodeOwners(codeOwnerConfig, Paths.get("/README.md")); ++ assertThat(result.codeOwnersAccountIds()).containsExactly(admin.id()); ++ assertThat(result.ownedByAllUsers()).isFalse(); ++ assertThat(result.hasUnresolvedCodeOwners()).isFalse(); ++ } ++ + @Test + public void resolvePathCodeOwnersNonResolvableCodeOwnersAreFilteredOut() throws Exception { + CodeOwnerConfig codeOwnerConfig = +@@ -655,7 +740,7 @@ public class CodeOwnerResolverTest extends AbstractCodeOwnersTest { + "domain example.com of email foo@example.org@example.com is allowed"); + assertIsEmailDomainAllowed( + "foo@example.org", false, "domain example.org of email foo@example.org is not allowed"); +- assertIsEmailDomainAllowed("foo", false, "email foo has no domain"); ++ assertIsEmailDomainAllowed("foo", true, "email foo has no domain"); + assertIsEmailDomainAllowed( + "foo@example.com@example.org", + false, +diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java +index 260e635e..7aab99d0 100644 +--- a/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java ++++ b/javatests/com/google/gerrit/plugins/codeowners/backend/findowners/FindOwnersCodeOwnerConfigParserTest.java +@@ -158,16 +158,42 @@ public class FindOwnersCodeOwnerConfigParserTest extends AbstractCodeOwnerConfig + codeOwnerConfigParser.parse( + TEST_REVISION, + CodeOwnerConfig.Key.create(project, "master", "/"), +- getCodeOwnerConfig(EMAIL_1, "INVALID", "NOT_AN_EMAIL", EMAIL_2))); ++ getCodeOwnerConfig(EMAIL_1, "INVALID!", "NOT!AN_EMAIL", EMAIL_2))); + assertThat(exception.getFullMessage(FindOwnersBackend.CODE_OWNER_CONFIG_FILE_NAME)) + .isEqualTo( + String.format( + "invalid code owner config file '/OWNERS' (project = %s, branch = master):\n" +- + " invalid line: INVALID\n" +- + " invalid line: NOT_AN_EMAIL", ++ + " invalid line: INVALID!\n" ++ + " invalid line: NOT!AN_EMAIL", + project)); + } + ++ @Test ++ public void codeOwnerConfigWithUsernames() throws Exception { ++ assertParseAndFormat( ++ getCodeOwnerConfig(EMAIL_1, "USERNAME", EMAIL_2), ++ codeOwnerConfig -> ++ assertThat(codeOwnerConfig) ++ .hasCodeOwnerSetsThat() ++ .onlyElement() ++ .hasCodeOwnersEmailsThat() ++ .containsExactly(EMAIL_1, "USERNAME", EMAIL_2), ++ getCodeOwnerConfig(EMAIL_1, "USERNAME", EMAIL_2)); ++ } ++ ++ @Test ++ public void codeOwnerConfigWithGroups() throws Exception { ++ assertParseAndFormat( ++ getCodeOwnerConfig(EMAIL_1, "group:tvl-employees", EMAIL_2), ++ codeOwnerConfig -> ++ assertThat(codeOwnerConfig) ++ .hasCodeOwnerSetsThat() ++ .onlyElement() ++ .hasCodeOwnersEmailsThat() ++ .containsExactly(EMAIL_1, "group:tvl-employees", EMAIL_2), ++ getCodeOwnerConfig(EMAIL_1, "group:tvl-employees", EMAIL_2)); ++ } ++ + @Test + public void codeOwnerConfigWithComment() throws Exception { + assertParseAndFormat( diff --git a/plugins/oauth/default.nix b/plugins/oauth/default.nix new file mode 100644 index 0000000..4b0f95a --- /dev/null +++ b/plugins/oauth/default.nix @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ buildGerritBazelPlugin, fetchgit }: + +buildGerritBazelPlugin rec { + name = "oauth"; + version = "982316"; + src = fetchgit { + url = "https://gerrit.googlesource.com/plugins/oauth"; + rev = "98231604d60788bb43490f1a301d792817ac8008"; + hash = "sha256-AuVO1Yys8BYqGHZI/adszCUg0JM2v4Td4fe26LdOPLM="; + }; + depsHash = "sha256-UjoGmNSyFx7NK4AUkk5oQbcJ/RpONbwwAlBgNnjXT+s="; + postOverlayPlugin = '' + cp "${src}/external_plugin_deps.bzl" "$out/plugins/external_plugin_deps.bzl" + ''; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..023005d --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors +# SPDX-License-Identifier: MIT + +{ nixpkgs ? +, pkgs ? import nixpkgs { } +}@args: + +(import ./default.nix args).gerrit.shell