Initial commit

This commit is contained in:
Luke Granger-Brown 2024-07-08 19:04:51 +01:00
commit 0a6e97b978
37 changed files with 1732 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
result*

18
.reuse/dep5 Normal file
View file

@ -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 <git@lukegb.com>
Source: https://git.lix.systems/lukegb/nix-gerrit
Files: AUTHORS
Copyright: The nix-gerrit Authors <git@lukegb.com>
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

1
AUTHORS Normal file
View file

@ -0,0 +1 @@
Luke Granger-Brown <git@lukegb.com>

20
COPYING Normal file
View file

@ -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.

73
LICENSES/Apache-2.0.txt Normal file
View file

@ -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.

9
LICENSES/MIT.txt Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) <year> <copyright holders>
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.

55
README.md Normal file
View file

@ -0,0 +1,55 @@
# nix-gerrit
<!--
SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
SPDX-License-Identifier: MIT
-->
[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'
```

View file

@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
{ makeSetupHook }:
makeSetupHook {
name = "rules_java_bazel_hook";
substitutions = {
local_java = ./local_java;
};
} ./setup-hook.sh

View file

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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")

View file

@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
workspace(name = "local_java")

View file

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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
}

View file

@ -0,0 +1,103 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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

View file

@ -0,0 +1,23 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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",
)

View file

@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
workspace(name = "nodejs")

View file

@ -0,0 +1,6 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
exec "@nodejs@/bin/node" "$@"

View file

@ -0,0 +1,6 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
exec "@nodejs@/bin/npm" "$@"

View file

@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT

View file

@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
workspace(name = "yarn")

View file

@ -0,0 +1,6 @@
#!/bin/sh
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
exec "@yarn@/bin/yarn" "$@"

View file

@ -0,0 +1,53 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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"
}

View file

@ -0,0 +1,160 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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;
}

24
default.nix Normal file
View file

@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
{ nixpkgs ? <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; }
];
})

27
flake.lock Normal file
View file

@ -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
}

22
flake.nix Normal file
View file

@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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}"; };
});
};
}

View file

@ -0,0 +1,37 @@
From 216843cff4a8e41ad9887118751a412c1a22ce72 Mon Sep 17 00:00:00 2001
From: The nix-gerrit Authors <git@lukegb.com>
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<string, string>([
['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

View file

@ -0,0 +1,37 @@
From 63f1ff6ea749ae2af29a53463bca81bc3f4bf25b Mon Sep 17 00:00:00 2001
From: The nix-gerrit Authors <git@lukegb.com>
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<string, string>([
['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

View file

@ -0,0 +1,214 @@
From ca2df6d7f53441d443d42908e30bf60fbfc15392 Mon Sep 17 00:00:00 2001
From: The nix-gerrit Authors <git@lukegb.com>
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<String, String[]> urlParameterMap,
Function<String, SanitizedContent> urlInScriptTagOrdainer,
- String requestedURL)
+ String requestedURL,
+ TitleComputer titleComputer)
throws URISyntaxException, RestApiException {
ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
data.putAll(
staticTemplateData(
canonicalURL, cdnPath, faviconPath, urlParameterMap, urlInScriptTagOrdainer))
- .putAll(dynamicTemplateData(gerritApi, requestedURL, canonicalURL));
+ .putAll(dynamicTemplateData(gerritApi, requestedURL, canonicalURL, titleComputer));
Set<String> 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<String, Object> dynamicTemplateData(
- GerritApi gerritApi, String requestedURL, String canonicalURL)
+ GerritApi gerritApi, String requestedURL, String canonicalURL, TitleComputer titleComputer)
throws RestApiException, URISyntaxException {
ImmutableMap.Builder<String, Object> data = ImmutableMap.builder();
Map<String, SanitizedContent> initialData = new HashMap<>();
@@ -141,6 +143,10 @@ public class IndexHtmlUtil {
}
data.put("gerritInitialData", initialData);
+
+ Optional<String> 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<String, SanitizedContent> 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<ChangesCollection> changes) {
+ this.changes = changes;
+ }
+
+ public Optional<String> 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<Change.Id> changeId = tryExtractChange(url.getPath());
+ if (changeId.isPresent()) {
+ return titleFromChangeId(changeId.get());
+ }
+
+ return Optional.empty();
+ }
+
+ private static final Pattern extractChangeIdRegex = Pattern.compile("^/(?:c/.*/\\+/)?(?<changeId>[0-9]+)(?:/[0-9]+)?(?:/.*)?$");
+ private final Provider<ChangesCollection> changes;
+
+ private Optional<Change.Id> tryExtractChange(String path) {
+ Matcher m = extractChangeIdRegex.matcher(path);
+ if (!m.matches()) {
+ return Optional.empty();
+ }
+ return Change.Id.tryParse(m.group("changeId"));
+ }
+
+ private Optional<String> 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: ?}
<!DOCTYPE html>{\n}
<html lang="en">{\n}
<meta charset="utf-8">{\n}
- <meta name="description" content="Gerrit Code Review">{\n}
+ {if $title}<title>{$title} · Gerrit Code Review</title>{\n}{/if}
+ <meta name="description" content="{if $title}{$title} · {/if}Gerrit Code Review">{\n}
<meta name="referrer" content="never">{\n}
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">{\n}
--
2.45.1

11
gerrit/bazelrc Normal file
View file

@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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"

117
gerrit/default.nix Normal file
View file

@ -0,0 +1,117 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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

View file

@ -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(

View file

@ -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(

View file

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
def web_test_repositories():
pass
def browser_repositories(*args, **kwargs):
pass

48
plugins/builder.nix Normal file
View file

@ -0,0 +1,48 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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}
'';
}))

View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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
];
}

View file

@ -0,0 +1,472 @@
commit 29ace6c38ac513f7ec56ca425230d5712c081043
Author: The nix-gerrit Authors <git@lukegb.com>
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> 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<String> messageBuilder = ImmutableList.builder();
AtomicBoolean ownedByAllUsers = new AtomicBoolean(false);
@@ -405,9 +429,53 @@ public class CodeOwnerResolver {
ImmutableMultimap<CodeOwnerReference, CodeOwnerAnnotation> annotations) {
requireNonNull(codeOwnerReferences, "codeOwnerReferences");
+ ImmutableSet<String> 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<String, CodeOwner> 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<CodeOwner, String> usersToGroups =
+ resolvedGroups.inverse();
+
ImmutableSet<String> 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<String, CodeOwner> 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<Pair<String, CodeOwner>> resolvedGroupsCodeOwnersStream =
+ resolvedGroups.entries().stream().map(e -> Pair.of(e.getKey(), e.getValue()));
+ Streams.concat(Streams.concat(newlyResolvedCodeOwnersStream, cachedCodeOwnersStream), resolvedGroupsCodeOwnersStream)
.forEach(
p -> {
ImmutableSet.Builder<CodeOwnerAnnotation> 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<String, Collection<ExternalId>> lookupExternalIds(
ImmutableList.Builder<String> messages, ImmutableSet<String> emails) {
+ String[] actualEmails = emails.stream()
+ .filter(email -> email.contains("@"))
+ .toArray(String[]::new);
+ ImmutableSet<String> usernames = emails.stream()
+ .filter(email -> !email.contains("@"))
+ .collect(ImmutableSet.toImmutableSet());
try {
- ImmutableMap<String, Collection<ExternalId>> extIdsByEmail =
- externalIdCache.byEmails(emails.toArray(new String[0])).asMap();
+ ImmutableMap<String, Collection<ExternalId>> extIds =
+ new ImmutableMap.Builder<String, Collection<ExternalId>>()
+ .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> 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<CodeOwner> 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<CodeOwner> result =
@@ -127,6 +142,18 @@ public class CodeOwnerResolverTest extends AbstractCodeOwnersTest {
CodeOwnerResolver.ALL_USERS_WILDCARD));
}
+ @Test
+ public void cannotResolveCodeOwnerReferenceForGroup() throws Exception {
+ OptionalResultWithMessages<CodeOwner> 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(

18
plugins/oauth/default.nix Normal file
View file

@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# 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"
'';
}

8
shell.nix Normal file
View file

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2024 The nix-gerrit Authors <git@lukegb.com>
# SPDX-License-Identifier: MIT
{ nixpkgs ? <nixpkgs>
, pkgs ? import nixpkgs { }
}@args:
(import ./default.nix args).gerrit.shell