Merge "Make primary actions in gr-change-actions use primary button styling."
diff --git a/AGENTS.md b/AGENTS.md
index 14eaac3..a8199e8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -110,6 +110,7 @@
- Java: Google Java Style Guide, use `./tools/gjf.sh run` before committing
- Commit messages: max 72 chars/line, present tense, include Change-Id (added by git hook)
- **Release-Notes footer required**: Every commit must have `Release-Notes:` footer. Use `Release-Notes: skip` for small fixes/refactorings, or add a summary for notable changes
+- **Copyright headers**: All new Java files must include the Apache 2.0 license header with the current year (e.g., `// Copyright (C) 2026 The Android Open Source Project`)
## Key Patterns
diff --git a/Documentation/dev-crafting-changes.txt b/Documentation/dev-crafting-changes.txt
index 9a97aad..a30e10f 100644
--- a/Documentation/dev-crafting-changes.txt
+++ b/Documentation/dev-crafting-changes.txt
@@ -147,7 +147,7 @@
To format Java source code, Gerrit uses the
link:https://github.com/google/google-java-format[`google-java-format`,role=external,window=_blank]
-tool (version 1.24.0), and to format Bazel BUILD, WORKSPACE and .bzl files the
+tool (version 1.35.0), and to format Bazel BUILD, WORKSPACE and .bzl files the
link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`,role=external,window=_blank]
tool (version 4.0.0). Unused dependencies are found and removed using the
link:https://github.com/bazelbuild/buildtools/tree/master/unused_deps[`unused_deps`,role=external,window=_blank]
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 9aaa557..d8bfb77 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -102,7 +102,7 @@
To format source code, Gerrit uses the
link:https://github.com/google/google-java-format[`google-java-format`,role=external,window=_blank]
-tool (version 1.24.0), which automatically formats code to follow the
+tool (version 1.35.0), which automatically formats code to follow the
style guide. See link:dev-crafting-changes.html#style[Code Style] for the
instruction how to set up command line tool that uses this formatter.
The Eclipse plugin is provided that allows to format with the same
diff --git a/external_deps.lock.json b/external_deps.lock.json
index 20cce5f..f5f6d81 100644
--- a/external_deps.lock.json
+++ b/external_deps.lock.json
@@ -109,7 +109,7 @@
"commons-dbcp:commons-dbcp": -873877417,
"commons-digester:commons-digester": 1688456634,
"commons-io:commons-io": -1192334111,
- "commons-logging:commons-logging": 416626737,
+ "commons-logging:commons-logging": 243315756,
"commons-net:commons-net": 1227155931,
"commons-pool:commons-pool": -2015226625,
"commons-validator:commons-validator": -89255997,
@@ -125,8 +125,8 @@
"javax.servlet:javax.servlet-api": 669233360,
"junit:junit": -744267592,
"log4j:log4j": 182326902,
- "net.bytebuddy:byte-buddy": 1859026651,
- "net.bytebuddy:byte-buddy-agent": 1422085603,
+ "net.bytebuddy:byte-buddy": 731630558,
+ "net.bytebuddy:byte-buddy-agent": 294689510,
"net.java.dev.jna:jna": 929040997,
"net.java.dev.jna:jna-platform": 1235639073,
"net.minidev:json-smart": -1043043954,
@@ -160,31 +160,31 @@
"org.apache.sshd:sshd-sftp": 2079258759,
"org.asciidoctor:asciidoctorj": -457860213,
"org.assertj:assertj-core": -1145412507,
- "org.bouncycastle:bcpg-jdk18on": 235240928,
- "org.bouncycastle:bcpkix-jdk18on": 1954093523,
- "org.bouncycastle:bcprov-jdk18on": 402064210,
- "org.bouncycastle:bcutil-jdk18on": 1337943403,
+ "org.bouncycastle:bcpg-jdk18on": -1572213535,
+ "org.bouncycastle:bcpkix-jdk18on": 146639060,
+ "org.bouncycastle:bcprov-jdk18on": -1405390253,
+ "org.bouncycastle:bcutil-jdk18on": -469511060,
"org.commonmark:commonmark": 1129543740,
"org.commonmark:commonmark-ext-autolink": -1853742120,
"org.commonmark:commonmark-ext-gfm-strikethrough": 350394231,
"org.commonmark:commonmark-ext-gfm-tables": 1881582931,
- "org.eclipse.jetty.ee8:jetty-ee8-nested": -685134473,
- "org.eclipse.jetty.ee8:jetty-ee8-security": 2052418638,
- "org.eclipse.jetty.ee8:jetty-ee8-servlet": -1368555033,
+ "org.eclipse.jetty.ee8:jetty-ee8-nested": -1812530566,
+ "org.eclipse.jetty.ee8:jetty-ee8-security": 925022545,
+ "org.eclipse.jetty.ee8:jetty-ee8-servlet": 1799016170,
"org.eclipse.jetty:jetty-http": 1984891007,
"org.eclipse.jetty:jetty-io": 1765684893,
"org.eclipse.jetty:jetty-jmx": -1704061949,
"org.eclipse.jetty:jetty-security": 1160320567,
"org.eclipse.jetty:jetty-server": 280305722,
"org.eclipse.jetty:jetty-servlet": 697742931,
- "org.eclipse.jetty:jetty-session": 292732683,
+ "org.eclipse.jetty:jetty-session": -834663410,
"org.eclipse.jetty:jetty-util": -1520256775,
"org.eclipse.jetty:jetty-util-ajax": -726746674,
"org.hamcrest:hamcrest": 1547523135,
"org.jruby:jruby-complete": -2103568068,
"org.json:json": -811907600,
"org.jsoup:jsoup": -1061087167,
- "org.mockito:mockito-core": 629099222,
+ "org.mockito:mockito-core": 1330163800,
"org.nibor.autolink:autolink": -342487050,
"org.objenesis:objenesis": 748376655,
"org.openid4java:openid4java": -842286787,
@@ -203,7 +203,7 @@
"org.slf4j:slf4j-log4j12": -630224096,
"org.slf4j:slf4j-reload4j": -1466046388,
"org.slf4j:slf4j-simple": -487947767,
- "org.tukaani:xz": 64286142,
+ "org.tukaani:xz": -1743168321,
"repositories": 2019057769,
"xerces:xercesImpl": -1165914651,
"xml-apis:xml-apis": -113825062
@@ -347,10 +347,10 @@
"javax.servlet:javax.servlet-api:jar:sources": 137081253,
"junit:junit": 238187285,
"junit:junit:jar:sources": 1084731434,
- "net.bytebuddy:byte-buddy": 397933364,
- "net.bytebuddy:byte-buddy-agent": -835790122,
- "net.bytebuddy:byte-buddy-agent:jar:sources": -1511145134,
- "net.bytebuddy:byte-buddy:jar:sources": 1647745115,
+ "net.bytebuddy:byte-buddy": 1931414768,
+ "net.bytebuddy:byte-buddy-agent": 1328010878,
+ "net.bytebuddy:byte-buddy-agent:jar:sources": -899388612,
+ "net.bytebuddy:byte-buddy:jar:sources": 1062390101,
"net.java.dev.jna:jna": -1916526385,
"net.java.dev.jna:jna-platform": 459618853,
"net.java.dev.jna:jna-platform:jar:sources": -2077304647,
@@ -407,16 +407,16 @@
"org.apache.sshd:sshd-sftp:jar:sources": 1450030982,
"org.asciidoctor:asciidoctorj": -943976727,
"org.asciidoctor:asciidoctorj:jar:sources": -1022891917,
- "org.assertj:assertj-core": -990295630,
+ "org.assertj:assertj-core": 1465577983,
"org.assertj:assertj-core:jar:sources": 1906472612,
- "org.bouncycastle:bcpg-jdk18on": -733747199,
- "org.bouncycastle:bcpg-jdk18on:jar:sources": -1694649401,
- "org.bouncycastle:bcpkix-jdk18on": -1768219399,
- "org.bouncycastle:bcpkix-jdk18on:jar:sources": -1872178221,
- "org.bouncycastle:bcprov-jdk18on": 2094444272,
- "org.bouncycastle:bcprov-jdk18on:jar:sources": -486883993,
- "org.bouncycastle:bcutil-jdk18on": -1524758277,
- "org.bouncycastle:bcutil-jdk18on:jar:sources": 522880491,
+ "org.bouncycastle:bcpg-jdk18on": 726067526,
+ "org.bouncycastle:bcpg-jdk18on:jar:sources": 812126316,
+ "org.bouncycastle:bcpkix-jdk18on": 2070253037,
+ "org.bouncycastle:bcpkix-jdk18on:jar:sources": -1497475064,
+ "org.bouncycastle:bcprov-jdk18on": -1778261676,
+ "org.bouncycastle:bcprov-jdk18on:jar:sources": -1245673492,
+ "org.bouncycastle:bcutil-jdk18on": 86705488,
+ "org.bouncycastle:bcutil-jdk18on:jar:sources": -784396673,
"org.checkerframework:checker-compat-qual": -1678975214,
"org.checkerframework:checker-compat-qual:jar:sources": -673395382,
"org.checkerframework:checker-qual": -1657280421,
@@ -429,14 +429,14 @@
"org.commonmark:commonmark-ext-gfm-tables": -435057552,
"org.commonmark:commonmark-ext-gfm-tables:jar:sources": -1247551792,
"org.commonmark:commonmark:jar:sources": -1275158082,
- "org.eclipse.jetty.ee8:jetty-ee8-nested": 1225428153,
- "org.eclipse.jetty.ee8:jetty-ee8-nested:jar:sources": 1999421701,
- "org.eclipse.jetty.ee8:jetty-ee8-security": 667551496,
- "org.eclipse.jetty.ee8:jetty-ee8-security:jar:sources": 447244400,
- "org.eclipse.jetty.ee8:jetty-ee8-servlet": 368157283,
- "org.eclipse.jetty.ee8:jetty-ee8-servlet:jar:sources": -2138255372,
- "org.eclipse.jetty.toolchain:jetty-servlet-api": 626738376,
- "org.eclipse.jetty.toolchain:jetty-servlet-api:jar:sources": -717561233,
+ "org.eclipse.jetty.ee8:jetty-ee8-nested": 134721126,
+ "org.eclipse.jetty.ee8:jetty-ee8-nested:jar:sources": 1857707413,
+ "org.eclipse.jetty.ee8:jetty-ee8-security": 467549301,
+ "org.eclipse.jetty.ee8:jetty-ee8-security:jar:sources": -946021443,
+ "org.eclipse.jetty.ee8:jetty-ee8-servlet": -313969048,
+ "org.eclipse.jetty.ee8:jetty-ee8-servlet:jar:sources": 450880365,
+ "org.eclipse.jetty.toolchain:jetty-servlet-api": 380579650,
+ "org.eclipse.jetty.toolchain:jetty-servlet-api:jar:sources": 212656908,
"org.eclipse.jetty:jetty-http": -2051325092,
"org.eclipse.jetty:jetty-http:jar:sources": -882391521,
"org.eclipse.jetty:jetty-io": 1774579468,
@@ -449,8 +449,8 @@
"org.eclipse.jetty:jetty-server:jar:sources": -710016839,
"org.eclipse.jetty:jetty-servlet": -974397932,
"org.eclipse.jetty:jetty-servlet:jar:sources": 1281350506,
- "org.eclipse.jetty:jetty-session": 1978843064,
- "org.eclipse.jetty:jetty-session:jar:sources": 244883860,
+ "org.eclipse.jetty:jetty-session": -559237008,
+ "org.eclipse.jetty:jetty-session:jar:sources": -351578476,
"org.eclipse.jetty:jetty-util": 2086696316,
"org.eclipse.jetty:jetty-util-ajax": -451213174,
"org.eclipse.jetty:jetty-util-ajax:jar:sources": 1686109257,
@@ -465,8 +465,8 @@
"org.jsoup:jsoup:jar:sources": 1377510617,
"org.jspecify:jspecify": -797399878,
"org.jspecify:jspecify:jar:sources": 1011232509,
- "org.mockito:mockito-core": 1840231411,
- "org.mockito:mockito-core:jar:sources": -1422053423,
+ "org.mockito:mockito-core": -1882151935,
+ "org.mockito:mockito-core:jar:sources": 41691546,
"org.nibor.autolink:autolink": 1237374319,
"org.nibor.autolink:autolink:jar:sources": 1695391615,
"org.objenesis:objenesis": -1055367721,
@@ -501,8 +501,8 @@
"org.slf4j:slf4j-reload4j:jar:sources": 1517828526,
"org.slf4j:slf4j-simple": 2142683014,
"org.slf4j:slf4j-simple:jar:sources": 334190933,
- "org.tukaani:xz": 1462559085,
- "org.tukaani:xz:jar:sources": 1791731254,
+ "org.tukaani:xz": 1514570375,
+ "org.tukaani:xz:jar:sources": -610773207,
"xerces:xercesImpl": -723395208,
"xerces:xercesImpl:jar:sources": -127516017
},
@@ -1002,17 +1002,17 @@
},
"net.bytebuddy:byte-buddy": {
"shasums": {
- "jar": "e50ba78d8fd22e832c7a87bfa84cbdf93476ff4901b6e985ff66ebbde83f7f8a",
- "sources": "c052324e5a6d8659b4f854581d0d5aed82c58baee48a34d53b816e6443e54cad"
+ "jar": "227d3e0ad51915809143f6a744ac0f4cb21f03214dab28e52ee7007c5ad7e9d9",
+ "sources": "8529b9ec93e698165dde6fddfd57e0820bb8f6d17f91ada1d8c525563636dc4d"
},
- "version": "1.18.5"
+ "version": "1.18.8"
},
"net.bytebuddy:byte-buddy-agent": {
"shasums": {
- "jar": "12b55548c2301b3ca57dbcf820fbbca7ad6effd4c1d5189b8a34deef3bab8064",
- "sources": "227a50865fa0d060f98bc01ec21eb5c7561a5558862450e971307bab9fe14ba4"
+ "jar": "e303594d597de090abcb54580aa788478fc5e58f259e6ae914a1115f207c43af",
+ "sources": "881b5f123cefa2afc4ed89f8662da3ef9975e3a6e00f8f563b0470caf34b9511"
},
- "version": "1.18.5"
+ "version": "1.18.8"
},
"net.java.dev.jna:jna": {
"shasums": {
@@ -1219,31 +1219,31 @@
},
"org.bouncycastle:bcpg-jdk18on": {
"shasums": {
- "jar": "4077fd4517761c98a81944c70a376ce73f4eb3e44c03db1eb5d699fc28ab48aa",
- "sources": "fd5057e4ae59e8ac68917e6bf865ad92c335ec9e8b0fd973e0bb82f7d8e9d79b"
+ "jar": "c0e6303a0d7589040f400950ecee87a14b81312e84ed15e5390ebb0c4566ddab",
+ "sources": "a8baa033c57614d36c3d2339a8c8e5902a8a2ed8cb7387cdb2b919e5a4b15f30"
},
- "version": "1.83"
+ "version": "1.84"
},
"org.bouncycastle:bcpkix-jdk18on": {
"shasums": {
- "jar": "d3c4c6b700c74ef8164bb15e549d939721b8f14fc0ff89fe19b220243bcfcbd8",
- "sources": "f1f0f0565a5d84f0bdde5e812d260a9abacf9be2f57c49e461bfe9ff58340bb9"
+ "jar": "c87f16ed9e5ec61bc94151e9f3646ac44e50cd448121ce84367fa4b7ec7ec1bb",
+ "sources": "fe00c12243c28ead30ad6c7742be40ff005ab29f493c350b83b637fe4a9b5597"
},
- "version": "1.83"
+ "version": "1.84"
},
"org.bouncycastle:bcprov-jdk18on": {
"shasums": {
- "jar": "82cf3a2af766c3bc874f6d36b9f20a8b99a8f09762dc776e8a227a45d8daaafb",
- "sources": "0c335c8d599b92e264f4591087ca104615de0339bbba46ecc3a51af032de0b16"
+ "jar": "64d6c5a6121fcd927152dd182cbed39afe0fda641a970d9bcc0c9cb1858b2731",
+ "sources": "e5f04550f7740e588edcbd1654c59277cd7ee8725d8b674e44f7f8f4b9c5674a"
},
- "version": "1.83"
+ "version": "1.84"
},
"org.bouncycastle:bcutil-jdk18on": {
"shasums": {
- "jar": "ee7d0eb4e74de70a735f7fb36b604dd5c6ad35720d50b914604db042114a0185",
- "sources": "935984a8b1d1893dd033a9f237b85898bcfa6ae7890a214f10bade935cd3536f"
+ "jar": "b374e16963421fb9cfb01cc20d7ad8fd2f8b8188e3eef0ec0a8965e245f7619a",
+ "sources": "192b719273dc33e8fd6edc3b30b126760b6740cf2e1ac3cc7cf845c7ffec9f2b"
},
- "version": "1.83"
+ "version": "1.84"
},
"org.checkerframework:checker-compat-qual": {
"shasums": {
@@ -1289,31 +1289,31 @@
},
"org.eclipse.jetty.ee8:jetty-ee8-nested": {
"shasums": {
- "jar": "0bcc1d92d6dc482bac55979b6c9d7756f8b061295fc41cf2713354f9bd464c38",
- "sources": "82f199d42bbfaecdf1574347191f9177af11d4ea692760cd0a84d0b8c89b4a53"
+ "jar": "876fd83d52002d26ecb1a145ed7ae2397742a77c5e8ea9c6d309071b41cbb59c",
+ "sources": "c4f2633f8501679b59a0156cd64a6b49f34c27abf9896b6113f45fa7f6ae7d4a"
},
- "version": "12.1.5"
+ "version": "12.1.8"
},
"org.eclipse.jetty.ee8:jetty-ee8-security": {
"shasums": {
- "jar": "789ba4518cd2848563473d636270b42c541d34cf79bed36c84e3ce811f76c2cd",
- "sources": "f232e995398de354b6d4c8fe67b60a708bae0b0cccfe933787ed8966886e3139"
+ "jar": "5643ebb9781e97b4ebc5096bea9533fefdeef535b1afa8b8f2c7524901a26dbe",
+ "sources": "e29ee9d16fcbaa271c12bb43ee39b29b2c04e1af5087aabf8d1cd18754175eef"
},
- "version": "12.1.5"
+ "version": "12.1.8"
},
"org.eclipse.jetty.ee8:jetty-ee8-servlet": {
"shasums": {
- "jar": "3c27e08483b1f2860e727c7577e094e63af595159dabe28022dd4e10e37f12b0",
- "sources": "4a2e1e23c428c562bc456cc62e8bb0848f571c7505460b56927d51240e899474"
+ "jar": "183551aaff43938c5fc0fa649b62521b279eacbd53b3e5ee692cb1a3c08a6f8e",
+ "sources": "c0bb488146c42812a19c6f8e4c9f43aa55c5cf1c700bfcd2400c0493279159c5"
},
- "version": "12.1.5"
+ "version": "12.1.8"
},
"org.eclipse.jetty.toolchain:jetty-servlet-api": {
"shasums": {
- "jar": "d90bf1f8a9d2ba89f4510bb51e1516dcf94ef6dc034e00f233654abdd78f2210",
- "sources": "4184a1bf70ea545bceb0a5aef1061e1f9986d9fe8f22a1cc77cf22931fcb9029"
+ "jar": "89916c0360ad8be8b0b6bab41d9f0d5d03b742abb5e13aeeac06817c14fdf037",
+ "sources": "f6a7790d55a14a7c1d542d8b0246939ac720fe99e92fae3108c067c4266180fc"
},
- "version": "4.0.6"
+ "version": "4.0.9"
},
"org.eclipse.jetty:jetty-http": {
"shasums": {
@@ -1359,10 +1359,10 @@
},
"org.eclipse.jetty:jetty-session": {
"shasums": {
- "jar": "ebe4f30c6fe7656294d884e1dc8eea4c77b6c861e3c010847a76fd78164ae166",
- "sources": "629906b99619eee119e44ff3c786c89ef20350958e16546fd62fb4ec7f08c6d9"
+ "jar": "ebe84dd41942d7adda5f7d1304095d274e7915999451d848e887cbd409f947ea",
+ "sources": "3741c771d7204c47d8e72ae5b6e834ab4710c9fe6118d4777bfd910a17bb847f"
},
- "version": "12.1.5"
+ "version": "12.1.8"
},
"org.eclipse.jetty:jetty-util": {
"shasums": {
@@ -1415,10 +1415,10 @@
},
"org.mockito:mockito-core": {
"shasums": {
- "jar": "03db23de742cbca42aa3d6127fdace560fac37b036d931870801f84c288bd286",
- "sources": "d1b3d1cfe46502804a5e73fe21f6bf385697c317efedd6b582431ef1dc7068f7"
+ "jar": "ae295bebd5d11fab97ab297815dc7617188b86003cbce3dfd5c0d5c3a6cc4a0c",
+ "sources": "38f869a12a1168ca8dc2a4acd090d8129a7f81afc8f1fc61582701894755fefd"
},
- "version": "5.21.0"
+ "version": "5.23.0"
},
"org.nibor.autolink:autolink": {
"shasums": {
@@ -1541,10 +1541,10 @@
},
"org.tukaani:xz": {
"shasums": {
- "jar": "0a4077f6aeae2865532a564807af8d30c26acc6f63b7928d93bd7ab1f2190449",
- "sources": "aab470d8c28e718859f9c1f333ed693f926cd44d8e55851c0dda27dd4a6d568c"
+ "jar": "3e158a87bd73d8afb4b6e8239c013b7d049c48563f45860ce99cd2e448cf4a6b",
+ "sources": "c35c682fa8b617c1f6f21270df14bad4e11df2fe46962dfe45e765b7aec0181b"
},
- "version": "1.11"
+ "version": "1.12"
},
"xerces:xercesImpl": {
"shasums": {
@@ -3484,9 +3484,13 @@
"org.bouncycastle.crypto.examples",
"org.bouncycastle.crypto.fpe",
"org.bouncycastle.crypto.generators",
+ "org.bouncycastle.crypto.hash2curve",
+ "org.bouncycastle.crypto.hash2curve.data",
+ "org.bouncycastle.crypto.hash2curve.impl",
"org.bouncycastle.crypto.hpke",
"org.bouncycastle.crypto.io",
"org.bouncycastle.crypto.kems",
+ "org.bouncycastle.crypto.kems.mlkem",
"org.bouncycastle.crypto.macs",
"org.bouncycastle.crypto.modes",
"org.bouncycastle.crypto.modes.gcm",
@@ -3497,6 +3501,8 @@
"org.bouncycastle.crypto.prng",
"org.bouncycastle.crypto.prng.drbg",
"org.bouncycastle.crypto.signers",
+ "org.bouncycastle.crypto.signers.mldsa",
+ "org.bouncycastle.crypto.signers.slhdsa",
"org.bouncycastle.crypto.threshold",
"org.bouncycastle.crypto.tls",
"org.bouncycastle.crypto.util",
@@ -3546,7 +3552,7 @@
"org.bouncycastle.jcajce.provider.drbg",
"org.bouncycastle.jcajce.provider.kdf",
"org.bouncycastle.jcajce.provider.kdf.hkdf",
- "org.bouncycastle.jcajce.provider.kdf.pbepbkdf2",
+ "org.bouncycastle.jcajce.provider.kdf.pbkdf2",
"org.bouncycastle.jcajce.provider.kdf.scrypt",
"org.bouncycastle.jcajce.provider.keystore",
"org.bouncycastle.jcajce.provider.keystore.bc",
@@ -3564,6 +3570,7 @@
"org.bouncycastle.jce.netscape",
"org.bouncycastle.jce.provider",
"org.bouncycastle.jce.spec",
+ "org.bouncycastle.ldap",
"org.bouncycastle.math",
"org.bouncycastle.math.ec",
"org.bouncycastle.math.ec.custom.djb",
@@ -3577,7 +3584,6 @@
"org.bouncycastle.math.raw",
"org.bouncycastle.pqc.asn1",
"org.bouncycastle.pqc.crypto",
- "org.bouncycastle.pqc.crypto.bike",
"org.bouncycastle.pqc.crypto.cmce",
"org.bouncycastle.pqc.crypto.crystals.dilithium",
"org.bouncycastle.pqc.crypto.falcon",
@@ -3589,14 +3595,12 @@
"org.bouncycastle.pqc.crypto.mlkem",
"org.bouncycastle.pqc.crypto.newhope",
"org.bouncycastle.pqc.crypto.ntru",
+ "org.bouncycastle.pqc.crypto.ntruplus",
"org.bouncycastle.pqc.crypto.ntruprime",
- "org.bouncycastle.pqc.crypto.picnic",
- "org.bouncycastle.pqc.crypto.rainbow",
"org.bouncycastle.pqc.crypto.saber",
"org.bouncycastle.pqc.crypto.slhdsa",
"org.bouncycastle.pqc.crypto.snova",
"org.bouncycastle.pqc.crypto.sphincs",
- "org.bouncycastle.pqc.crypto.sphincsplus",
"org.bouncycastle.pqc.crypto.util",
"org.bouncycastle.pqc.crypto.xmss",
"org.bouncycastle.pqc.crypto.xwing",
@@ -3613,6 +3617,7 @@
"org.bouncycastle.pqc.jcajce.provider.mayo",
"org.bouncycastle.pqc.jcajce.provider.newhope",
"org.bouncycastle.pqc.jcajce.provider.ntru",
+ "org.bouncycastle.pqc.jcajce.provider.ntruplus",
"org.bouncycastle.pqc.jcajce.provider.ntruprime",
"org.bouncycastle.pqc.jcajce.provider.picnic",
"org.bouncycastle.pqc.jcajce.provider.saber",
@@ -3622,6 +3627,10 @@
"org.bouncycastle.pqc.jcajce.provider.util",
"org.bouncycastle.pqc.jcajce.provider.xmss",
"org.bouncycastle.pqc.jcajce.spec",
+ "org.bouncycastle.pqc.legacy.bike",
+ "org.bouncycastle.pqc.legacy.picnic",
+ "org.bouncycastle.pqc.legacy.rainbow",
+ "org.bouncycastle.pqc.legacy.sphincsplus",
"org.bouncycastle.pqc.math.ntru",
"org.bouncycastle.pqc.math.ntru.parameters",
"org.bouncycastle.util",
diff --git a/java/com/google/gerrit/entities/Permission.java b/java/com/google/gerrit/entities/Permission.java
index 885edc6..c212f0c 100644
--- a/java/com/google/gerrit/entities/Permission.java
+++ b/java/com/google/gerrit/entities/Permission.java
@@ -60,6 +60,7 @@
public static final String SUBMIT = "submit";
public static final String SUBMIT_AS = "submitAs";
public static final String TOGGLE_WORK_IN_PROGRESS_STATE = "toggleWipState";
+ public static final String POST_REVIEW_COMMENT = "postReviewComment";
public static final String VIEW_PRIVATE_CHANGES = "viewPrivateChanges";
public static final String AI_REVIEW = "aiReview";
@@ -99,6 +100,7 @@
NAMES_LC.add(SUBMIT.toLowerCase(Locale.US));
NAMES_LC.add(SUBMIT_AS.toLowerCase(Locale.US));
NAMES_LC.add(TOGGLE_WORK_IN_PROGRESS_STATE.toLowerCase(Locale.US));
+ NAMES_LC.add(POST_REVIEW_COMMENT.toLowerCase(Locale.US));
NAMES_LC.add(VIEW_PRIVATE_CHANGES.toLowerCase(Locale.US));
NAMES_LC.add(AI_REVIEW.toLowerCase(Locale.US));
diff --git a/java/com/google/gerrit/server/account/Accounts.java b/java/com/google/gerrit/server/account/Accounts.java
index 55192e9..ec781e0 100644
--- a/java/com/google/gerrit/server/account/Accounts.java
+++ b/java/com/google/gerrit/server/account/Accounts.java
@@ -14,11 +14,17 @@
package com.google.gerrit.server.account;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Account;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.Random;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -62,6 +68,19 @@
List<Account.Id> firstNIds(int n) throws IOException;
/**
+ * Returns n random account IDs.
+ *
+ * @param n the number of account IDs that should be returned
+ * @param seed seed that should be used to randomize the order
+ * @return n random account IDs
+ */
+ default ImmutableList<Account.Id> randomNIds(int n, long seed) throws IOException {
+ List<Account.Id> allIds = new ArrayList<>(allIds());
+ Collections.shuffle(allIds, new Random(seed));
+ return allIds.stream().limit(n).collect(toImmutableList());
+ }
+
+ /**
* Checks if any account exists.
*
* @return {@code true} if at least one account exists, otherwise {@code false}.
diff --git a/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java b/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java
index 28d3a43..02ad518 100644
--- a/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java
+++ b/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java
@@ -51,6 +51,7 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
+/** NoteDb-based implementation of {@link Accounts}. */
@Singleton
public class AccountsNoteDbImpl implements Accounts {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
index 0113355..2217d81 100644
--- a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
@@ -195,17 +195,17 @@
case FLUSH_CACHES, KILL_TASK, RUN_GC, VIEW_CACHES, VIEW_QUEUE ->
has(globalPermissionName(perm)) || can(GlobalPermission.MAINTAIN_SERVER);
case CREATE_ACCOUNT,
- CREATE_GROUP,
- DELETE_GROUP,
- CREATE_PROJECT,
- MAINTAIN_SERVER,
- MODIFY_ACCOUNT,
- READ_AS,
- STREAM_EVENTS,
- VIEW_ACCESS,
- VIEW_ALL_ACCOUNTS,
- VIEW_CONNECTIONS,
- VIEW_PLUGINS ->
+ CREATE_GROUP,
+ DELETE_GROUP,
+ CREATE_PROJECT,
+ MAINTAIN_SERVER,
+ MODIFY_ACCOUNT,
+ READ_AS,
+ STREAM_EVENTS,
+ VIEW_ACCESS,
+ VIEW_ALL_ACCOUNTS,
+ VIEW_CONNECTIONS,
+ VIEW_PLUGINS ->
has(globalPermissionName(perm)) || isAdmin();
case VIEW_SECONDARY_EMAILS ->
has(globalPermissionName(perm))
diff --git a/java/com/google/gerrit/server/query/change/EqualsLabelPredicates.java b/java/com/google/gerrit/server/query/change/EqualsLabelPredicates.java
index 488df59..369ddd2 100644
--- a/java/com/google/gerrit/server/query/change/EqualsLabelPredicates.java
+++ b/java/com/google/gerrit/server/query/change/EqualsLabelPredicates.java
@@ -90,6 +90,48 @@
}
}
+ /**
+ * Label predicate that trusts the index result without post-filtering.
+ *
+ * <p>Used when the query has no group or count constraint; the index result is exact and
+ * re-verification is unnecessary.
+ */
+ public static class IndexOnlyEqualsLabelPredicate extends ChangeIndexPredicate {
+ private final Matcher matcher;
+
+ public IndexOnlyEqualsLabelPredicate(
+ LabelPredicate.Args args, String label, int expVal, @Nullable Account.Id account) {
+ super(ChangeField.LABEL_SPEC, ChangeField.formatLabel(label, expVal, account, null));
+ this.matcher = new Matcher(args, label, expVal, account, null);
+ }
+
+ @Override
+ public boolean match(ChangeData object) {
+ return matcher.match(object);
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+ }
+
+ /**
+ * Returns a label predicate that post-filters only when group membership or vote count must be
+ * verified at query time; otherwise trusts the index result directly.
+ */
+ public static ChangeIndexPredicate indexPredicate(
+ LabelPredicate.Args args,
+ String label,
+ int expVal,
+ @Nullable Account.Id account,
+ @Nullable Integer count) {
+ if (args.group != null || count != null) {
+ return new IndexEqualsLabelPredicate(args, label, expVal, account, count);
+ }
+ return new IndexOnlyEqualsLabelPredicate(args, label, expVal, account);
+ }
+
private static class Matcher {
protected final AccountResolver accountResolver;
protected final ProjectCache projectCache;
@@ -182,6 +224,9 @@
hasVote = true;
if (match(cd, psa)) {
matchingVotes += 1;
+ if (count == null) {
+ break;
+ }
}
}
}
@@ -296,13 +341,15 @@
}
}
- IdentifiedUser reviewer = userFactory.create(approver);
- if (group != null && !reviewer.getEffectiveGroups().contains(group)) {
- logger.atFine().log(
- "vote %s on change %s doesn't match since the approver %s is not a member of the"
- + " expected group %s",
- psa, cd.change().getChangeId(), approver, group);
- return false;
+ if (group != null) {
+ IdentifiedUser reviewer = userFactory.create(approver);
+ if (!reviewer.getEffectiveGroups().contains(group)) {
+ logger.atFine().log(
+ "vote %s on change %s doesn't match since the approver %s is not a member of the"
+ + " expected group %s",
+ psa, cd.change().getChangeId(), approver, group);
+ return false;
+ }
}
// Check the user has 'READ' permission.
diff --git a/java/com/google/gerrit/server/query/change/LabelPredicate.java b/java/com/google/gerrit/server/query/change/LabelPredicate.java
index dc859a3..c838a70 100644
--- a/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -203,11 +203,11 @@
return new EqualsLabelPredicates.PostFilterEqualsLabelPredicate(args, label, expVal, count);
}
if (args.accounts == null || args.accounts.isEmpty()) {
- return new EqualsLabelPredicates.IndexEqualsLabelPredicate(args, label, expVal, count);
+ return EqualsLabelPredicates.indexPredicate(args, label, expVal, null, count);
}
List<Predicate<ChangeData>> r = new ArrayList<>();
for (Account.Id a : args.accounts) {
- r.add(new EqualsLabelPredicates.IndexEqualsLabelPredicate(args, label, expVal, a, count));
+ r.add(EqualsLabelPredicates.indexPredicate(args, label, expVal, a, count));
}
return or(r);
}
diff --git a/java/com/google/gerrit/server/query/change/MagicLabelPredicates.java b/java/com/google/gerrit/server/query/change/MagicLabelPredicates.java
index 68da77a..d28a60d 100644
--- a/java/com/google/gerrit/server/query/change/MagicLabelPredicates.java
+++ b/java/com/google/gerrit/server/query/change/MagicLabelPredicates.java
@@ -87,8 +87,7 @@
@Override
protected Predicate<ChangeData> numericPredicate(String label, short value) {
- return new EqualsLabelPredicates.IndexEqualsLabelPredicate(
- args, label, value, account, count);
+ return EqualsLabelPredicates.indexPredicate(args, label, value, account, count);
}
}
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index 514b993..6eb9a02 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -219,6 +219,10 @@
grant(config, refsFor, Permission.PUSH, registered);
grant(config, refsFor, Permission.PUSH_MERGE, registered);
});
+
+ config.upsertAccessSection(
+ AccessSection.ALL,
+ refsFor -> grant(config, refsFor, Permission.POST_REVIEW_COMMENT, registered));
}
private void initDefaultAclsForServiceUsers(
diff --git a/java/com/google/gerrit/server/schema/GrantPostReviewCommentPermission.java b/java/com/google/gerrit/server/schema/GrantPostReviewCommentPermission.java
new file mode 100644
index 0000000..e9f34fd
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/GrantPostReviewCommentPermission.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.schema.AclUtil.grant;
+
+import com.google.gerrit.entities.AccessSection;
+import com.google.gerrit.entities.GroupReference;
+import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * This class adds the "postReviewComment" permission to all hosts that call this method with the
+ * relevant projectName. This class should be called with AllProjects as the project, by all hosts
+ * before enabling the "postReviewComment" permission.
+ */
+public class GrantPostReviewCommentPermission {
+
+ private final GitRepositoryManager repoManager;
+ private final ProjectConfig.Factory projectConfigFactory;
+ private final SystemGroupBackend systemGroupBackend;
+ private final PersonIdent serverUser;
+
+ @Inject
+ public GrantPostReviewCommentPermission(
+ GitRepositoryManager repoManager,
+ ProjectConfig.Factory projectConfigFactory,
+ SystemGroupBackend systemGroupBackend,
+ @GerritPersonIdent PersonIdent serverUser) {
+ this.repoManager = repoManager;
+ this.projectConfigFactory = projectConfigFactory;
+ this.systemGroupBackend = systemGroupBackend;
+ this.serverUser = serverUser;
+ }
+
+ public void execute(Project.NameKey projectName) throws IOException, ConfigInvalidException {
+ GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS);
+ try (Repository repo = repoManager.openRepository(projectName)) {
+ MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, projectName, repo);
+ ProjectConfig projectConfig = projectConfigFactory.read(md);
+
+ AtomicBoolean shouldExit = new AtomicBoolean(false);
+ projectConfig.upsertAccessSection(
+ AccessSection.ALL,
+ all -> {
+ Permission permissionOnRefsStar =
+ all.build().getPermission(Permission.POST_REVIEW_COMMENT);
+ if (permissionOnRefsStar != null) {
+ if (permissionOnRefsStar.getRule(registeredUsers) == null) {
+ // If admins already changed the permission, don't do anything.
+ shouldExit.set(true);
+ return;
+ }
+ // permission already exists on refs/* for Registered Users, don't do anything.
+ return;
+ }
+ // If the permission doesn't exist on refs/* for Registered Users, grant it.
+ grant(projectConfig, all, Permission.POST_REVIEW_COMMENT, registeredUsers);
+ });
+
+ if (shouldExit.get()) {
+ return;
+ }
+
+ md.getCommitBuilder().setAuthor(serverUser);
+ md.getCommitBuilder().setCommitter(serverUser);
+ md.setMessage("Add Post Review Comment permission for all registered users\n");
+
+ projectConfig.commit(md);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
index d84ae60..69e6fb3 100644
--- a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
@@ -34,7 +34,8 @@
Schema_182.class,
Schema_183.class,
Schema_184.class,
- Schema_185.class)
+ Schema_185.class,
+ Schema_186.class)
.collect(toImmutableSortedMap(naturalOrder(), v -> guessVersion(v).get(), v -> v));
public static final int FIRST = ALL.firstKey();
diff --git a/java/com/google/gerrit/server/schema/Schema_186.java b/java/com/google/gerrit/server/schema/Schema_186.java
new file mode 100644
index 0000000..de1e315
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/Schema_186.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+public class Schema_186 implements NoteDbSchemaVersion {
+ @Override
+ public void upgrade(NoteDbSchemaVersion.Arguments args, UpdateUI ui) throws Exception {
+ new GrantPostReviewCommentPermission(
+ args.repoManager, args.projectConfigFactory, args.systemGroupBackend, args.serverUser)
+ .execute(args.allProjects);
+ }
+}
diff --git a/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java b/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java
index 5c3bcc1..7168cb6 100644
--- a/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java
+++ b/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java
@@ -54,6 +54,7 @@
ImmutableList.of(
"[access \"refs/*\"]",
" read = group Administrators",
+ " postReviewComment = group Registered Users",
" read = block group Blocked Users",
"[access \"refs/for/*\"]",
" addPatchSet = group Registered Users",
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 4272a88..67f33e8 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -300,7 +300,7 @@
if (repo.getRefDatabase().exactRef(ref) != null) {
RefUpdate ru = repo.updateRef(ref);
ru.setForceUpdate(true);
- assertWithMessage("Failed to delete " + ref)
+ assertWithMessage("Failed to delete %s", ref)
.that(ru.delete())
.isEqualTo(RefUpdate.Result.FORCED);
}
@@ -569,6 +569,20 @@
}
@Test
+ public void randomNIds() throws Exception {
+ accountOperations.newAccount().create();
+ accountOperations.newAccount().create();
+ accountOperations.newAccount().create();
+
+ ImmutableList<Account.Id> result = accounts.randomNIds(2, 12345L);
+
+ assertThat(result).hasSize(2);
+ for (Account.Id id : result) {
+ assertThat(gApi.accounts().id(id.get()).get()).isNotNull();
+ }
+ }
+
+ @Test
public void get() throws Exception {
AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter();
try (Registration registration =
diff --git a/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java
index 3558e61..80886d1 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java
@@ -68,6 +68,7 @@
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.group.testing.TestGroupBackend;
import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.schema.GrantPostReviewCommentPermission;
import com.google.gerrit.server.schema.GrantRevertPermission;
import com.google.inject.Inject;
import java.util.Arrays;
@@ -98,6 +99,7 @@
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private ExtensionRegistry extensionRegistry;
@Inject private GrantRevertPermission grantRevertPermission;
+ @Inject private GrantPostReviewCommentPermission grantPostReviewCommentPermission;
private Project.NameKey newProjectName;
@@ -209,6 +211,67 @@
}
@Test
+ public void grantPostReviewCommentPermission() throws Exception {
+ String ref = "refs/*";
+ String groupId = "global:Registered-Users";
+
+ grantPostReviewCommentPermission.execute(newProjectName);
+
+ ProjectAccessInfo info = pApi().access();
+ assertThat(info.local.containsKey(ref)).isTrue();
+ AccessSectionInfo accessSectionInfo = info.local.get(ref);
+ assertThat(accessSectionInfo.permissions.containsKey(Permission.POST_REVIEW_COMMENT)).isTrue();
+ PermissionInfo permissionInfo =
+ accessSectionInfo.permissions.get(Permission.POST_REVIEW_COMMENT);
+ assertThat(permissionInfo.rules.containsKey(groupId)).isTrue();
+ PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId);
+ assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW);
+ }
+
+ @Test
+ public void grantPostReviewCommentPermissionOnlyWorksOnce() throws Exception {
+ grantPostReviewCommentPermission.execute(newProjectName);
+ grantPostReviewCommentPermission.execute(newProjectName);
+
+ try (Repository repo = repoManager.openRepository(newProjectName)) {
+ MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
+ ProjectConfig projectConfig = projectConfigFactory.read(md);
+ AccessSection all = projectConfig.getAccessSection(AccessSection.ALL);
+
+ Permission permission = all.getPermission(Permission.POST_REVIEW_COMMENT);
+ assertThat(permission.getRules()).hasSize(1);
+ }
+ }
+
+ @Test
+ public void grantPostReviewCommentPermissionDoesntOverrideAdminsPreferences() throws Exception {
+ GroupReference otherGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS);
+
+ try (Repository repo = repoManager.openRepository(newProjectName)) {
+ MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
+ ProjectConfig projectConfig = projectConfigFactory.read(md);
+ projectConfig.upsertAccessSection(
+ AccessSection.ALL,
+ all -> {
+ grant(projectConfig, all, Permission.POST_REVIEW_COMMENT, otherGroup);
+ });
+ md.getCommitBuilder().setAuthor(admin.newIdent());
+ md.getCommitBuilder().setCommitter(admin.newIdent());
+ md.setMessage("Add Post Review Comment permission for anonymous users\n");
+
+ projectConfig.commit(md);
+ }
+ projectCache.evict(newProjectName);
+ ProjectAccessInfo expected = pApi().access();
+
+ grantPostReviewCommentPermission.execute(newProjectName);
+ projectCache.evict(newProjectName);
+ ProjectAccessInfo actual = pApi().access();
+ // Permissions don't change.
+ assertThat(actual.local).isEqualTo(expected.local);
+ }
+
+ @Test
public void getDefaultInheritance() throws Exception {
String inheritedName = pApi().access().inheritsFrom.name;
assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
diff --git a/modules/jgit b/modules/jgit
index d95b54f..0675211 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit d95b54f6080eb2aa9421e01188d25ab4a4a0c6bf
+Subproject commit 067521170e2a7308c6a1292a99900eadcee0b790
diff --git a/plugins/replication b/plugins/replication
index 9b78af8..87a975d 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 9b78af806be9cc7131a856f81051ee78d4f8c0e9
+Subproject commit 87a975dd6a62d383fc2d3914162365976123e1c2
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
index 5502a73..1b3c5c0 100644
--- a/polygerrit-ui/app/constants/reporting.ts
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -103,6 +103,8 @@
COPY_TO_CLIPBOARD = 'CopyToClipboard',
// Time to autocomplete a comment
COMMENT_COMPLETION = 'CommentCompletion',
+ // Time for AI chat requests to complete
+ AI_CHAT_REQUEST = 'AiChatRequest',
}
export enum Interaction {
@@ -184,6 +186,8 @@
FLOWS_TAB_RENDERED = 'flows-tab-rendered',
CREATE_FLOW_DIALOG_OPENED = 'create-flow-dialog-opened',
FLOW_CREATED = 'flow-created',
+ // AI Chat interaction request failures
+ AI_CHAT_FAILURE = 'ai-chat-failure',
}
/**
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 6f95970..26551c0 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -583,7 +583,7 @@
renderLink(link?: Link) {
// The expanded state renders all links in more detail. Hide in summary.
if (this.isExpanded) return;
- if (!link) return;
+ if (!link?.url?.trim()) return;
const tooltipText = link.tooltip ?? tooltipForLink(link.icon);
const icon = iconForLink(link.icon);
return html`<gr-tooltip-content
@@ -916,7 +916,7 @@
}
private renderLink(link?: Link, targetBlank = true) {
- if (!link) return;
+ if (!link?.url?.trim()) return;
const text = link.tooltip ?? tooltipForLink(link.icon);
const target = targetBlank ? '_blank' : undefined;
const icon = iconForLink(link.icon);
@@ -1407,7 +1407,7 @@
}
private renderLink(link?: Link) {
- if (!link) return;
+ if (!link?.url?.trim()) return;
const tooltipText = link.tooltip ?? tooltipForLink(link.icon);
const icon = iconForLink(link.icon);
return html`<gr-tooltip-content
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
index d9e0431..45940cc 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
@@ -16,6 +16,7 @@
import {
checkRun0,
checkRun1,
+ checkRun6,
setAllcheckRuns,
} from '../../test/test-data-generators';
import {resolve} from '../../models/dependency';
@@ -28,6 +29,7 @@
import {getAppContext} from '../../services/app-context';
import {suggestionsServiceToken} from '../../services/suggestions/suggestions-service';
import {testResolver} from '../../test/common-test-setup';
+import {Link} from '../../api/checks';
suite('gr-result-row test', () => {
let element: GrResultRow;
@@ -233,6 +235,12 @@
)
);
});
+
+ test('renderLink returns undefined when url is empty, whitespace, or missing', () => {
+ assert.isUndefined(element.renderLink({url: ''} as Link));
+ assert.isUndefined(element.renderLink({url: ' '} as Link));
+ assert.isUndefined(element.renderLink(undefined));
+ });
});
suite('gr-result-expanded test', () => {
@@ -378,6 +386,36 @@
`
);
});
+
+ test('renderLink returns undefined when url is empty, whitespace, or missing', async () => {
+ element.result = {...checkRun6, ...checkRun6.results![0]} as RunResult;
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <div class="header-content">
+ <div class="links"></div>
+ <div class="links">
+ <a
+ href="https://google.com"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ <gr-icon class="link" icon="download"> </gr-icon>
+ <span> Download </span>
+ </a>
+ </div>
+ <div class="links"></div>
+ </div>
+ <gr-endpoint-decorator name="check-result-expanded">
+ <gr-endpoint-param name="run"> </gr-endpoint-param>
+ <gr-endpoint-param name="result"> </gr-endpoint-param>
+ <gr-formatted-text class="message"> </gr-formatted-text>
+ </gr-endpoint-decorator>
+ `
+ );
+ });
});
suite('gr-checks-results test', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
index 7631ef0..6adc5c6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -8,7 +8,7 @@
import '../../shared/gr-tooltip-content/gr-tooltip-content';
import {DiffViewMode} from '../../../constants/constants';
import {customElement, property, state} from 'lit/decorators.js';
-import {fireIronAnnounce} from '../../../utils/event-util';
+import {fireAlert} from '../../../utils/event-util';
import {browserModelToken} from '../../../models/browser/browser-model';
import {resolve} from '../../../models/dependency';
import {css, html, LitElement} from 'lit';
@@ -120,7 +120,7 @@
announcement = 'Changed diff view to side by side';
}
if (announcement) {
- fireIronAnnounce(this, announcement);
+ fireAlert(this, announcement);
}
}
diff --git a/polygerrit-ui/app/models/chat/chat-model.ts b/polygerrit-ui/app/models/chat/chat-model.ts
index 619424b..2711edb 100644
--- a/polygerrit-ui/app/models/chat/chat-model.ts
+++ b/polygerrit-ui/app/models/chat/chat-model.ts
@@ -37,6 +37,8 @@
import {contextItemEquals} from './context-item-util';
import {FilesModel, NormalizedFileInfo} from '../change/files-model';
import {isMagicPath} from '../../utils/path-list-util';
+import {getAppContext} from '../../services/app-context';
+import {Interaction, Timing} from '../../constants/reporting';
/** The available display modes in the chat panel. */
export enum ChatPanelMode {
@@ -616,6 +618,19 @@
});
},
emitError: (errorMessage: string) => {
+ getAppContext().reportingService.timeEnd(Timing.AI_CHAT_REQUEST, {
+ modelName: request.model_name,
+ actionId: action.id,
+ error: errorMessage,
+ });
+ getAppContext().reportingService.reportInteraction(
+ Interaction.AI_CHAT_FAILURE,
+ {
+ modelName: request.model_name,
+ actionId: action.id,
+ error: errorMessage,
+ }
+ );
const state = this.getState();
if (state.id !== conversationId) return;
const turns: readonly Turn[] = state.turns;
@@ -630,6 +645,10 @@
});
},
done: () => {
+ getAppContext().reportingService.timeEnd(Timing.AI_CHAT_REQUEST, {
+ modelName: request.model_name,
+ actionId: action.id,
+ });
const state = this.getState();
if (state.id !== conversationId) return;
assert(turnIndex < state.turns.length, 'turn index out of bounds');
@@ -642,6 +661,7 @@
});
},
};
+ getAppContext().reportingService.time(Timing.AI_CHAT_REQUEST);
this.plugin?.chat?.(request, listener);
}
diff --git a/polygerrit-ui/app/models/chat/chat-model_test.ts b/polygerrit-ui/app/models/chat/chat-model_test.ts
index 9f70830..d244bd4 100644
--- a/polygerrit-ui/app/models/chat/chat-model_test.ts
+++ b/polygerrit-ui/app/models/chat/chat-model_test.ts
@@ -16,6 +16,8 @@
import sinon from 'sinon';
import {ParsedChangeInfo} from '../../types/types';
+import {getAppContext} from '../../services/app-context';
+import {Interaction, Timing} from '../../constants/reporting';
suite('chat-model tests', () => {
let model: ChatModel;
@@ -345,4 +347,99 @@
const state = model.getState();
assert.equal(state.turns[0].geminiMessage.regenerationIndex, 0);
});
+
+ suite('telemetry reporting', () => {
+ let timeStub: sinon.SinonStub;
+ let timeEndStub: sinon.SinonStub;
+ let reportInteractionStub: sinon.SinonStub;
+
+ setup(() => {
+ timeStub = sinon.stub(getAppContext().reportingService, 'time');
+ timeEndStub = sinon.stub(getAppContext().reportingService, 'timeEnd');
+ reportInteractionStub = sinon.stub(
+ getAppContext().reportingService,
+ 'reportInteraction'
+ );
+
+ // Set up a change, models, and actions
+ const models = {
+ models: [
+ {
+ model_id: 'test-model',
+ full_display_text: 'Test Model',
+ short_text: 'Test',
+ },
+ ],
+ default_model_id: 'test-model',
+ };
+ const actions = {
+ actions: [
+ {
+ id: 'test-action',
+ display_text: 'Test Action',
+ initial_user_prompt: 'Test Prompt',
+ },
+ ],
+ default_action_id: 'test-action',
+ };
+ (provider.getActions as sinon.SinonStub).resolves(actions);
+ (provider.getModels as sinon.SinonStub).resolves(models);
+
+ changeModel.updateStateChange(createParsedChange());
+ });
+
+ test('chat request starts a timer', async () => {
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ model.updateUserInput('hello');
+ model.chat('hello', 'test-action', 0);
+
+ assert.isTrue(timeStub.calledOnceWith(Timing.AI_CHAT_REQUEST));
+ });
+
+ test('chat request success stops the timer', async () => {
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ (provider.chat as sinon.SinonStub).callsFake((_, listener) => {
+ listener.done();
+ });
+
+ model.updateUserInput('hello');
+ model.chat('hello', 'test-action', 0);
+
+ assert.isTrue(
+ timeEndStub.calledOnceWith(Timing.AI_CHAT_REQUEST, {
+ modelName: 'test-model',
+ actionId: 'test-action',
+ })
+ );
+ });
+
+ test('chat request failure stops the timer and logs interaction', async () => {
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ (provider.chat as sinon.SinonStub).callsFake((_, listener) => {
+ listener.emitError('some error');
+ });
+
+ model.updateUserInput('hello');
+ model.chat('hello', 'test-action', 0);
+
+ assert.isTrue(
+ timeEndStub.calledOnceWith(Timing.AI_CHAT_REQUEST, {
+ modelName: 'test-model',
+ actionId: 'test-action',
+ error: 'some error',
+ })
+ );
+
+ assert.isTrue(
+ reportInteractionStub.calledOnceWith(Interaction.AI_CHAT_FAILURE, {
+ modelName: 'test-model',
+ actionId: 'test-action',
+ error: 'some error',
+ })
+ );
+ });
+ });
});
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index a2d8694..a22d0a9 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -1840,6 +1840,29 @@
attemptDetails: [],
};
+export const checkRun6: CheckRun = {
+ pluginName: 'f6',
+ internalRunId: 'f6',
+ checkName: 'FAKE Run with no link URL',
+ status: RunStatus.SCHEDULED,
+ isSingleAttempt: true,
+ isLatestAttempt: true,
+ attemptDetails: [],
+ results: [
+ {
+ internalResultId: 'f0r0',
+ category: Category.ERROR,
+ summary: 'I would like to point out this error: 1 is not equal to 2!',
+ links: [
+ {primary: true, url: '', icon: LinkIcon.EXTERNAL},
+ {primary: false, url: ' ', icon: LinkIcon.EXTERNAL},
+ {primary: true, url: 'https://google.com', icon: LinkIcon.DOWNLOAD},
+ ],
+ tags: [{name: 'OBSOLETE'}, {name: 'E2E'}],
+ },
+ ],
+};
+
export function setAllcheckRuns(model: ChecksModel) {
model.updateStateSetProvider('f0', ChecksPatchset.LATEST);
model.updateStateSetProvider('f1', ChecksPatchset.LATEST);
diff --git a/tools/deps.toml b/tools/deps.toml
index 7dfe305..3b4b7af 100644
--- a/tools/deps.toml
+++ b/tools/deps.toml
@@ -1,8 +1,8 @@
[versions]
antlr = "3.5.2"
autoValueGson = "1.3.1"
-bouncyCastle = "1.83"
-byteBuddy = "1.18.5"
+bouncyCastle = "1.84"
+byteBuddy = "1.18.8"
caffeine = "2.9.2"
commonmark = "0.24.0"
gitiles = "1.6.0"
@@ -86,7 +86,7 @@
jsr305 = { module = "com.google.code.findbugs:jsr305", version = "3.0.1" }
junit = { module = "junit:junit", version = "4.13.2" }
mime-util = { module = "eu.medsea.mimeutil:mime-util", version = "2.1.3" }
-mockito-core = { module = "org.mockito:mockito-core", version = "5.21.0" }
+mockito-core = { module = "org.mockito:mockito-core", version = "5.23.0" }
objenesis = { module = "org.objenesis:objenesis", version = "3.4" }
prolog-cafeteria = { module = "com.googlecode.prolog-cafe:prolog-cafeteria", version.ref = "prolog" }
prolog-compiler = { module = "com.googlecode.prolog-cafe:prolog-compiler", version.ref = "prolog" }
diff --git a/tools/gjf.sh b/tools/gjf.sh
index 78c34e6..b346032 100755
--- a/tools/gjf.sh
+++ b/tools/gjf.sh
@@ -168,8 +168,8 @@
"
-# Keep the default version in sync with dev-contributing.txt.
-DEFAULT_VERSION="1.24.0"
+# Keep the default version in sync with dev-crafting-changes.txt.
+DEFAULT_VERSION="1.35.0"
VERSION=${2:-$DEFAULT_VERSION}
verify_version $VERSION
diff --git a/tools/nongoogle.toml b/tools/nongoogle.toml
index ca16c61..6fb57d0 100644
--- a/tools/nongoogle.toml
+++ b/tools/nongoogle.toml
@@ -61,5 +61,5 @@
truth-java8-extension = { module = "com.google.truth.extensions:truth-java8-extension", version.ref = "truth" }
truth-liteproto-extension = { module = "com.google.truth.extensions:truth-liteproto-extension", version.ref = "truth" }
truth-proto-extension = { module = "com.google.truth.extensions:truth-proto-extension", version.ref = "truth" }
-tukaani-xz = { module = "org.tukaani:xz", version = "1.11" }
+tukaani-xz = { module = "org.tukaani:xz", version = "1.12" }
xerces = { module = "xerces:xercesImpl", version = "2.12.2" }