Merge changes I467d0158,I6c20a2bb,If052dc0e,Ia6a5b440 into stable-3.6
* changes:
sshd config: Add reference to the implementation sections upstream
Documentation: Supplement key exchange supported by Apache MINA
Documentation: Supplement 'encrypt-then-MAC' supported by Apache MINA
Documentation: Supplement ciphers supported by Apache MINA
diff --git a/.bazelproject b/.bazelproject
index a7f5450..ad7b022 100644
--- a/.bazelproject
+++ b/.bazelproject
@@ -16,7 +16,7 @@
targets:
//...:all
-java_language_level: 8
+java_language_level: 11
workspace_type: java
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index ce3a024..7f1a6e8 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -58,6 +58,9 @@
link:cmd-ban-commit.html[gerrit ban-commit]::
Bans a commit from a project's repository.
+link:cmd-copy-approvals.html[gerrit copy-approvals]::
+ Copy all inferred approvals labels to the latest patch-set.
+
link:cmd-create-branch.html[gerrit create-branch]::
Create a new project branch.
diff --git a/java/com/google/gerrit/asciidoctor/DocIndexer.java b/java/com/google/gerrit/asciidoctor/DocIndexer.java
index 513bdd7..6cbe2a9 100644
--- a/java/com/google/gerrit/asciidoctor/DocIndexer.java
+++ b/java/com/google/gerrit/asciidoctor/DocIndexer.java
@@ -107,11 +107,19 @@
String title;
try (BufferedReader titleReader = Files.newBufferedReader(file.toPath(), UTF_8)) {
- title = titleReader.readLine();
- if (title != null && title.startsWith("[[")) {
+ while ((title = titleReader.readLine()) != null) {
// Generally the first line of the txt is the title. In a few cases the
- // first line is a "[[tag]]" and the second line is the title.
- title = titleReader.readLine();
+ // first lines are "[[tag]]" and or ":attribute:" and the next line
+ // after those is the title.
+ if (title.startsWith("[[")) {
+ continue;
+ }
+ // Skip attributes such as :linkattrs:
+ if (title.startsWith(":") && title.endsWith(":")) {
+ continue;
+ }
+ // We found the title
+ break;
}
}
Matcher matcher = SECTION_HEADER.matcher(title);
diff --git a/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java b/java/com/google/gerrit/httpd/GuiceRequestScopePropagator.java
similarity index 80%
rename from java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
rename to java/com/google/gerrit/httpd/GuiceRequestScopePropagator.java
index 99dd8bf..7c8094a 100644
--- a/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
+++ b/java/com/google/gerrit/httpd/GuiceRequestScopePropagator.java
@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.util;
+package com.google.gerrit.httpd;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.util.RequestScopePropagator;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.Provider;
@@ -29,21 +31,25 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
+import javax.servlet.http.HttpServletRequest;
/** Propagator for Guice's built-in servlet scope. */
public class GuiceRequestScopePropagator extends RequestScopePropagator {
private final String url;
private final SocketAddress peer;
+ private final Provider<HttpServletRequest> request;
@Inject
GuiceRequestScopePropagator(
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
@RemotePeer Provider<SocketAddress> remotePeerProvider,
- ThreadLocalRequestContext local) {
+ ThreadLocalRequestContext local,
+ Provider<HttpServletRequest> request) {
super(ServletScopes.REQUEST, local);
this.url = urlProvider != null ? urlProvider.get() : null;
this.peer = remotePeerProvider.get();
+ this.request = request;
}
/** @see RequestScopePropagator#wrap(Callable) */
@@ -64,6 +70,11 @@
seedMap.put(Key.get(typeOfProvider(SocketAddress.class), RemotePeer.class), Providers.of(peer));
seedMap.put(Key.get(SocketAddress.class, RemotePeer.class), peer);
+ Key<?> webSessionAttrKey = Key.get(WebSession.class);
+ Object webSessionAttrValue = request.get().getAttribute(webSessionAttrKey.toString());
+ seedMap.put(webSessionAttrKey, webSessionAttrValue);
+ seedMap.put(Key.get(typeOfProvider(WebSession.class)), Providers.of(webSessionAttrValue));
+
return ServletScopes.continueRequest(callable, seedMap);
}
diff --git a/java/com/google/gerrit/httpd/WebModule.java b/java/com/google/gerrit/httpd/WebModule.java
index da485cc..79dde85 100644
--- a/java/com/google/gerrit/httpd/WebModule.java
+++ b/java/com/google/gerrit/httpd/WebModule.java
@@ -29,7 +29,6 @@
import com.google.gerrit.server.config.GerritRequestModule;
import com.google.gerrit.server.config.GitwebCgiConfig;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits.AsyncReceiveCommitsModule;
-import com.google.gerrit.server.util.GuiceRequestScopePropagator;
import com.google.gerrit.server.util.RequestScopePropagator;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
diff --git a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index dddc298..a03aa36 100644
--- a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -489,7 +489,7 @@
}
if (toc != null) {
- appendPageAsSection(scanner, toc, "Documentaion", md);
+ appendPageAsSection(scanner, toc, "Documentation", md);
} else {
appendEntriesSection(scanner, docs, "Documentation", md, prefix, 0);
appendEntriesSection(scanner, servlets, "Servlets", md, prefix, "servlet-".length());
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index 0d5e7b3..7684545 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -56,6 +56,7 @@
*/
public abstract class QueryProcessor<T> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final int MAX_LIMIT_BUFFER_MULTIPLIER = 100;
protected static class Metrics {
final Timer1<String> executionTime;
@@ -366,7 +367,7 @@
private int getEffectiveLimit(Predicate<T> p) {
if (isNoLimit == true) {
- return Integer.MAX_VALUE;
+ return getIndexSize() + MAX_LIMIT_BUFFER_MULTIPLIER * getBatchSize();
}
List<Integer> possibleLimits = new ArrayList<>(4);
possibleLimits.add(getBackendSupportedLimit());
@@ -394,4 +395,8 @@
}
protected abstract String formatForLogging(T t);
+
+ protected abstract int getIndexSize();
+
+ protected abstract int getBatchSize();
}
diff --git a/java/com/google/gerrit/server/account/AccountAttributeLoader.java b/java/com/google/gerrit/server/account/AccountAttributeLoader.java
new file mode 100644
index 0000000..ae57941
--- /dev/null
+++ b/java/com/google/gerrit/server/account/AccountAttributeLoader.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2022 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.account;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AccountAttributeLoader {
+
+ public interface Factory {
+ AccountAttributeLoader create();
+ }
+
+ private final InternalAccountDirectory directory;
+ private final Map<Account.Id, AccountAttribute> created = new HashMap<>();
+
+ @Inject
+ AccountAttributeLoader(InternalAccountDirectory directory) {
+ this.directory = directory;
+ }
+
+ @Nullable
+ public synchronized AccountAttribute get(@Nullable Account.Id id) {
+ if (id == null) {
+ return null;
+ }
+ return created.computeIfAbsent(id, k -> new AccountAttribute(k.get()));
+ }
+
+ public void fill() {
+ directory.fillAccountAttributeInfo(created.values());
+ }
+}
diff --git a/java/com/google/gerrit/server/account/AccountDirectory.java b/java/com/google/gerrit/server/account/AccountDirectory.java
index 98b2ca9..10aecd3 100644
--- a/java/com/google/gerrit/server/account/AccountDirectory.java
+++ b/java/com/google/gerrit/server/account/AccountDirectory.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.account;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.permissions.PermissionBackendException;
import java.util.Set;
@@ -24,7 +25,7 @@
* <p>Implementations supply data to Gerrit about user accounts.
*/
public abstract class AccountDirectory {
- /** Fields to be populated for a REST API response. */
+ /** Fields to be populated for SSH or REST API response. */
public enum FillOptions {
/** Full name or username. */
NAME,
@@ -59,4 +60,6 @@
public abstract void fillAccountInfo(Iterable<? extends AccountInfo> in, Set<FillOptions> options)
throws PermissionBackendException;
+
+ public abstract void fillAccountAttributeInfo(Iterable<? extends AccountAttribute> in);
}
diff --git a/java/com/google/gerrit/server/account/InternalAccountDirectory.java b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
index 130fa44..f7b0b60 100644
--- a/java/com/google/gerrit/server/account/InternalAccountDirectory.java
+++ b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.avatar.AvatarProvider;
+import com.google.gerrit.server.data.AccountAttribute;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -52,6 +53,9 @@
@Singleton
public class InternalAccountDirectory extends AccountDirectory {
static final Set<FillOptions> ID_ONLY = Collections.unmodifiableSet(EnumSet.of(FillOptions.ID));
+ static final Set<FillOptions> ALL_ACCOUNT_ATTRIBUTES =
+ Collections.unmodifiableSet(
+ EnumSet.of(FillOptions.NAME, FillOptions.EMAIL, FillOptions.USERNAME));
public static class InternalAccountDirectoryModule extends AbstractModule {
@Override
@@ -129,6 +133,44 @@
}
}
+ @Override
+ public void fillAccountAttributeInfo(Iterable<? extends AccountAttribute> in) {
+ Set<Account.Id> ids = stream(in).map(a -> Account.id(a.accountId)).collect(toSet());
+ Map<Account.Id, AccountState> accountStates = accountCache.get(ids);
+ for (AccountAttribute accountAttribute : in) {
+ Account.Id id = Account.id(accountAttribute.accountId);
+ AccountState accountState = accountStates.get(id);
+ if (accountState != null) {
+ fill(accountAttribute, accountState, ALL_ACCOUNT_ATTRIBUTES);
+ } else {
+ accountAttribute.accountId = null;
+ }
+ }
+ }
+
+ private void fill(
+ AccountAttribute accountAttribute, AccountState accountState, Set<FillOptions> options) {
+ Account account = accountState.account();
+ if (options.contains(FillOptions.NAME)) {
+ accountAttribute.name = Strings.emptyToNull(account.fullName());
+ if (accountAttribute.name == null) {
+ accountAttribute.name = accountState.userName().orElse(null);
+ }
+ }
+ if (options.contains(FillOptions.EMAIL)) {
+ accountAttribute.email = account.preferredEmail();
+ }
+ if (options.contains(FillOptions.USERNAME)) {
+ accountAttribute.username = accountState.userName().orElse(null);
+ }
+ if (options.contains(FillOptions.ID)) {
+ accountAttribute.accountId = account.id().get();
+ } else {
+ // Was previously set to look up account for filling.
+ accountAttribute.accountId = null;
+ }
+ }
+
private void fill(AccountInfo info, AccountState accountState, Set<FillOptions> options) {
Account account = accountState.account();
if (options.contains(FillOptions.ID)) {
diff --git a/java/com/google/gerrit/server/data/AccountAttribute.java b/java/com/google/gerrit/server/data/AccountAttribute.java
index 19605a2..9be221b 100644
--- a/java/com/google/gerrit/server/data/AccountAttribute.java
+++ b/java/com/google/gerrit/server/data/AccountAttribute.java
@@ -18,4 +18,11 @@
public String name;
public String email;
public String username;
+ public Integer accountId;
+
+ public AccountAttribute(Integer id) {
+ this.accountId = id;
+ }
+
+ public AccountAttribute() {}
}
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 2b402a6..95f6d96 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -36,6 +36,7 @@
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.AccountAttributeLoader;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Emails;
@@ -121,7 +122,7 @@
this.accountTemplateUtil = accountTemplateUtil;
}
- public ChangeAttribute asChangeAttribute(Change change) {
+ public ChangeAttribute asChangeAttribute(Change change, AccountAttributeLoader accountLoader) {
ChangeAttribute a = new ChangeAttribute();
a.project = change.getProject().get();
a.branch = change.getDest().shortName();
@@ -129,15 +130,9 @@
a.id = change.getKey().get();
a.number = change.getId().get();
a.subject = change.getSubject();
- try {
- a.commitMessage = changeDataFactory.create(change).commitMessage();
- } catch (Exception e) {
- logger.atSevere().withCause(e).log(
- "Error while getting full commit message for change %d", a.number);
- }
a.url = getChangeUrl(change);
- a.owner = asAccountAttribute(change.getOwner());
- a.assignee = asAccountAttribute(change.getAssignee());
+ a.owner = asAccountAttribute(change.getOwner(), accountLoader);
+ a.assignee = asAccountAttribute(change.getAssignee(), accountLoader);
a.status = change.getStatus();
a.createdOn = change.getCreatedOn().getEpochSecond();
a.wip = change.isWorkInProgress() ? true : null;
@@ -151,12 +146,9 @@
/** Create a {@link ChangeAttribute} instance from the specified change. */
public ChangeAttribute asChangeAttribute(Change change, ChangeNotes notes) {
- ChangeAttribute a = asChangeAttribute(change);
- Set<String> hashtags = notes.load().getHashtags();
- if (!hashtags.isEmpty()) {
- a.hashtags = new ArrayList<>(hashtags.size());
- a.hashtags.addAll(hashtags);
- }
+ ChangeAttribute a = asChangeAttribute(change, (AccountAttributeLoader) null);
+ addHashTags(a, notes);
+ addCommitMessage(a, notes);
return a;
}
/**
@@ -180,25 +172,27 @@
}
/** Add allReviewers to an existing {@link ChangeAttribute}. */
- public void addAllReviewers(ChangeAttribute a, ChangeNotes notes) {
+ public void addAllReviewers(
+ ChangeAttribute a, ChangeNotes notes, AccountAttributeLoader accountLoader) {
Collection<Account.Id> reviewers = approvalsUtil.getReviewers(notes).all();
if (!reviewers.isEmpty()) {
a.allReviewers = Lists.newArrayListWithCapacity(reviewers.size());
for (Account.Id id : reviewers) {
- a.allReviewers.add(asAccountAttribute(id));
+ a.allReviewers.add(asAccountAttribute(id, accountLoader));
}
}
}
/** Add submitRecords to an existing {@link ChangeAttribute}. */
- public void addSubmitRecords(ChangeAttribute ca, List<SubmitRecord> submitRecords) {
+ public void addSubmitRecords(
+ ChangeAttribute ca, List<SubmitRecord> submitRecords, AccountAttributeLoader accountLoader) {
ca.submitRecords = new ArrayList<>();
for (SubmitRecord submitRecord : submitRecords) {
SubmitRecordAttribute sa = new SubmitRecordAttribute();
sa.status = submitRecord.status.name();
if (submitRecord.status != SubmitRecord.Status.RULE_ERROR) {
- addSubmitRecordLabels(submitRecord, sa);
+ addSubmitRecordLabels(submitRecord, sa, accountLoader);
addSubmitRecordRequirements(submitRecord, sa);
}
ca.submitRecords.add(sa);
@@ -209,7 +203,8 @@
}
}
- private void addSubmitRecordLabels(SubmitRecord submitRecord, SubmitRecordAttribute sa) {
+ private void addSubmitRecordLabels(
+ SubmitRecord submitRecord, SubmitRecordAttribute sa, AccountAttributeLoader accountLoader) {
if (submitRecord.labels != null && !submitRecord.labels.isEmpty()) {
sa.labels = new ArrayList<>();
for (SubmitRecord.Label lbl : submitRecord.labels) {
@@ -217,7 +212,7 @@
la.label = lbl.label;
la.status = lbl.status.name();
if (lbl.appliedBy != null) {
- la.by = asAccountAttribute(lbl.appliedBy);
+ la.by = asAccountAttribute(lbl.appliedBy, accountLoader);
}
sa.labels.add(la);
}
@@ -352,13 +347,23 @@
a.commitMessage = commitMessage;
}
+ private void addCommitMessage(ChangeAttribute changeAttribute, ChangeNotes notes) {
+ try {
+ addCommitMessage(changeAttribute, changeDataFactory.create(notes).commitMessage());
+ } catch (Exception e) {
+ logger.atSevere().withCause(e).log(
+ "Error while getting full commit message for change %d", changeAttribute.number);
+ }
+ }
+
public void addPatchSets(
RevWalk revWalk,
ChangeAttribute ca,
Collection<PatchSet> ps,
Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
- LabelTypes labelTypes) {
- addPatchSets(revWalk, ca, ps, approvals, false, null, labelTypes);
+ LabelTypes labelTypes,
+ AccountAttributeLoader accountLoader) {
+ addPatchSets(revWalk, ca, ps, approvals, false, null, labelTypes, accountLoader);
}
public void addPatchSets(
@@ -368,13 +373,14 @@
Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
boolean includeFiles,
Change change,
- LabelTypes labelTypes) {
+ LabelTypes labelTypes,
+ AccountAttributeLoader accountLoader) {
if (!ps.isEmpty()) {
ca.patchSets = new ArrayList<>(ps.size());
for (PatchSet p : ps) {
- PatchSetAttribute psa = asPatchSetAttribute(revWalk, change, p);
+ PatchSetAttribute psa = asPatchSetAttribute(revWalk, change, p, accountLoader);
if (approvals != null) {
- addApprovals(psa, p.id(), approvals, labelTypes);
+ addApprovals(psa, p.id(), approvals, labelTypes, accountLoader);
}
ca.patchSets.add(psa);
if (includeFiles) {
@@ -385,13 +391,15 @@
}
public void addPatchSetComments(
- PatchSetAttribute patchSetAttribute, Collection<HumanComment> comments) {
+ PatchSetAttribute patchSetAttribute,
+ Collection<HumanComment> comments,
+ AccountAttributeLoader accountLoader) {
for (HumanComment comment : comments) {
if (comment.key.patchSetId == patchSetAttribute.number) {
if (patchSetAttribute.comments == null) {
patchSetAttribute.comments = new ArrayList<>();
}
- patchSetAttribute.comments.add(asPatchSetLineAttribute(comment));
+ patchSetAttribute.comments.add(asPatchSetLineAttribute(comment, accountLoader));
}
}
}
@@ -421,22 +429,30 @@
}
}
- public void addComments(ChangeAttribute ca, Collection<ChangeMessage> messages) {
+ public void addComments(
+ ChangeAttribute ca,
+ Collection<ChangeMessage> messages,
+ AccountAttributeLoader accountLoader) {
if (!messages.isEmpty()) {
ca.comments = new ArrayList<>();
for (ChangeMessage message : messages) {
- ca.comments.add(asMessageAttribute(message));
+ ca.comments.add(asMessageAttribute(message, accountLoader));
}
}
}
- /** Create a PatchSetAttribute for the given patchset suitable for serialization to JSON. */
public PatchSetAttribute asPatchSetAttribute(RevWalk revWalk, Change change, PatchSet patchSet) {
+ return asPatchSetAttribute(revWalk, change, patchSet, null);
+ }
+
+ /** Create a PatchSetAttribute for the given patchset suitable for serialization to JSON. */
+ public PatchSetAttribute asPatchSetAttribute(
+ RevWalk revWalk, Change change, PatchSet patchSet, AccountAttributeLoader accountLoader) {
PatchSetAttribute p = new PatchSetAttribute();
p.revision = patchSet.commitId().name();
p.number = patchSet.number();
p.ref = patchSet.refName();
- p.uploader = asAccountAttribute(patchSet.uploader());
+ p.uploader = asAccountAttribute(patchSet.uploader(), accountLoader);
p.createdOn = patchSet.createdOn().getEpochSecond();
PatchSet.Id pId = patchSet.id();
try {
@@ -453,7 +469,7 @@
p.author.name = author.getName();
p.author.username = "";
} else {
- p.author = asAccountAttribute(author.getAccount());
+ p.author = asAccountAttribute(author.getAccount(), accountLoader);
}
Map<String, FileDiffOutput> modifiedFiles =
@@ -476,20 +492,24 @@
PatchSetAttribute p,
PatchSet.Id id,
Map<PatchSet.Id, Collection<PatchSetApproval>> all,
- LabelTypes labelTypes) {
+ LabelTypes labelTypes,
+ AccountAttributeLoader accountLoader) {
Collection<PatchSetApproval> list = all.get(id);
if (list != null) {
- addApprovals(p, list, labelTypes);
+ addApprovals(p, list, labelTypes, accountLoader);
}
}
public void addApprovals(
- PatchSetAttribute p, Collection<PatchSetApproval> list, LabelTypes labelTypes) {
+ PatchSetAttribute p,
+ Collection<PatchSetApproval> list,
+ LabelTypes labelTypes,
+ AccountAttributeLoader accountLoader) {
if (!list.isEmpty()) {
p.approvals = new ArrayList<>(list.size());
for (PatchSetApproval a : list) {
if (a.value() != 0) {
- p.approvals.add(asApprovalAttribute(a, labelTypes));
+ p.approvals.add(asApprovalAttribute(a, labelTypes, accountLoader));
}
}
if (p.approvals.isEmpty()) {
@@ -498,6 +518,10 @@
}
}
+ public AccountAttribute asAccountAttribute(Account.Id id, AccountAttributeLoader accountLoader) {
+ return accountLoader != null ? accountLoader.get(id) : asAccountAttribute(id);
+ }
+
/** Create an AuthorAttribute for the given account suitable for serialization to JSON. */
public AccountAttribute asAccountAttribute(Account.Id id) {
if (id == null) {
@@ -529,11 +553,12 @@
* @param labelTypes label types for the containing project
* @return object suitable for serialization to JSON
*/
- public ApprovalAttribute asApprovalAttribute(PatchSetApproval approval, LabelTypes labelTypes) {
+ public ApprovalAttribute asApprovalAttribute(
+ PatchSetApproval approval, LabelTypes labelTypes, AccountAttributeLoader accountLoader) {
ApprovalAttribute a = new ApprovalAttribute();
a.type = approval.labelId().get();
a.value = Short.toString(approval.value());
- a.by = asAccountAttribute(approval.accountId());
+ a.by = asAccountAttribute(approval.accountId(), accountLoader);
a.grantedOn = approval.granted().getEpochSecond();
a.oldValue = null;
@@ -542,20 +567,22 @@
return a;
}
- public MessageAttribute asMessageAttribute(ChangeMessage message) {
+ public MessageAttribute asMessageAttribute(
+ ChangeMessage message, AccountAttributeLoader accountLoader) {
MessageAttribute a = new MessageAttribute();
a.timestamp = message.getWrittenOn().getEpochSecond();
a.reviewer =
message.getAuthor() != null
- ? asAccountAttribute(message.getAuthor())
+ ? asAccountAttribute(message.getAuthor(), accountLoader)
: asAccountAttribute(myIdent.get());
a.message = accountTemplateUtil.replaceTemplates(message.getMessage());
return a;
}
- public PatchSetCommentAttribute asPatchSetLineAttribute(HumanComment c) {
+ public PatchSetCommentAttribute asPatchSetLineAttribute(
+ HumanComment c, AccountAttributeLoader accountLoader) {
PatchSetCommentAttribute a = new PatchSetCommentAttribute();
- a.reviewer = asAccountAttribute(c.author.getId());
+ a.reviewer = asAccountAttribute(c.author.getId(), accountLoader);
a.file = c.key.filename;
a.line = c.lineNbr;
a.message = c.message;
@@ -569,4 +596,12 @@
}
return null;
}
+
+ private void addHashTags(ChangeAttribute changeAttribute, ChangeNotes notes) {
+ Set<String> hashtags = notes.load().getHashtags();
+ if (!hashtags.isEmpty()) {
+ changeAttribute.hashtags = new ArrayList<>(hashtags.size());
+ changeAttribute.hashtags.addAll(hashtags);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/git/DelegateRepository.java b/java/com/google/gerrit/server/git/DelegateRepository.java
index d839bce..9046d9d 100644
--- a/java/com/google/gerrit/server/git/DelegateRepository.java
+++ b/java/com/google/gerrit/server/git/DelegateRepository.java
@@ -65,7 +65,8 @@
this.delegate = delegate;
}
- Repository delegate() {
+ /** Returns the wrapped {@link Repository} instance. */
+ public Repository delegate() {
return delegate;
}
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 718eec2..dd5af2c 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -36,6 +36,7 @@
import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PublishCommentsOp;
+import com.google.gerrit.server.cache.PerThreadCache;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -385,7 +386,7 @@
() -> {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(oldName + "-for-" + currentThreadName);
- try {
+ try (PerThreadCache threadLocalCache = PerThreadCache.create()) {
return receiveCommits.processCommands(commands, monitor);
} finally {
Thread.currentThread().setName(oldName);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 889dfd6..c06bbbf 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -3486,7 +3486,7 @@
rw.markStart(newTip);
rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
- Map<Change.Key, ChangeNotes> byKey = null;
+ Map<Change.Key, ChangeData> changeDataByKey = null;
List<ReplaceRequest> replaceAndClose = new ArrayList<>();
int existingPatchSets = 0;
@@ -3522,8 +3522,8 @@
for (String changeId :
ChangeUtil.getChangeIdsFromFooter(c, urlFormatter.get())) {
- if (byKey == null) {
- byKey =
+ if (changeDataByKey == null) {
+ changeDataByKey =
retryHelper
.changeIndexQuery(
"queryOpenChangesByKeyByBranch",
@@ -3531,14 +3531,15 @@
.call();
}
- ChangeNotes onto = byKey.get(Change.key(changeId.trim()));
+ ChangeData onto = changeDataByKey.get(Change.key(changeId.trim()));
if (onto != null) {
newPatchSets++;
// Hold onto this until we're done with the walk, as the call to
// req.validate below calls isMergedInto which resets the walk.
+ ChangeNotes ontoNotes = onto.notes();
ReplaceRequest req =
- new ReplaceRequest(onto.getChangeId(), c, cmd, false);
- req.notes = onto;
+ new ReplaceRequest(ontoNotes.getChangeId(), c, cmd, false);
+ req.notes = ontoNotes;
replaceAndClose.add(req);
continue COMMIT;
}
@@ -3611,14 +3612,17 @@
}
}
- private Map<Change.Key, ChangeNotes> openChangesByKeyByBranch(
+ private Map<Change.Key, ChangeData> openChangesByKeyByBranch(
InternalChangeQuery internalChangeQuery, BranchNameKey branch) {
try (TraceTimer traceTimer =
newTimer("openChangesByKeyByBranch", Metadata.builder().branchName(branch.branch()))) {
- Map<Change.Key, ChangeNotes> r = new HashMap<>();
+ Map<Change.Key, ChangeData> r = new HashMap<>();
for (ChangeData cd : internalChangeQuery.byBranchOpen(branch)) {
try {
- r.put(cd.change().getKey(), cd.notes());
+ // ChangeData is not materialised into a ChangeNotes for avoiding
+ // to load a potentially large number of changes meta-data into memory
+ // which would cause unnecessary disk I/O, CPU and heap utilisation.
+ r.put(cd.change().getKey(), cd);
} catch (NoSuchChangeException e) {
// Ignore deleted change
}
diff --git a/java/com/google/gerrit/server/git/validators/AccountValidator.java b/java/com/google/gerrit/server/git/validators/AccountValidator.java
index 873f421..b38f405 100644
--- a/java/com/google/gerrit/server/git/validators/AccountValidator.java
+++ b/java/com/google/gerrit/server/git/validators/AccountValidator.java
@@ -91,7 +91,7 @@
return ImmutableList.of(String.format("account '%s' does not exist", accountId.get()));
}
- if (accountId.equals(self.get().getAccountId()) && !newAccount.get().isActive()) {
+ if (!newAccount.get().isActive() && accountId.equals(self.get().getAccountId())) {
messages.add("cannot deactivate own account");
}
diff --git a/java/com/google/gerrit/server/notedb/RepoSequence.java b/java/com/google/gerrit/server/notedb/RepoSequence.java
index 47e12ff..d743921 100644
--- a/java/com/google/gerrit/server/notedb/RepoSequence.java
+++ b/java/com/google/gerrit/server/notedb/RepoSequence.java
@@ -340,4 +340,16 @@
counterLock.unlock();
}
}
+
+ /**
+ * Retrieves the last returned sequence number.
+ *
+ * <p>Explicitly calls {@link #next()} if this instance didn't return sequence number until now.
+ */
+ public int last() {
+ if (counter == 0) {
+ next();
+ }
+ return counter - 1;
+ }
}
diff --git a/java/com/google/gerrit/server/notedb/Sequences.java b/java/com/google/gerrit/server/notedb/Sequences.java
index 7ae98778..b42253e 100644
--- a/java/com/google/gerrit/server/notedb/Sequences.java
+++ b/java/com/google/gerrit/server/notedb/Sequences.java
@@ -55,6 +55,9 @@
private final RepoSequence changeSeq;
private final RepoSequence groupSeq;
private final Timer2<SequenceType, Boolean> nextIdLatency;
+ private final int accountBatchSize;
+ private final int changeBatchSize;
+ private final int groupBatchSize = 1;
@Inject
public Sequences(
@@ -65,7 +68,7 @@
AllUsersName allUsers,
MetricMaker metrics) {
- int accountBatchSize =
+ accountBatchSize =
cfg.getInt(
SECTION_NOTEDB,
NAME_ACCOUNTS,
@@ -80,7 +83,7 @@
() -> FIRST_ACCOUNT_ID,
accountBatchSize);
- int changeBatchSize =
+ changeBatchSize =
cfg.getInt(
SECTION_NOTEDB,
NAME_CHANGES,
@@ -95,7 +98,6 @@
() -> FIRST_CHANGE_ID,
changeBatchSize);
- int groupBatchSize = 1;
groupSeq =
new RepoSequence(
repoManager,
@@ -147,6 +149,18 @@
}
}
+ public int changeBatchSize() {
+ return changeBatchSize;
+ }
+
+ public int groupBatchSize() {
+ return groupBatchSize;
+ }
+
+ public int accountBatchSize() {
+ return accountBatchSize;
+ }
+
public int currentChangeId() {
return changeSeq.current();
}
@@ -159,6 +173,18 @@
return groupSeq.current();
}
+ public int lastChangeId() {
+ return changeSeq.last();
+ }
+
+ public int lastGroupId() {
+ return groupSeq.last();
+ }
+
+ public int lastAccountId() {
+ return accountSeq.last();
+ }
+
public void setChangeIdValue(int value) {
changeSeq.storeNew(value);
}
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
index 2e29bbd..e380ef1 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gerrit.server.index.account.AccountIndexRewriter;
import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
+import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -41,6 +42,7 @@
*/
public class AccountQueryProcessor extends QueryProcessor<AccountState> {
private final AccountControl.Factory accountControlFactory;
+ private final Sequences sequences;
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
@@ -57,7 +59,8 @@
IndexConfig indexConfig,
AccountIndexCollection indexes,
AccountIndexRewriter rewriter,
- AccountControl.Factory accountControlFactory) {
+ AccountControl.Factory accountControlFactory,
+ Sequences sequences) {
super(
metricMaker,
AccountSchemaDefinitions.INSTANCE,
@@ -67,6 +70,7 @@
FIELD_LIMIT,
() -> limitsFactory.create(userProvider.get()).getQueryLimit());
this.accountControlFactory = accountControlFactory;
+ this.sequences = sequences;
}
@Override
@@ -79,4 +83,14 @@
protected String formatForLogging(AccountState accountState) {
return accountState.account().id().toString();
}
+
+ @Override
+ protected int getIndexSize() {
+ return sequences.lastAccountId();
+ }
+
+ @Override
+ protected int getBatchSize() {
+ return sequences.accountBatchSize();
+ }
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index ed1f2f1..7d02ecd 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -39,6 +39,7 @@
import com.google.gerrit.server.index.change.ChangeIndexRewriter;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.index.change.IndexedChangeQuery;
+import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
@@ -61,6 +62,7 @@
private final Map<String, DynamicBean> dynamicBeans = new HashMap<>();
private final List<Extension<ChangePluginDefinedInfoFactory>>
changePluginDefinedInfoFactoriesByPlugin = new ArrayList<>();
+ private final Sequences sequences;
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
@@ -77,6 +79,7 @@
IndexConfig indexConfig,
ChangeIndexCollection indexes,
ChangeIndexRewriter rewriter,
+ Sequences sequences,
ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory,
DynamicSet<ChangePluginDefinedInfoFactory> changePluginDefinedInfoFactories) {
super(
@@ -89,6 +92,7 @@
() -> limitsFactory.create(userProvider.get()).getQueryLimit());
this.userProvider = userProvider;
this.changeIsVisibleToPredicateFactory = changeIsVisibleToPredicateFactory;
+ this.sequences = sequences;
changePluginDefinedInfoFactories
.entries()
@@ -138,4 +142,14 @@
protected String formatForLogging(ChangeData changeData) {
return changeData.getId().toString();
}
+
+ @Override
+ protected int getIndexSize() {
+ return sequences.lastChangeId();
+ }
+
+ @Override
+ protected int getBatchSize() {
+ return sequences.changeBatchSize();
+ }
}
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index 1b6dc62..716cf10 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -18,6 +18,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.LabelTypes;
@@ -28,6 +29,8 @@
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryResult;
import com.google.gerrit.server.DynamicOptions;
+import com.google.gerrit.server.account.AccountAttributeLoader;
+import com.google.gerrit.server.cache.PerThreadCache;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.data.ChangeAttribute;
import com.google.gerrit.server.data.PatchSetAttribute;
@@ -86,6 +89,7 @@
private final EventFactory eventFactory;
private final TrackingFooters trackingFooters;
private final SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
+ private final AccountAttributeLoader.Factory accountAttributeLoaderFactory;
private OutputFormat outputFormat = OutputFormat.TEXT;
private boolean includePatchSets;
@@ -110,13 +114,15 @@
ChangeQueryProcessor queryProcessor,
EventFactory eventFactory,
TrackingFooters trackingFooters,
- SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory) {
+ SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory,
+ AccountAttributeLoader.Factory accountAttributeLoaderFactory) {
this.repoManager = repoManager;
this.queryBuilder = queryBuilder;
this.queryProcessor = queryProcessor;
this.eventFactory = eventFactory;
this.trackingFooters = trackingFooters;
this.submitRuleEvaluatorFactory = submitRuleEvaluatorFactory;
+ this.accountAttributeLoaderFactory = accountAttributeLoaderFactory;
}
void setLimit(int n) {
@@ -205,7 +211,7 @@
return;
}
- try {
+ try (PerThreadCache ignored = PerThreadCache.create()) {
final QueryStatsAttribute stats = new QueryStatsAttribute();
stats.runTimeMilliseconds = TimeUtil.nowMs();
@@ -214,9 +220,13 @@
QueryResult<ChangeData> results = queryProcessor.query(queryBuilder.parse(queryString));
pluginInfosByChange = queryProcessor.createPluginDefinedInfos(results.entities());
try {
+ AccountAttributeLoader accountLoader = accountAttributeLoaderFactory.create();
+ List<ChangeAttribute> changeAttributes = new ArrayList<>();
for (ChangeData d : results.entities()) {
- show(buildChangeAttribute(d, repos, revWalks));
+ changeAttributes.add(buildChangeAttribute(d, repos, revWalks, accountLoader));
}
+ accountLoader.fill();
+ changeAttributes.forEach(c -> show(c));
} finally {
closeAll(revWalks.values(), repos.values());
}
@@ -247,10 +257,14 @@
}
private ChangeAttribute buildChangeAttribute(
- ChangeData d, Map<Project.NameKey, Repository> repos, Map<Project.NameKey, RevWalk> revWalks)
+ ChangeData d,
+ Map<Project.NameKey, Repository> repos,
+ Map<Project.NameKey, RevWalk> revWalks,
+ AccountAttributeLoader accountLoader)
throws IOException {
LabelTypes labelTypes = d.getLabelTypes();
- ChangeAttribute c = eventFactory.asChangeAttribute(d.change(), d.notes());
+ ChangeAttribute c = eventFactory.asChangeAttribute(d.change(), accountLoader);
+ c.hashtags = Lists.newArrayList(d.hashtags());
eventFactory.extend(c, d.change());
if (!trackingFooters.isEmpty()) {
@@ -258,13 +272,14 @@
}
if (includeAllReviewers) {
- eventFactory.addAllReviewers(c, d.notes());
+ eventFactory.addAllReviewers(c, d.notes(), accountLoader);
}
if (includeSubmitRecords) {
SubmitRuleOptions options =
SubmitRuleOptions.builder().recomputeOnClosedChanges(true).build();
- eventFactory.addSubmitRecords(c, submitRuleEvaluatorFactory.create(options).evaluate(d));
+ eventFactory.addSubmitRecords(
+ c, submitRuleEvaluatorFactory.create(options).evaluate(d), accountLoader);
}
if (includeCommitMessage) {
@@ -292,26 +307,28 @@
includeApprovals ? d.approvals().asMap() : null,
includeFiles,
d.change(),
- labelTypes);
+ labelTypes,
+ accountLoader);
}
if (includeCurrentPatchSet) {
PatchSet current = d.currentPatchSet();
if (current != null) {
c.currentPatchSet = eventFactory.asPatchSetAttribute(rw, d.change(), current);
- eventFactory.addApprovals(c.currentPatchSet, d.currentApprovals(), labelTypes);
+ eventFactory.addApprovals(
+ c.currentPatchSet, d.currentApprovals(), labelTypes, accountLoader);
if (includeFiles) {
eventFactory.addPatchSetFileNames(c.currentPatchSet, d.change(), d.currentPatchSet());
}
if (includeComments) {
- eventFactory.addPatchSetComments(c.currentPatchSet, d.publishedComments());
+ eventFactory.addPatchSetComments(c.currentPatchSet, d.publishedComments(), accountLoader);
}
}
}
if (includeComments) {
- eventFactory.addComments(c, d.messages());
+ eventFactory.addComments(c, d.messages(), accountLoader);
if (includePatchSets) {
eventFactory.addPatchSets(
rw,
@@ -320,9 +337,10 @@
includeApprovals ? d.approvals().asMap() : null,
includeFiles,
d.change(),
- labelTypes);
+ labelTypes,
+ accountLoader);
for (PatchSetAttribute attribute : c.patchSets) {
- eventFactory.addPatchSetComments(attribute, d.publishedComments());
+ eventFactory.addPatchSetComments(attribute, d.publishedComments(), accountLoader);
}
}
}
diff --git a/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java b/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
index 9e56807..e5fb036 100644
--- a/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.index.group.GroupIndexCollection;
import com.google.gerrit.server.index.group.GroupIndexRewriter;
import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
+import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -42,6 +43,7 @@
public class GroupQueryProcessor extends QueryProcessor<InternalGroup> {
private final Provider<CurrentUser> userProvider;
private final GroupControl.GenericFactory groupControlFactory;
+ private final Sequences sequences;
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
@@ -58,7 +60,8 @@
IndexConfig indexConfig,
GroupIndexCollection indexes,
GroupIndexRewriter rewriter,
- GroupControl.GenericFactory groupControlFactory) {
+ GroupControl.GenericFactory groupControlFactory,
+ Sequences sequences) {
super(
metricMaker,
GroupSchemaDefinitions.INSTANCE,
@@ -69,6 +72,7 @@
() -> limitsFactory.create(userProvider.get()).getQueryLimit());
this.userProvider = userProvider;
this.groupControlFactory = groupControlFactory;
+ this.sequences = sequences;
}
@Override
@@ -81,4 +85,14 @@
protected String formatForLogging(InternalGroup internalGroup) {
return internalGroup.getGroupUUID().get();
}
+
+ @Override
+ protected int getIndexSize() {
+ return sequences.lastGroupId();
+ }
+
+ @Override
+ protected int getBatchSize() {
+ return sequences.groupBatchSize();
+ }
}
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java b/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
index 8e6d8a1..5465d6d 100644
--- a/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountLimits;
import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -44,6 +45,7 @@
public class ProjectQueryProcessor extends QueryProcessor<ProjectData> {
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> userProvider;
+ private final ProjectCache projectCache;
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
@@ -60,7 +62,8 @@
IndexConfig indexConfig,
ProjectIndexCollection indexes,
ProjectIndexRewriter rewriter,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ ProjectCache projectCache) {
super(
metricMaker,
ProjectSchemaDefinitions.INSTANCE,
@@ -71,6 +74,7 @@
() -> limitsFactory.create(userProvider.get()).getQueryLimit());
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
+ this.projectCache = projectCache;
}
@Override
@@ -83,4 +87,14 @@
protected String formatForLogging(ProjectData projectData) {
return projectData.getProject().getName();
}
+
+ @Override
+ protected int getIndexSize() {
+ return projectCache.all().size();
+ }
+
+ @Override
+ protected int getBatchSize() {
+ return 1;
+ }
}
diff --git a/java/com/google/gerrit/sshd/CommandModule.java b/java/com/google/gerrit/sshd/CommandModule.java
index ac07056..4242c71 100644
--- a/java/com/google/gerrit/sshd/CommandModule.java
+++ b/java/com/google/gerrit/sshd/CommandModule.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.account.AccountAttributeLoader;
import com.google.inject.binder.LinkedBindingBuilder;
import org.apache.sshd.server.command.Command;
@@ -29,6 +30,7 @@
* @return a binding that must be bound to a non-singleton provider for a {@link Command} object.
*/
protected LinkedBindingBuilder<Command> command(String name) {
+ factory(AccountAttributeLoader.Factory.class);
return bind(Commands.key(name));
}
diff --git a/java/com/google/gerrit/sshd/SshScope.java b/java/com/google/gerrit/sshd/SshScope.java
index 0fe8b78..f19c395 100644
--- a/java/com/google/gerrit/sshd/SshScope.java
+++ b/java/com/google/gerrit/sshd/SshScope.java
@@ -53,6 +53,8 @@
private volatile long startedMemory;
private volatile long finishedMemory;
+ private IdentifiedUser identifiedUser;
+
private Context(SshSession s, String c, long at) {
session = s;
commandLine = c;
@@ -125,8 +127,10 @@
public CurrentUser getUser() {
CurrentUser user = session.getUser();
if (user != null && user.isIdentifiedUser()) {
- IdentifiedUser identifiedUser = userFactory.create(user.getAccountId());
- identifiedUser.setAccessPath(user.getAccessPath());
+ if (identifiedUser == null) {
+ identifiedUser = userFactory.create(user.getAccountId());
+ identifiedUser.setAccessPath(user.getAccessPath());
+ }
return identifiedUser;
}
return user;
diff --git a/javatests/com/google/gerrit/integration/git/BUILD b/javatests/com/google/gerrit/integration/git/BUILD
index 28755af..86b3f36 100644
--- a/javatests/com/google/gerrit/integration/git/BUILD
+++ b/javatests/com/google/gerrit/integration/git/BUILD
@@ -1,13 +1,32 @@
load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+# TODO(davido): This was only needed as own rule, to provide a dedicated
+# tag to skip Git version v2 protocol tests. That was particularly
+# needed for RBE, because this test assumes that git client version is
+# at least 2.17.1. Once Bazel docker image for Ubuntu 20.04 is available
+# and we removed our own RBE docker image, we can merge this rule with
+# the other rules in this package.
acceptance_tests(
srcs = ["GitProtocolV2IT.java"],
group = "protocol-v2",
labels = ["git-protocol-v2"],
)
+# This rule can be also merged with the other tests in this package.
acceptance_tests(
srcs = ["UploadArchiveIT.java"],
group = "upload-archive",
labels = ["git-upload-archive"],
)
+
+acceptance_tests(
+ srcs = glob(
+ ["*.java"],
+ exclude = [
+ "GitProtocolV2IT.java",
+ "UploadArchiveIT.java",
+ ],
+ ),
+ group = "git_tests",
+ labels = ["git"],
+)
diff --git a/javatests/com/google/gerrit/integration/git/PushToRefsUsersIT.java b/javatests/com/google/gerrit/integration/git/PushToRefsUsersIT.java
new file mode 100644
index 0000000..c42f00d
--- /dev/null
+++ b/javatests/com/google/gerrit/integration/git/PushToRefsUsersIT.java
@@ -0,0 +1,111 @@
+// Copyright (C) 2022 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.integration.git;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.StandaloneSiteTest;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+// TODO(davido): In addition to push over HTTP also add a test for push over SSH
+public class PushToRefsUsersIT extends StandaloneSiteTest {
+ private static final String ADMIN_PASSWORD = "secret";
+ private final String[] GIT_CLONE = new String[] {"git", "clone"};
+ private final String[] GIT_FETCH_USERS_SELF =
+ new String[] {"git", "fetch", "origin", "refs/users/self"};
+ private final String[] GIT_CO_FETCH_HEAD = new String[] {"git", "checkout", "FETCH_HEAD"};
+ private final String[] GIT_CONFIG_USER_EMAIL =
+ new String[] {"git", "config", "user.email", "admin@example.com"};
+ private final String[] GIT_CONFIG_USER_NAME =
+ new String[] {"git", "config", "user.name", "Administrator"};
+ private final String[] GIT_COMMIT = new String[] {"git", "commit", "-am", "OOO"};
+ private final String[] GIT_PUSH_USERS_SELF =
+ new String[] {"git", "push", "origin", "HEAD:refs/users/self"};
+
+ @Inject private GerritApi gApi;
+ @Inject private @GerritServerConfig Config config;
+ @Inject private AllUsersName allUsersName;
+
+ @Test
+ public void testPushToRefsUsersOverHttp() throws Exception {
+ try (ServerContext ctx = startServer()) {
+ ctx.getInjector().injectMembers(this);
+
+ // Setup admin password
+ gApi.accounts().id(admin.username()).setHttpPassword(ADMIN_PASSWORD);
+
+ // Get authenticated Git/HTTP URL
+ String urlWithCredentials =
+ config
+ .getString("gerrit", null, "canonicalweburl")
+ .replace("http://", "http://" + admin.username() + ":" + ADMIN_PASSWORD + "@");
+
+ // Clone All-Users repository
+ execute(
+ ImmutableList.<String>builder()
+ .add(GIT_CLONE)
+ .add(urlWithCredentials + "/a/" + allUsersName)
+ .add(sitePaths.data_dir.toFile().getAbsolutePath())
+ .build(),
+ sitePaths.site_path);
+
+ // Fetch refs/users/self for admin user
+ execute(GIT_FETCH_USERS_SELF);
+
+ // Checkout FETCH_HEAD
+ execute(GIT_CO_FETCH_HEAD);
+
+ // Set admin user status to OOO
+ Files.write(
+ sitePaths.data_dir.resolve("account.config"),
+ " status = OOO".getBytes(UTF_8),
+ StandardOpenOption.APPEND);
+
+ // Set user email
+ execute(GIT_CONFIG_USER_EMAIL);
+
+ // Set user name
+ execute(GIT_CONFIG_USER_NAME);
+
+ // Commit
+ execute(GIT_COMMIT);
+
+ // Push
+ assertThat(execute(GIT_PUSH_USERS_SELF)).contains("Processing changes: refs: 1, done");
+
+ // Verify user status
+ assertThat(gApi.accounts().id(admin.id().get()).detail().status).isEqualTo("OOO");
+ }
+ }
+
+ private String execute(String... cmds) throws Exception {
+ return execute(ImmutableList.<String>builder().add(cmds).build(), sitePaths.data_dir);
+ }
+
+ private String execute(ImmutableList<String> cmd, Path path) throws Exception {
+ return execute(cmd, path.toFile(), ImmutableMap.of());
+ }
+}
diff --git a/javatests/com/google/gerrit/server/git/delegate/DelegateRepositoryTest.java b/javatests/com/google/gerrit/server/git/delegate/DelegateRepositoryTest.java
new file mode 100644
index 0000000..74e0fac
--- /dev/null
+++ b/javatests/com/google/gerrit/server/git/delegate/DelegateRepositoryTest.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 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.git.delegate;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.git.DelegateRepository;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import java.io.IOException;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Test;
+
+public class DelegateRepositoryTest {
+
+ @Test
+ public void shouldDelegateRepositoryFromAnyPackage() throws IOException {
+ Repository foo = new InMemoryRepositoryManager().createRepository(Project.nameKey("foo"));
+ try (TestDelegateRepository delegateRepository = new TestDelegateRepository(foo)) {
+ assertThat(delegateRepository.delegate()).isSameInstanceAs(foo);
+ }
+ }
+
+ static class TestDelegateRepository extends DelegateRepository {
+ protected TestDelegateRepository(Repository delegate) {
+ super(delegate);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java b/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java
index a768eaf..0c9f731 100644
--- a/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java
+++ b/javatests/com/google/gerrit/server/notedb/RepoSequenceTest.java
@@ -91,27 +91,37 @@
assertThat(s.acquireCount).isEqualTo(0);
assertThat(s.next()).isEqualTo(1);
+ assertThat(s.last()).isEqualTo(1);
assertThat(s.acquireCount).isEqualTo(1);
assertThat(s.next()).isEqualTo(2);
+ assertThat(s.last()).isEqualTo(2);
assertThat(s.acquireCount).isEqualTo(1);
assertThat(s.next()).isEqualTo(3);
+ assertThat(s.last()).isEqualTo(3);
assertThat(s.acquireCount).isEqualTo(1);
assertThat(s.next()).isEqualTo(4);
+ assertThat(s.last()).isEqualTo(4);
assertThat(s.acquireCount).isEqualTo(2);
assertThat(s.next()).isEqualTo(5);
+ assertThat(s.last()).isEqualTo(5);
assertThat(s.acquireCount).isEqualTo(2);
assertThat(s.next()).isEqualTo(6);
+ assertThat(s.last()).isEqualTo(6);
assertThat(s.acquireCount).isEqualTo(2);
assertThat(s.next()).isEqualTo(7);
+ assertThat(s.last()).isEqualTo(7);
assertThat(s.acquireCount).isEqualTo(3);
assertThat(s.next()).isEqualTo(8);
+ assertThat(s.last()).isEqualTo(8);
assertThat(s.acquireCount).isEqualTo(3);
assertThat(s.next()).isEqualTo(9);
+ assertThat(s.last()).isEqualTo(9);
assertThat(s.acquireCount).isEqualTo(3);
assertThat(s.next()).isEqualTo(10);
+ assertThat(s.last()).isEqualTo(10);
assertThat(s.acquireCount).isEqualTo(4);
}
@@ -127,6 +137,8 @@
assertThat(s2.next()).isEqualTo(5);
assertThat(s1.next()).isEqualTo(3);
assertThat(s2.next()).isEqualTo(6);
+ assertThat(s1.last()).isEqualTo(3);
+ assertThat(s2.last()).isEqualTo(6);
// s2 acquires 7-9; s1 acquires 10-12.
assertThat(s2.next()).isEqualTo(7);
@@ -135,6 +147,8 @@
assertThat(s1.next()).isEqualTo(11);
assertThat(s2.next()).isEqualTo(9);
assertThat(s1.next()).isEqualTo(12);
+ assertThat(s1.last()).isEqualTo(12);
+ assertThat(s2.last()).isEqualTo(9);
}
@Test
@@ -284,48 +298,61 @@
}
@Test
- public void nextWithCountOneCaller() throws Exception {
+ public void nextWithCountAndLastByOneCaller() throws Exception {
RepoSequence s = newSequence("id", 1, 3);
assertThat(s.next(2)).containsExactly(1, 2).inOrder();
assertThat(s.acquireCount).isEqualTo(1);
+ assertThat(s.last()).isEqualTo(2);
assertThat(s.next(2)).containsExactly(3, 4).inOrder();
assertThat(s.acquireCount).isEqualTo(2);
+ assertThat(s.last()).isEqualTo(4);
assertThat(s.next(2)).containsExactly(5, 6).inOrder();
assertThat(s.acquireCount).isEqualTo(2);
+ assertThat(s.last()).isEqualTo(6);
assertThat(s.next(3)).containsExactly(7, 8, 9).inOrder();
assertThat(s.acquireCount).isEqualTo(3);
+ assertThat(s.last()).isEqualTo(9);
assertThat(s.next(3)).containsExactly(10, 11, 12).inOrder();
assertThat(s.acquireCount).isEqualTo(4);
+ assertThat(s.last()).isEqualTo(12);
assertThat(s.next(3)).containsExactly(13, 14, 15).inOrder();
assertThat(s.acquireCount).isEqualTo(5);
+ assertThat(s.last()).isEqualTo(15);
assertThat(s.next(7)).containsExactly(16, 17, 18, 19, 20, 21, 22).inOrder();
assertThat(s.acquireCount).isEqualTo(6);
+ assertThat(s.last()).isEqualTo(22);
assertThat(s.next(7)).containsExactly(23, 24, 25, 26, 27, 28, 29).inOrder();
assertThat(s.acquireCount).isEqualTo(7);
+ assertThat(s.last()).isEqualTo(29);
assertThat(s.next(7)).containsExactly(30, 31, 32, 33, 34, 35, 36).inOrder();
assertThat(s.acquireCount).isEqualTo(8);
+ assertThat(s.last()).isEqualTo(36);
}
@Test
- public void nextWithCountMultipleCallers() throws Exception {
+ public void nextWithCountAndLastByMultipleCallers() throws Exception {
RepoSequence s1 = newSequence("id", 1, 3);
RepoSequence s2 = newSequence("id", 1, 4);
assertThat(s1.next(2)).containsExactly(1, 2).inOrder();
+ assertThat(s1.last()).isEqualTo(2);
assertThat(s1.acquireCount).isEqualTo(1);
// s1 hasn't exhausted its last batch.
assertThat(s2.next(2)).containsExactly(4, 5).inOrder();
+ assertThat(s2.last()).isEqualTo(5);
assertThat(s2.acquireCount).isEqualTo(1);
// s1 acquires again to cover this request, plus a whole new batch.
assertThat(s1.next(3)).containsExactly(3, 8, 9);
+ assertThat(s1.last()).isEqualTo(9);
assertThat(s1.acquireCount).isEqualTo(2);
// s2 hasn't exhausted its last batch, do so now.
assertThat(s2.next(2)).containsExactly(6, 7);
+ assertThat(s2.last()).isEqualTo(7);
assertThat(s2.acquireCount).isEqualTo(1);
}
diff --git a/polygerrit-ui/app/api/BUILD_for_publishing_api_only b/polygerrit-ui/app/api/BUILD_for_publishing_api_only
index 9d3029b..67a26cd 100644
--- a/polygerrit-ui/app/api/BUILD_for_publishing_api_only
+++ b/polygerrit-ui/app/api/BUILD_for_publishing_api_only
@@ -38,6 +38,7 @@
# Use this rule for publishing the js plugin api as a package to the npm repo.
pkg_npm(
name = "js_plugin_api_npm_package",
+ package_name = "@gerritcodereview/typescript-api",
srcs = glob(
["**/*"],
exclude = [
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index e9b7e2f..10fd50e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -836,6 +836,7 @@
if (changedProperties.has('change')) {
this.reload();
+ this.actions = this.change?.actions ?? {};
}
this.editStatusChanged();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index 01bc9ef..3207279 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -135,17 +135,19 @@
element = await fixture<GrChangeActions>(html`
<gr-change-actions></gr-change-actions>
`);
- element.change = createChangeViewChange();
- element.changeNum = 42 as NumericChangeId;
- element.latestPatchNum = 2 as PatchSetNum;
- element.actions = {
- '/': {
- method: HttpMethod.DELETE,
- label: 'Delete Change',
- title: 'Delete change X_X',
- enabled: true,
+ element.change = {
+ ...createChangeViewChange(),
+ actions: {
+ '/': {
+ method: HttpMethod.DELETE,
+ label: 'Delete Change',
+ title: 'Delete change X_X',
+ enabled: true,
+ },
},
};
+ element.changeNum = 42 as NumericChangeId;
+ element.latestPatchNum = 2 as PatchSetNum;
element.account = {
_account_id: 123 as AccountId,
};
@@ -1294,13 +1296,16 @@
setup(async () => {
fireActionStub = sinon.stub(element, 'fireAction');
element.commitMessage = 'random commit message';
- element.change!.current_revision = 'abcdef' as CommitId;
- element.actions = {
- revert: {
- method: HttpMethod.POST,
- label: 'Revert',
- title: 'Revert the change',
- enabled: true,
+ element.change = {
+ ...createChangeViewChange(),
+ current_revision: 'abcdef' as CommitId,
+ actions: {
+ revert: {
+ method: HttpMethod.POST,
+ label: 'Revert',
+ title: 'Revert the change',
+ enabled: true,
+ },
},
};
await element.updateComplete;
@@ -1322,6 +1327,14 @@
element.change = {
...createChangeViewChange(),
current_revision: 'abc1234' as CommitId,
+ actions: {
+ revert: {
+ method: HttpMethod.POST,
+ label: 'Revert',
+ title: 'Revert the change',
+ enabled: true,
+ },
+ },
};
stubRestApi('getChanges').returns(
Promise.resolve([
@@ -1368,6 +1381,14 @@
...createChangeViewChange(),
submission_id: '199 0' as ChangeSubmissionId,
current_revision: '2000' as CommitId,
+ actions: {
+ revert: {
+ method: HttpMethod.POST,
+ label: 'Revert',
+ title: 'Revert the change',
+ enabled: true,
+ },
+ },
};
getChangesStub = stubRestApi('getChanges').returns(
Promise.resolve([
@@ -1521,6 +1542,14 @@
...createChangeViewChange(),
submission_id: '199' as ChangeSubmissionId,
current_revision: '2000' as CommitId,
+ actions: {
+ revert: {
+ method: HttpMethod.POST,
+ label: 'Revert',
+ title: 'Revert the change',
+ enabled: true,
+ },
+ },
};
stubRestApi('getChanges').returns(
Promise.resolve([
@@ -1725,18 +1754,18 @@
setup(async () => {
fireActionStub = sinon.stub(element, 'fireAction');
- element.change = {
- ...createChangeViewChange(),
- current_revision: 'abc1234' as CommitId,
- };
deleteAction = {
method: HttpMethod.DELETE,
label: 'Delete Change',
title: 'Delete change X_X',
enabled: true,
};
- element.actions = {
- '/': deleteAction,
+ element.change = {
+ ...createChangeViewChange(),
+ current_revision: 'abc1234' as CommitId,
+ actions: {
+ '/': deleteAction,
+ },
};
await element.updateComplete;
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index 9af14f5..2c80a86 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -37,6 +37,7 @@
import {
ChangeStatus,
GpgKeyInfoStatus,
+ InheritedBooleanInfoConfiguredValue,
SubmitType,
} from '../../../constants/constants';
import {changeIsOpen, isOwner} from '../../../utils/change-util';
@@ -48,6 +49,7 @@
ChangeInfo,
CommitId,
CommitInfo,
+ ConfigInfo,
GpgKeyInfo,
Hashtag,
isAccount,
@@ -150,6 +152,8 @@
@property({type: Boolean}) parentIsCurrent?: boolean;
+ @property({type: Object}) repoConfig?: ConfigInfo;
+
// private but used in test
@state() mutable = false;
@@ -749,7 +753,8 @@
}
if (
changedProperties.has('serverConfig') ||
- changedProperties.has('change')
+ changedProperties.has('change') ||
+ changedProperties.has('repoConfig')
) {
this.pushCertificateValidation = this.computePushCertificateValidation();
}
@@ -887,6 +892,9 @@
if (!this.change || !this.serverConfig?.receive?.enable_signed_push)
return undefined;
+ if (!this.isEnabledSignedPushOnRepo()) {
+ return undefined;
+ }
const rev = this.change.revisions[this.change.current_revision];
if (!rev.push_certificate?.key) {
return {
@@ -930,6 +938,20 @@
}
}
+ // private but used in test
+ isEnabledSignedPushOnRepo() {
+ if (!this.repoConfig?.enable_signed_push) return false;
+
+ const enableSignedPush = this.repoConfig.enable_signed_push;
+ return (
+ (enableSignedPush.configured_value ===
+ InheritedBooleanInfoConfiguredValue.INHERIT &&
+ enableSignedPush.inherited_value) ||
+ enableSignedPush.configured_value ===
+ InheritedBooleanInfoConfiguredValue.TRUE
+ );
+ }
+
private problems(msg: string, key: GpgKeyInfo) {
if (!key?.problems || key.problems.length === 0) {
return msg;
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
index 2b48697..a000445 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.ts
@@ -32,11 +32,13 @@
createCommit,
createRevision,
createAccountDetailWithId,
+ createConfig,
} from '../../../test/test-data-generators';
import {
ChangeStatus,
SubmitType,
GpgKeyInfoStatus,
+ InheritedBooleanInfoConfiguredValue,
} from '../../../constants/constants';
import {
EmailAddress,
@@ -494,6 +496,13 @@
labels: {},
mergeable: true,
};
+ element.repoConfig = {
+ ...createConfig(),
+ enable_signed_push: {
+ configured_value: 'TRUE' as InheritedBooleanInfoConfiguredValue,
+ value: true,
+ },
+ };
});
test('Push Certificate Validation test BAD', () => {
@@ -546,6 +555,38 @@
assert.equal(result?.icon, 'gr-icons:help');
assert.equal(result?.class, 'help');
});
+
+ test('computePushCertificateValidation returns undefined', () => {
+ element.change = change;
+ delete serverConfig!.receive!.enable_signed_push;
+ element.serverConfig = serverConfig;
+ assert.isUndefined(element.computePushCertificateValidation());
+ });
+
+ test('isEnabledSignedPushOnRepo', () => {
+ change!.revisions.rev1!.push_certificate = {
+ certificate: 'Push certificate',
+ key: {
+ status: GpgKeyInfoStatus.TRUSTED,
+ },
+ };
+ element.change = change;
+ element.serverConfig = serverConfig;
+ element.repoConfig!.enable_signed_push!.configured_value =
+ InheritedBooleanInfoConfiguredValue.INHERIT;
+ element.repoConfig!.enable_signed_push!.inherited_value = true;
+ assert.isTrue(element.isEnabledSignedPushOnRepo());
+
+ element.repoConfig!.enable_signed_push!.inherited_value = false;
+ assert.isFalse(element.isEnabledSignedPushOnRepo());
+
+ element.repoConfig!.enable_signed_push!.configured_value =
+ InheritedBooleanInfoConfiguredValue.TRUE;
+ assert.isTrue(element.isEnabledSignedPushOnRepo());
+
+ element.repoConfig = undefined;
+ assert.isFalse(element.isEnabledSignedPushOnRepo());
+ });
});
test('computeParents', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index b7c4d7b..c8eb6a8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -1303,10 +1303,6 @@
if (value.basePatchNum === undefined)
value.basePatchNum = ParentPatchSetNum;
- if (value.patchNum === undefined) {
- value.patchNum = computeLatestPatchNum(this._allPatchSets);
- }
-
const patchChanged = this.hasPatchRangeChanged(value);
let patchNumChanged = this.hasPatchNumChanged(value);
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index 5d4db56..025a74b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -362,7 +362,6 @@
change="[[_change]]"
disable-edit="[[disableEdit]]"
has-parent="[[hasParent]]"
- actions="[[_change.actions]]"
revision-actions="[[_currentRevisionActions]]"
account="[[_account]]"
change-num="[[_changeNum]]"
@@ -397,6 +396,7 @@
commit-info="[[_commitInfo]]"
server-config="[[_serverConfig]]"
parent-is-current="[[_parentIsCurrent]]"
+ repo-config="[[_projectConfig]]"
on-show-reply-dialog="_handleShowReplyDialog"
>
</gr-change-metadata>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index fb8f279..f8103fd 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -39,7 +39,6 @@
ChangeStatus,
ProgressStatus,
} from '../../../constants/constants';
-import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {fireEvent} from '../../../utils/event-util';
import {css, html, LitElement, PropertyValues} from 'lit';
import {sharedStyles} from '../../../styles/shared-styles';
@@ -423,9 +422,7 @@
}
private toggleChangeSelected(e: Event) {
- const changeId = ((dom(e) as EventApi).localTarget as HTMLElement).dataset[
- 'item'
- ]! as ChangeInfoId;
+ const changeId = (e.target as HTMLElement).dataset['item']! as ChangeInfoId;
if (this.selectedChangeIds.has(changeId))
this.selectedChangeIds.delete(changeId);
else this.selectedChangeIds.add(changeId);
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index 0902fe6..f9afd8c 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -25,6 +25,7 @@
import {css, html, LitElement, nothing, PropertyValues} from 'lit';
import {MessageTag, SpecialFilePath} from '../../../constants/constants';
import {customElement, property, state} from 'lit/decorators';
+import {hasOwnProperty} from '../../../utils/common-util';
import {
ChangeInfo,
ServerInfo,
@@ -36,6 +37,7 @@
PatchSetNum,
AccountInfo,
BasePatchSetNum,
+ LabelNameToInfoMap,
} from '../../../types/common';
import {
ChangeMessage,
@@ -44,6 +46,7 @@
LabelExtreme,
PATCH_SET_PREFIX_PATTERN,
} from '../../../utils/comment-util';
+import {LABEL_TITLE_SCORE_PATTERN} from '../gr-message-scores/gr-message-scores';
import {getAppContext} from '../../../services/app-context';
import {pluralize} from '../../../utils/string-util';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
@@ -377,7 +380,8 @@
false,
this.message.message.substring(0, 1000),
this.message.accounts_in_message,
- this.message.tag
+ this.message.tag,
+ this.change?.labels
) || this.patchsetCommentSummary();
return html` <div class="content messageContent">
<div class="message hideOnOpen">${messageContentCollapsed}</div>
@@ -391,7 +395,8 @@
true,
this.message.message,
this.message.accounts_in_message,
- this.message.tag
+ this.message.tag,
+ this.change?.labels
);
return html`
<gr-formatted-text
@@ -594,7 +599,8 @@
isExpanded: boolean,
content?: string,
accountsInMessage?: AccountInfo[],
- tag?: ReviewInputTag
+ tag?: ReviewInputTag,
+ labels?: LabelNameToInfoMap
) {
if (!content) return '';
const isNewPatchSet = this.isNewPatchsetTag(tag);
@@ -614,8 +620,24 @@
if (line.startsWith('(') && line.endsWith(' comments)')) {
return false;
}
- if (!isNewPatchSet && line.match(PATCH_SET_PREFIX_PATTERN)) {
- return false;
+ if (!isNewPatchSet && labels) {
+ // Legacy change messages may contain the 'Patch Set' prefix
+ // and a message(not containing label scores) on the same line.
+ // To handle them correctly, only filter out lines which contain
+ // the 'Patch Set' prefix and label scores.
+ const match = line.match(PATCH_SET_PREFIX_PATTERN);
+ if (match && match[1]) {
+ const message = match[1].split(' ');
+ if (
+ message
+ .map(s => s.match(LABEL_TITLE_SCORE_PATTERN))
+ .filter(
+ ms => ms && ms.length === 4 && hasOwnProperty(labels, ms[2])
+ ).length === message.length
+ ) {
+ return false;
+ }
+ }
}
return true;
});
@@ -630,7 +652,7 @@
// Only make this replacement if the line starts with Patch Set, since if
// it starts with "Uploaded patch set" (e.g for votes) we want to keep the
// "Uploaded patch set".
- if (isNewPatchSet && line.startsWith('Patch Set')) {
+ if (line.startsWith('Patch Set')) {
line = line.replace(PATCH_SET_PREFIX_PATTERN, '$1');
}
return line;
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
index bbe39ff..6e550ac 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
@@ -24,6 +24,7 @@
createChangeMessage,
createComment,
createRevisions,
+ createLabelInfo,
} from '../../../test/test-data-generators';
import {
mockPromise,
@@ -444,13 +445,18 @@
});
suite('compute messages', () => {
+ const labels = {
+ 'Code-Review': createLabelInfo(1),
+ 'Code-Style': createLabelInfo(1),
+ };
test('empty', () => {
assert.equal(
element.computeMessageContent(
true,
'',
undefined,
- '' as ReviewInputTag
+ '' as ReviewInputTag,
+ labels
),
''
);
@@ -459,7 +465,8 @@
false,
'',
undefined,
- '' as ReviewInputTag
+ '' as ReviewInputTag,
+ labels
),
''
);
@@ -469,8 +476,18 @@
const original = 'Uploaded patch set 1.';
const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
let actual = element.computeMessageContent(true, original, [], tag);
+ assert.equal(
+ actual,
+ element.computeMessageContent(true, original, [], tag, labels)
+ );
assert.equal(actual, original);
- actual = element.computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, original);
});
@@ -478,9 +495,25 @@
const original = 'Patch Set 27: Patch Set 26 was rebased';
const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
const expected = 'Patch Set 26 was rebased';
- let actual = element.computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(
+ true,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
- actual = element.computeMessageContent(false, original, [], tag);
+ assert.equal(
+ actual,
+ element.computeMessageContent(true, original, [], tag)
+ );
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
});
@@ -488,27 +521,89 @@
const original = 'Patch Set 1:\n\nThis change is ready for review.';
const tag = undefined;
const expected = 'This change is ready for review.';
- let actual = element.computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(
+ true,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
- actual = element.computeMessageContent(false, original, [], tag);
+ assert.equal(
+ actual,
+ element.computeMessageContent(true, original, [], tag)
+ );
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
});
test('new patchset with vote', () => {
const original = 'Uploaded patch set 2: Code-Review+1';
const tag = 'autogenerated:gerrit:newPatchSet' as ReviewInputTag;
const expected = 'Uploaded patch set 2: Code-Review+1';
- let actual = element.computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(
+ true,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
- actual = element.computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
});
test('vote', () => {
const original = 'Patch Set 1: Code-Style+1';
const tag = undefined;
const expected = '';
- let actual = element.computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(
+ true,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
- actual = element.computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
+ assert.equal(actual, expected);
+ });
+
+ test('legacy change message', () => {
+ const original = 'Patch Set 1: Legacy Message';
+ const tag = undefined;
+ const expected = 'Legacy Message';
+ let actual = element.computeMessageContent(
+ true,
+ original,
+ [],
+ tag,
+ labels
+ );
+ assert.equal(actual, expected);
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
});
@@ -516,9 +611,21 @@
const original = 'Patch Set 1:\n\n(3 comments)';
const tag = undefined;
const expected = '';
- let actual = element.computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(
+ true,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
- actual = element.computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
});
@@ -536,14 +643,16 @@
true,
original,
accountsInMessage,
- tag
+ tag,
+ labels
);
assert.equal(actual, expected);
actual = element.computeMessageContent(
false,
original,
accountsInMessage,
- tag
+ tag,
+ labels
);
assert.equal(actual, expected);
});
@@ -554,9 +663,21 @@
const tag = undefined;
const expected =
'Removed vote: \n\n * Code-Style+1 by Gerrit Account 1\n * Code-Style-1 by Gerrit Account 2';
- let actual = element.computeMessageContent(true, original, [], tag);
+ let actual = element.computeMessageContent(
+ true,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
- actual = element.computeMessageContent(false, original, [], tag);
+ actual = element.computeMessageContent(
+ false,
+ original,
+ [],
+ tag,
+ labels
+ );
assert.equal(actual, expected);
});
});
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
index daed7d2..ae20c4e 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.ts
@@ -18,7 +18,6 @@
import '../../shared/gr-button/gr-button';
import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
import '../../shared/gr-overlay/gr-overlay';
-import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {SshKeyInfo} from '../../../types/common';
import {GrButton} from '../../shared/gr-button/gr-button';
import {IronAutogrowTextareaElement} from '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
@@ -240,14 +239,14 @@
}
private showKey(e: Event) {
- const el = (dom(e) as EventApi).localTarget as GrButton;
+ const el = e.target as GrButton;
const index = Number(el.getAttribute('data-index')!);
this.keyToView = this.keys[index];
this.viewKeyOverlay.open();
}
private handleDeleteKey(e: Event) {
- const el = (dom(e) as EventApi).localTarget as GrButton;
+ const el = e.target as GrButton;
const index = Number(el.getAttribute('data-index')!);
this.keysToRemove.push(this.keys[index]);
this.keys.splice(index, 1);
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index b1d3914..13375b3 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -24,7 +24,6 @@
import '../gr-icons/gr-icons';
import '../gr-label/gr-label';
import '../gr-tooltip-content/gr-tooltip-content';
-import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {
AccountInfo,
LabelInfo,
@@ -448,7 +447,7 @@
if (!this.change) return;
e.preventDefault();
- let target = (dom(e) as EventApi).rootTarget as GrButton;
+ let target = e.composedPath()[0] as GrButton;
while (!target.classList.contains('deleteBtn')) {
if (!target.parentElement) {
return;
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
index 8e6a147..2729e69 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
@@ -97,6 +97,7 @@
HashtagsInput,
ImagesForDiff,
IncludedInInfo,
+ LabelNameToLabelTypeInfoMap,
MergeableInfo,
NameToProjectInfoMap,
NumericChangeId,
@@ -130,11 +131,13 @@
TagInput,
TopMenuEntryInfo,
UrlEncodedCommentId,
+ UrlEncodedRepoName,
} from '../../types/common';
import {
DiffInfo,
DiffPreferencesInfo,
IgnoreWhitespaceType,
+ WebLinkInfo,
} from '../../types/diff';
import {
CancelConditionCallback,
@@ -148,6 +151,7 @@
createDefaultEditPrefs,
createDefaultPreferences,
HttpMethod,
+ ProjectState,
ReviewerState,
} from '../../constants/constants';
import {firePageError, fireServerError} from '../../utils/event-util';
@@ -1429,36 +1433,26 @@
filter: string | undefined,
reposPerPage: number,
offset?: number
- ) {
- const defaultFilter = 'state:active OR state:read-only';
- const namePartDelimiters = /[@.\-\s/_]/g;
+ ): [boolean, string] {
+ const defaultFilter = '';
offset = offset || 0;
-
- if (filter && !filter.includes(':') && filter.match(namePartDelimiters)) {
- // The query language specifies hyphens as operators. Split the string
- // by hyphens and 'AND' the parts together as 'inname:' queries.
- // If the filter includes a semicolon, the user is using a more complex
- // query so we trust them and don't do any magic under the hood.
- const originalFilter = filter;
- filter = '';
- originalFilter.split(namePartDelimiters).forEach(part => {
- if (part) {
- filter += (filter === '' ? 'inname:' : ' AND inname:') + part;
- }
- });
- }
- // Check if filter is now empty which could be either because the user did
- // not provide it or because the user provided only a split character.
- if (!filter) {
- filter = defaultFilter;
- }
-
- filter = filter.trim();
+ filter ??= defaultFilter;
const encodedFilter = encodeURIComponent(filter);
- return (
- `/projects/?n=${reposPerPage + 1}&S=${offset}` + `&query=${encodedFilter}`
- );
+ if (filter.includes(':')) {
+ // If the filter includes a semicolon, the user is using a more complex
+ // query so we trust them and don't do any magic under the hood.
+ return [
+ true,
+ `/projects/?n=${reposPerPage + 1}&S=${offset}` +
+ `&query=${encodedFilter}`,
+ ];
+ }
+
+ return [
+ false,
+ `/projects/?n=${reposPerPage + 1}&S=${offset}` + `&d=&m=${encodedFilter}`,
+ ];
}
invalidateGroupsCache() {
@@ -1491,14 +1485,47 @@
reposPerPage: number,
offset?: number
): Promise<ProjectInfoWithName[] | undefined> {
- const url = this._getReposUrl(filter, reposPerPage, offset);
+ const [isQuery, url] = this._getReposUrl(filter, reposPerPage, offset);
// TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
// supports it.
- return this._fetchSharedCacheURL({
- url, // The url contains query,so the response is an array, not map
- anonymizedUrl: '/projects/?*',
- }) as Promise<ProjectInfoWithName[] | undefined>;
+ // If query then return directly as the result will be expected to be an array
+ if (isQuery) {
+ return this._fetchSharedCacheURL({
+ url, // The url contains query,so the response is an array, not map
+ anonymizedUrl: '/projects/?*',
+ }) as Promise<ProjectInfoWithName[] | undefined>;
+ }
+ const result: Promise<NameToProjectInfoMap[] | undefined> =
+ this._fetchSharedCacheURL({
+ url, // The url contains query,so the response is an array, not map
+ anonymizedUrl: '/projects/?*',
+ }) as Promise<NameToProjectInfoMap[] | undefined>;
+ return this._transformToArray(result);
+ }
+
+ _transformToArray(
+ res: Promise<NameToProjectInfoMap[] | undefined>
+ ): Promise<ProjectInfoWithName[] | undefined> {
+ return res.then(response => {
+ const reposList: ProjectInfoWithName[] = [];
+ for (const [name, project] of Object.entries(response ?? {})) {
+ const projectInfo: ProjectInfoWithName = {
+ id: project.id as unknown as UrlEncodedRepoName,
+ name: name as RepoName,
+ parent: project.parent as unknown as RepoName,
+ description: project.description as unknown as string,
+ state: project.state as unknown as ProjectState,
+ branches: project.branches as unknown as {
+ [branchName: string]: CommitId;
+ },
+ labels: project.labels as unknown as LabelNameToLabelTypeInfoMap,
+ web_links: project.web_links as unknown as WebLinkInfo[],
+ };
+ reposList.push(projectInfo);
+ }
+ return reposList;
+ });
}
setRepoHead(repo: RepoName, ref: GitRef) {
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js
index fbfd152..d8c3ff2 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.js
@@ -677,16 +677,19 @@
});
test('normal use', () => {
- const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
+ const defaultQuery = '';
- assert.equal(element._getReposUrl('test', 25),
- '/projects/?n=26&S=0&query=test');
+ assert.equal(element._getReposUrl('test', 25).toString(),
+ [false, '/projects/?n=26&S=0&d=&m=test'].toString());
- assert.equal(element._getReposUrl(null, 25),
- `/projects/?n=26&S=0&query=${defaultQuery}`);
+ assert.equal(element._getReposUrl(null, 25).toString(),
+ [false, `/projects/?n=26&S=0&d=&m=${defaultQuery}`].toString());
- assert.equal(element._getReposUrl('test', 25, 25),
- '/projects/?n=26&S=25&query=test');
+ assert.equal(element._getReposUrl('test', 25, 25).toString(),
+ [false, '/projects/?n=26&S=25&d=&m=test'].toString());
+
+ assert.equal(element._getReposUrl('inname:test', 25, 25).toString(),
+ [true, '/projects/?n=26&S=25&query=inname%3Atest'].toString());
});
test('invalidateReposCache', () => {
@@ -714,67 +717,74 @@
});
suite('getRepos', () => {
- const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
+ const defaultQuery = '';
let fetchCacheURLStub;
setup(() => {
fetchCacheURLStub =
- sinon.stub(element._restApiHelper, 'fetchCacheURL');
+ sinon.stub(element._restApiHelper, 'fetchCacheURL')
+ .returns(Promise.resolve([]));
});
test('normal use', () => {
element.getRepos('test', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=0&query=test');
+ '/projects/?n=26&S=0&d=&m=test');
element.getRepos(null, 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- `/projects/?n=26&S=0&query=${defaultQuery}`);
+ `/projects/?n=26&S=0&d=&m=${defaultQuery}`);
element.getRepos('test', 25, 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=25&query=test');
+ '/projects/?n=26&S=25&d=&m=test');
});
test('with blank', () => {
element.getRepos('test/test', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=0&query=inname%3Atest%20AND%20inname%3Atest');
+ '/projects/?n=26&S=0&d=&m=test%2Ftest');
});
test('with hyphen', () => {
element.getRepos('foo-bar', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ '/projects/?n=26&S=0&d=&m=foo-bar');
});
test('with leading hyphen', () => {
element.getRepos('-bar', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=0&query=inname%3Abar');
+ '/projects/?n=26&S=0&d=&m=-bar');
});
test('with trailing hyphen', () => {
element.getRepos('foo-bar-', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ '/projects/?n=26&S=0&d=&m=foo-bar-');
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ '/projects/?n=26&S=0&d=&m=foo_bar');
});
test('with underscore', () => {
element.getRepos('foo_bar', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- '/projects/?n=26&S=0&query=inname%3Afoo%20AND%20inname%3Abar');
+ '/projects/?n=26&S=0&d=&m=foo_bar');
});
test('hyphen only', () => {
element.getRepos('-', 25);
assert.equal(fetchCacheURLStub.lastCall.args[0].url,
- `/projects/?n=26&S=0&query=${defaultQuery}`);
+ `/projects/?n=26&S=0&d=&m=-`);
+ });
+
+ test('using query', () =>{
+ element.getRepos('description:project', 25);
+ assert.equal(fetchCacheURLStub.lastCall.args[0].url,
+ `/projects/?n=26&S=0&query=description%3Aproject`);
});
});
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index 2fffc9a..8c33c79 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -51,6 +51,7 @@
GroupId,
GroupInfo,
InheritedBooleanInfo,
+ LabelInfo,
MaxObjectSizeLimitInfo,
MergeableInfo,
NumericChangeId,
@@ -239,6 +240,30 @@
};
}
+export function createLabelInfo(score = 1): LabelInfo {
+ return {
+ all: [
+ {
+ value: score,
+ permitted_voting_range: {
+ min: -1,
+ max: 1,
+ },
+ _account_id: 1000 as AccountId,
+ name: 'Foo',
+ email: 'foo@example.com' as EmailAddress,
+ username: 'foo',
+ },
+ ],
+ values: {
+ '-1': 'Fail',
+ ' 0': 'No score',
+ '+1': 'Pass',
+ },
+ default_value: 0,
+ };
+}
+
export function createCommit(): CommitInfo {
return {
parents: [],