Merge "Merge branch 'stable-3.8' into stable-3.9" into stable-3.9
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 7f25509..f795def 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3420,6 +3420,15 @@
+
Defaults to true.
+[[index.excludeProjectFromChangeReindex]]index.excludeProjectFromChangeReindex::
++
+A list of projects that will be excluded from reindexing. This can be used
+to exclude projects which are expensive to reindex to prioritize the other
+projects.
++
+Excluded projects can later be reindexed by for example using the
+link:cmd-index-changes-in-project.html[index changes in project command].
+
[[index.paginationType]]index.paginationType::
+
The pagination type to use when index queries are repeated to
diff --git a/Documentation/config-submit-requirements.txt b/Documentation/config-submit-requirements.txt
index fb12ff3..1bcda63 100644
--- a/Documentation/config-submit-requirements.txt
+++ b/Documentation/config-submit-requirements.txt
@@ -127,7 +127,7 @@
link:#submit_requirement_submittable_if[submittableIf] expression evaluates to
true or not.
-* `BYPASSED`
+* `FORCED`
+
The change was merged directly bypassing code review by supplying the
link:user-upload.html#auto_merge[submit] push option while doing a git push.
diff --git a/Documentation/dev-processes.txt b/Documentation/dev-processes.txt
index 175a159..41543ab 100644
--- a/Documentation/dev-processes.txt
+++ b/Documentation/dev-processes.txt
@@ -53,15 +53,14 @@
=== Election of non-Google steering committee members
The election of the non-Google steering committee members happens once
-a year in May. Non-Google link:dev-roles.html#maintainer[maintainers]
+a year in June. Non-Google link:dev-roles.html#maintainer[maintainers]
can nominate themselves by posting an informal application on the
non-public mailto:gerritcodereview-community-managers@googlegroups.com[
-community manager mailing list] by end of April (deadline for 2020
-is Thu 30th of April EOD).
+community manager mailing list] when the call for nomiations is sent to
+the maintainers list by a community manager.
The list with all candidates will be published at the beginning of the
-voting period (for 2020 the start of the voting is planned for Mon 4th
-of May).
+voting period.
Keeping the candidates private during the nomination phase and
publishing all candidates at once only at the start of the voting
@@ -83,7 +82,7 @@
happens by posting on the
mailto:gerritcodereview-maintainers@googlegroups.com[maintainer mailing
list]. The voting period is 14 calendar days from the start of the
-voting (for 2020 the voting period ends on Mon 18th May EOD).
+voting.
Google maintainers do not take part in this vote, because Google
already has dedicated seats in the steering committee (see section
diff --git a/Documentation/dev-roles.txt b/Documentation/dev-roles.txt
index f3a81e7..36fd46e 100644
--- a/Documentation/dev-roles.txt
+++ b/Documentation/dev-roles.txt
@@ -329,10 +329,10 @@
This is a group that remains private between the individual community
member and community managers.
-The community managers should be a pair or trio that shares the work:
+The community managers should be at least a pair that shares the work:
* One Googler that is appointed by Google.
-* One or two non-Googlers, elected by the community if there are more
+* One or more non-Googlers, elected by the community if there are more
than two candidates. If there is no candidate, we only have the one
community manager from Google.
diff --git a/java/com/google/gerrit/acceptance/AccountCreator.java b/java/com/google/gerrit/acceptance/AccountCreator.java
index 310d141..f3881f2 100644
--- a/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -24,6 +24,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.GroupCache;
@@ -32,7 +33,6 @@
import com.google.gerrit.server.account.externalids.ExternalIdFactory;
import com.google.gerrit.server.group.db.GroupDelta;
import com.google.gerrit.server.group.db.GroupsUpdate;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
index c6457a4..edbb1ee 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
@@ -20,6 +20,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountDelta;
import com.google.gerrit.server.account.AccountState;
@@ -28,7 +29,6 @@
import com.google.gerrit.server.account.AccountsUpdate.ConfigureDeltaFromState;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdFactory;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.Optional;
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImpl.java
index 5efcfc6..dbcfceb 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImpl.java
@@ -33,6 +33,7 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.change.ChangeInserter;
@@ -41,7 +42,6 @@
import com.google.gerrit.server.edit.tree.TreeModification;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException;
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
index dcf1158..0a22688 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
@@ -21,13 +21,13 @@
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.GroupUuid;
import com.google.gerrit.server.group.db.GroupDelta;
import com.google.gerrit.server.group.db.Groups;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.group.db.InternalGroupCreation;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.Optional;
diff --git a/java/com/google/gerrit/entities/Account.java b/java/com/google/gerrit/entities/Account.java
index 699acc0..52ad0a9 100644
--- a/java/com/google/gerrit/entities/Account.java
+++ b/java/com/google/gerrit/entities/Account.java
@@ -162,7 +162,7 @@
/**
* Create a new account.
*
- * @param newId unique id, see {@link com.google.gerrit.server.notedb.Sequences#nextAccountId()}.
+ * @param newId unique id, see Sequences#nextAccountId().
* @param registeredOn when the account was registered.
*/
public static Account.Builder builder(Account.Id newId, Instant registeredOn) {
diff --git a/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java b/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java
index 77c5381..d9f1c09 100644
--- a/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java
+++ b/java/com/google/gerrit/httpd/NumericChangeIdRedirectServlet.java
@@ -29,7 +29,17 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-/** Redirects {@code domain.tld/123} to {@code domain.tld/c/project/+/123}. */
+/**
+ * Redirects:
+ *
+ * <ul>
+ * <li>{@code domain.tld/123} to {@code domain.tld/c/project/+/123}
+ * <li/>
+ * <li>{@code domain.tld/123/comment/bc630c55_3e265b44} to {@code
+ * domain.tld/c/project/+/123/comment/bc630c55_3e265b44/}
+ * <li/>
+ * </ul>
+ */
@Singleton
public class NumericChangeIdRedirectServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -43,7 +53,11 @@
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
- String idString = req.getPathInfo();
+ String uriPath = req.getPathInfo();
+ // Check if we are processing a comment url, like "/c/1/comment/ff3303fd_8341647b/".
+ int commentIdx = uriPath.indexOf("/comment");
+ String idString = commentIdx == -1 ? uriPath : uriPath.substring(0, commentIdx);
+
if (idString.endsWith("/")) {
idString = idString.substring(0, idString.length() - 1);
}
@@ -64,6 +78,10 @@
}
String path =
PageLinks.toChange(changeResource.getProject(), changeResource.getChange().getId());
+ if (commentIdx > -1) {
+ // path already contain a trailing /, hence we start from "commentIdx + 1"
+ path = path + uriPath.substring(commentIdx + 1);
+ }
UrlModule.toGerrit(path, req, rsp);
}
}
diff --git a/java/com/google/gerrit/httpd/UrlModule.java b/java/com/google/gerrit/httpd/UrlModule.java
index 5a898a1..aad6b57 100644
--- a/java/com/google/gerrit/httpd/UrlModule.java
+++ b/java/com/google/gerrit/httpd/UrlModule.java
@@ -73,6 +73,7 @@
serveRegex("^/register$").with(registerScreen(false));
serveRegex("^/register/(.+)$").with(registerScreen(true));
serveRegex("^(?:/c)?/([1-9][0-9]*)/?$").with(NumericChangeIdRedirectServlet.class);
+ serveRegex("^(?:/c)?/([1-9][0-9]*)/comment/\\w+/?$").with(NumericChangeIdRedirectServlet.class);
serveRegex("^/p/(.*)$").with(queryProjectNew());
serveRegex("^/r/(.+)/?$").with(DirectChangeByCommit.class);
diff --git a/java/com/google/gerrit/pgm/init/InitModule.java b/java/com/google/gerrit/pgm/init/InitModule.java
index a40a704..f36ec3d 100644
--- a/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/java/com/google/gerrit/pgm/init/InitModule.java
@@ -20,13 +20,13 @@
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.pgm.init.api.Section;
+import com.google.gerrit.pgm.init.api.SequencesOnInit.DisabledGitRefUpdatedRepoAccountsSequenceProvider;
import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.account.externalids.ExternalIdFactory;
import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdFactoryNoteDbImpl;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.notedb.RepoSequence.DisabledGitRefUpdatedRepoAccountsSequenceProvider;
import com.google.inject.Singleton;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.UniqueAnnotations;
diff --git a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
index f6e2b9c..68b1de7 100644
--- a/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
@@ -17,8 +17,17 @@
import static com.google.gerrit.server.Sequence.LightweightAccounts;
import com.google.gerrit.server.Sequence;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.RepoSequence;
+import com.google.gerrit.server.notedb.RepoSequence.RepoSequenceModule;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Config;
@Singleton
public class SequencesOnInit {
@@ -32,4 +41,39 @@
public int nextAccountId() {
return accountsSequence.next();
}
+
+ /** A accounts sequence provider that does not fire git reference updates. */
+ public static class DisabledGitRefUpdatedRepoAccountsSequenceProvider
+ implements Provider<Sequence> {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+ private final Config cfg;
+
+ @Inject
+ DisabledGitRefUpdatedRepoAccountsSequenceProvider(
+ @GerritServerConfig Config cfg,
+ GitRepositoryManagerOnInit repoManager,
+ AllUsersName allUsersName) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsersName;
+ this.cfg = cfg;
+ }
+
+ @Override
+ public Sequence get() {
+ int accountBatchSize =
+ cfg.getInt(
+ RepoSequenceModule.SECTION_NOTE_DB,
+ Sequence.NAME_ACCOUNTS,
+ RepoSequenceModule.KEY_SEQUENCE_BATCH_SIZE,
+ RepoSequenceModule.DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE);
+ return new RepoSequence(
+ repoManager,
+ GitReferenceUpdated.DISABLED,
+ allUsers,
+ Sequence.NAME_ACCOUNTS,
+ () -> Sequences.FIRST_ACCOUNT_ID,
+ accountBatchSize);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/DeleteZombieComments.java b/java/com/google/gerrit/server/DeleteZombieComments.java
index 8deee6f..4532b04 100644
--- a/java/com/google/gerrit/server/DeleteZombieComments.java
+++ b/java/com/google/gerrit/server/DeleteZombieComments.java
@@ -109,9 +109,15 @@
@CanIgnoreReturnValue
public int execute() throws IOException {
setup();
- List<KeyT> emptyDrafts = filterByCleanupPercentage(listEmptyDrafts(), "empty");
ListMultimap<KeyT, HumanComment> alreadyPublished = listDraftCommentsThatAreAlsoPublished();
- if (dryRun) {
+ if (!dryRun) {
+ deleteZombieDrafts(alreadyPublished);
+ }
+
+ List<KeyT> emptyDrafts = filterByCleanupPercentage(listEmptyDrafts(), "empty");
+ if (!dryRun) {
+ deleteEmptyDraftsByKey(emptyDrafts);
+ } else {
logInfo(
String.format(
"Running in dry run mode. Skipping deletion."
@@ -119,9 +125,6 @@
+ "\nEmpty drafts = %d"
+ "\nAlready published drafts (zombies) = %d",
cleanupPercentage, emptyDrafts.size(), alreadyPublished.size()));
- } else {
- deleteEmptyDraftsByKey(emptyDrafts);
- deleteZombieDrafts(alreadyPublished);
}
return emptyDrafts.size() + alreadyPublished.size();
}
diff --git a/java/com/google/gerrit/server/notedb/Sequences.java b/java/com/google/gerrit/server/Sequences.java
similarity index 97%
rename from java/com/google/gerrit/server/notedb/Sequences.java
rename to java/com/google/gerrit/server/Sequences.java
index 780998b..431a1b2 100644
--- a/java/com/google/gerrit/server/notedb/Sequences.java
+++ b/java/com/google/gerrit/server/Sequences.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.notedb;
+package com.google.gerrit.server;
import static com.google.gerrit.server.Sequence.NAME_ACCOUNTS;
import static com.google.gerrit.server.Sequence.NAME_CHANGES;
@@ -24,7 +24,6 @@
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer2;
-import com.google.gerrit.server.Sequence;
import com.google.gerrit.server.Sequence.SequenceType;
import com.google.gerrit.server.logging.Metadata;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/account/AccountManager.java b/java/com/google/gerrit/server/account/AccountManager.java
index edec52c..5023413 100644
--- a/java/com/google/gerrit/server/account/AccountManager.java
+++ b/java/com/google/gerrit/server/account/AccountManager.java
@@ -35,6 +35,7 @@
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -45,7 +46,6 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.db.GroupDelta;
import com.google.gerrit.server.group.db.GroupsUpdate;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 09fbb89..24e8ba5 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -26,7 +26,7 @@
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.Sequences;
import com.google.inject.BindingAnnotation;
import java.io.IOException;
import java.lang.annotation.Retention;
diff --git a/java/com/google/gerrit/server/git/CommitUtil.java b/java/com/google/gerrit/server/git/CommitUtil.java
index 74f1355..8a7840b 100644
--- a/java/com/google/gerrit/server/git/CommitUtil.java
+++ b/java/com/google/gerrit/server/git/CommitUtil.java
@@ -39,6 +39,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.ReviewerSet;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.approval.ApprovalsUtil;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeMessages;
@@ -51,7 +52,6 @@
import com.google.gerrit.server.mail.send.OutgoingEmail;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index b0e1e38..d27b13ad 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -119,6 +119,7 @@
import com.google.gerrit.server.PublishCommentsOp;
import com.google.gerrit.server.RequestInfo;
import com.google.gerrit.server.RequestListener;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.approval.ApprovalsUtil;
import com.google.gerrit.server.cancellation.RequestCancelledException;
@@ -159,7 +160,6 @@
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.patch.AutoMerger;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.permissions.ChangePermission;
diff --git a/java/com/google/gerrit/server/index/OnlineReindexer.java b/java/com/google/gerrit/server/index/OnlineReindexer.java
index eef394d..e3b8e7c 100644
--- a/java/com/google/gerrit/server/index/OnlineReindexer.java
+++ b/java/com/google/gerrit/server/index/OnlineReindexer.java
@@ -106,9 +106,6 @@
"Starting online reindex of %s from schema version %s to %s",
name, version(indexes.getSearchIndex()), version(index));
- if (oldVersion != newVersion) {
- index.deleteAll();
- }
SiteIndexer.Result result = batchIndexer.indexAll(index);
if (!result.success()) {
logger.atSevere().log(
diff --git a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index 4f411a2..b98bae0 100644
--- a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -22,7 +22,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
@@ -31,6 +31,7 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
import com.google.gerrit.index.SiteIndexer;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
@@ -46,10 +47,14 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
@@ -82,6 +87,7 @@
private final ChangeIndexer.Factory indexerFactory;
private final ChangeNotes.Factory notesFactory;
private final ProjectCache projectCache;
+ private final Set<Project.NameKey> projectsToSkip;
@Inject
AllChangesIndexer(
@@ -91,7 +97,8 @@
@IndexExecutor(BATCH) ListeningExecutorService executor,
ChangeIndexer.Factory indexerFactory,
ChangeNotes.Factory notesFactory,
- ProjectCache projectCache) {
+ ProjectCache projectCache,
+ @GerritServerConfig Config config) {
this.multiProgressMonitorFactory = multiProgressMonitorFactory;
this.changeDataFactory = changeDataFactory;
this.repoManager = repoManager;
@@ -99,6 +106,11 @@
this.indexerFactory = indexerFactory;
this.notesFactory = notesFactory;
this.projectCache = projectCache;
+ this.projectsToSkip =
+ Sets.newHashSet(config.getStringList("index", null, "excludeProjectFromChangeReindex"))
+ .stream()
+ .map(p -> Project.NameKey.parse(p))
+ .collect(Collectors.toSet());
}
@AutoValue
@@ -186,15 +198,20 @@
return Result.create(sw, ok.get(), nDone, nFailed);
}
+ /**
+ * Reindexes all changes in a given project, even if they already exist in the index. Changes will
+ * not be sliced to allow multithreaded reindexing.
+ */
@Nullable
public Callable<Void> reindexProject(
ChangeIndexer indexer, Project.NameKey project, Task done, Task failed) {
try (Repository repo = repoManager.openRepository(project)) {
- return reindexProjectSlice(
+ return new ProjectSliceIndexer(
indexer,
ProjectSlice.oneSlice(project, ChangeNotes.Factory.scanChangeIds(repo)),
done,
- failed);
+ failed,
+ true);
} catch (IOException e) {
logger.atSevere().log("%s", e.getMessage());
return null;
@@ -203,7 +220,7 @@
public Callable<Void> reindexProjectSlice(
ChangeIndexer indexer, ProjectSlice projectSlice, Task done, Task failed) {
- return new ProjectSliceIndexer(indexer, projectSlice, done, failed);
+ return new ProjectSliceIndexer(indexer, projectSlice, done, failed, false);
}
private class ProjectSliceIndexer implements Callable<Void> {
@@ -211,46 +228,73 @@
private final ProjectSlice projectSlice;
private final ProgressMonitor done;
private final ProgressMonitor failed;
+ private final boolean forceReindex;
private ProjectSliceIndexer(
ChangeIndexer indexer,
ProjectSlice projectSlice,
ProgressMonitor done,
- ProgressMonitor failed) {
+ ProgressMonitor failed,
+ boolean forceReindex) {
this.indexer = indexer;
this.projectSlice = projectSlice;
this.done = done;
this.failed = failed;
+ this.forceReindex = forceReindex;
}
@Override
public Void call() throws Exception {
- OnlineReindexMode.begin();
- // Order of scanning changes is undefined. This is ok if we assume that packfile locality is
- // not important for indexing, since sites should have a fully populated DiffSummary cache.
- // It does mean that reindexing after invalidating the DiffSummary cache will be expensive,
- // but the goal is to invalidate that cache as infrequently as we possibly can. And besides,
- // we don't have concrete proof that improving packfile locality would help.
- notesFactory
- .scan(
- projectSlice.metaIdByChange(),
- projectSlice.name(),
- id -> (id.get() % projectSlice.slices()) == projectSlice.slice())
- .forEach(r -> index(r));
- OnlineReindexMode.end();
+ String oldThreadName = Thread.currentThread().getName();
+ try {
+ Thread.currentThread()
+ .setName(
+ oldThreadName
+ + "["
+ + projectSlice.name().toString()
+ + "-"
+ + projectSlice.slice()
+ + "]");
+ OnlineReindexMode.begin();
+ Optional<ChangeIndex> newestIndex = indexer.getNewestIndex();
+ if (newestIndex.isEmpty()) {
+ logger.atWarning().log("No change index available yet");
+ }
+ // Order of scanning changes is undefined. This is ok if we assume that packfile locality is
+ // not important for indexing, since sites should have a fully populated DiffSummary cache.
+ // It does mean that reindexing after invalidating the DiffSummary cache will be expensive,
+ // but the goal is to invalidate that cache as infrequently as we possibly can. And besides,
+ // we don't have concrete proof that improving packfile locality would help.
+ notesFactory
+ .scan(
+ projectSlice.metaIdByChange(),
+ projectSlice.name(),
+ id -> (id.get() % projectSlice.slices()) == projectSlice.slice())
+ .forEach(r -> index(r, newestIndex));
+ OnlineReindexMode.end();
+ } finally {
+ Thread.currentThread().setName(oldThreadName);
+ }
return null;
}
- private void index(ChangeNotesResult r) {
+ private void index(ChangeNotesResult r, Optional<ChangeIndex> newestIndex) {
if (r.error().isPresent()) {
fail("Failed to read change " + r.id() + " for indexing", true, r.error().get());
return;
}
try {
- indexer.index(changeDataFactory.create(r.notes()));
+ if (forceReindex || !indexer.isChangeAlreadyIndexed(r.id(), newestIndex)) {
+ indexer.index(changeDataFactory.create(r.notes()));
+ verboseWriter.format(
+ "Reindexed change %d (project: %s)\n",
+ r.id().get(), r.notes().getProjectName().get());
+
+ } else {
+ verboseWriter.format(
+ "Skipped change %d (project: %s)\n", r.id().get(), r.notes().getProjectName().get());
+ }
done.update(1);
- verboseWriter.format(
- "Reindexed change %d (project: %s)\n", r.id().get(), r.notes().getProjectName().get());
} catch (RejectedExecutionException e) {
// Server shutdown, don't spam the logs.
failSilently();
@@ -302,7 +346,7 @@
}
private List<ListenableFuture<?>> schedule() throws ProjectsCollectionFailure {
- ImmutableSortedSet<Project.NameKey> projects = projectCache.all();
+ Set<Project.NameKey> projects = Sets.difference(projectCache.all(), projectsToSkip);
int projectCount = projects.size();
slicingProjects = mpm.beginSubTask("Slicing projects", projectCount);
for (Project.NameKey name : projects) {
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index 517809a..bf83a1b 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -27,6 +27,8 @@
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.events.ChangeIndexedListener;
import com.google.gerrit.index.Index;
+import com.google.gerrit.metrics.proc.ThreadMXBeanFactory;
+import com.google.gerrit.metrics.proc.ThreadMXBeanInterface;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.IndexExecutor;
import com.google.gerrit.server.index.StalenessCheckResult;
@@ -45,6 +47,7 @@
import com.google.inject.assistedinject.AssistedInject;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -62,6 +65,7 @@
*/
public class ChangeIndexer {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final ThreadMXBeanInterface threadMxBean = ThreadMXBeanFactory.create();
public interface Factory {
ChangeIndexer create(ListeningExecutorService executor, ChangeIndex index);
@@ -218,32 +222,56 @@
}
private void indexImpl(ChangeData cd) {
- logger.atFine().log("Reindex change %d in index.", cd.getId().get());
- for (Index<?, ChangeData> i : getWriteIndexes()) {
- try (TraceTimer traceTimer =
- TraceContext.newTimer(
- "Reindexing change in index",
- Metadata.builder()
- .changeId(cd.getId().get())
- .patchSetId(cd.currentPatchSet().number())
- .indexVersion(i.getSchema().getVersion())
- .build())) {
- if (isFirstInsertForEntry.equals(IsFirstInsertForEntry.YES)) {
- i.insert(cd);
- } else {
- i.replace(cd);
+ long memoryAtStart = 0;
+ if (logger.atFine().isEnabled()) {
+ memoryAtStart = threadMxBean.getCurrentThreadAllocatedBytes();
+ logger.atFine().log("Reindex change %d in index.", cd.getId().get());
+ }
+ try {
+ for (Index<?, ChangeData> i : getWriteIndexes()) {
+ try (TraceTimer traceTimer =
+ TraceContext.newTimer(
+ "Reindexing change in index",
+ Metadata.builder()
+ .changeId(cd.getId().get())
+ .patchSetId(cd.currentPatchSet().number())
+ .indexVersion(i.getSchema().getVersion())
+ .build())) {
+ if (isFirstInsertForEntry.equals(IsFirstInsertForEntry.YES)) {
+ i.insert(cd);
+ } else {
+ i.replace(cd);
+ }
+ } catch (RuntimeException e) {
+ throw new StorageException(
+ String.format(
+ "Failed to reindex change %d in index version %d (current patch set = %d)",
+ cd.getId().get(), i.getSchema().getVersion(), cd.currentPatchSet().number()),
+ e);
}
- } catch (RuntimeException e) {
- throw new StorageException(
- String.format(
- "Failed to reindex change %d in index version %d (current patch set = %d)",
- cd.getId().get(), i.getSchema().getVersion(), cd.currentPatchSet().number()),
- e);
+ }
+ } finally {
+ if (logger.atFine().isEnabled()) {
+ long memAllocated = threadMxBean.getCurrentThreadAllocatedBytes() - memoryAtStart;
+ logger.atFine().log(
+ "Reindexing of change %d allocated %d bytes of memory.",
+ cd.getId().get(), memAllocated);
}
}
fireChangeIndexedEvent(cd.project().get(), cd.getId().get());
}
+ public boolean isChangeAlreadyIndexed(Change.Id id, Optional<ChangeIndex> newestIndex) {
+ if (newestIndex.isEmpty()) {
+ return false;
+ }
+ return newestIndex.get().get(id, IndexedChangeQuery.oneResult()).isPresent();
+ }
+
+ public Optional<ChangeIndex> getNewestIndex() {
+ return getWriteIndexes().stream().max(Comparator.comparingInt(i -> i.getSchema().getVersion()));
+ }
+
private void fireChangeScheduledForIndexingEvent(String projectName, int id) {
indexedListeners.runEach(l -> l.onChangeScheduledForIndexing(projectName, id));
}
diff --git a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index 00642a9..f7ff13c 100644
--- a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -21,6 +21,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.gerrit.entities.Change;
import com.google.gerrit.index.IndexConfig;
@@ -51,6 +52,10 @@
*/
public class IndexedChangeQuery extends IndexedQuery<Change.Id, ChangeData>
implements ChangeDataSource, Matchable<ChangeData> {
+ public static QueryOptions oneResult() {
+ IndexConfig config = IndexConfig.createDefault();
+ return createOptions(config, 0, 1, config.pageSizeMultiplier(), 1, ImmutableSet.of());
+ }
public static QueryOptions createOptions(
IndexConfig config, int start, int limit, Set<String> fields) {
diff --git a/java/com/google/gerrit/server/notedb/RepoSequence.java b/java/com/google/gerrit/server/notedb/RepoSequence.java
index 38ce3a0..bf2795d 100644
--- a/java/com/google/gerrit/server/notedb/RepoSequence.java
+++ b/java/com/google/gerrit/server/notedb/RepoSequence.java
@@ -38,6 +38,7 @@
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.git.RefUpdateUtil;
import com.google.gerrit.server.Sequence;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -81,11 +82,11 @@
}
public static class RepoSequenceModule extends FactoryModule {
- private static final String SECTION_NOTE_DB = "noteDb";
- private static final String KEY_SEQUENCE_BATCH_SIZE = "sequenceBatchSize";
- private static final int DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE = 1;
- private static final int DEFAULT_GROUPS_SEQUENCE_BATCH_SIZE = 1;
- private static final int DEFAULT_CHANGES_SEQUENCE_BATCH_SIZE = 20;
+ public static final String SECTION_NOTE_DB = "noteDb";
+ public static final String KEY_SEQUENCE_BATCH_SIZE = "sequenceBatchSize";
+ public static final int DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE = 1;
+ public static final int DEFAULT_GROUPS_SEQUENCE_BATCH_SIZE = 1;
+ public static final int DEFAULT_CHANGES_SEQUENCE_BATCH_SIZE = 20;
@Provides
@Named(NAME_ACCOUNTS)
@@ -173,41 +174,6 @@
}
}
- /** A accounts sequence provider that does not fire git reference updates. */
- public static class DisabledGitRefUpdatedRepoAccountsSequenceProvider
- implements Provider<Sequence> {
- private final GitRepositoryManager repoManager;
- private final AllUsersName allUsers;
- private final Config cfg;
-
- @Inject
- DisabledGitRefUpdatedRepoAccountsSequenceProvider(
- @GerritServerConfig Config cfg,
- GitRepositoryManager repoManager,
- AllUsersName allUsersName) {
- this.repoManager = repoManager;
- this.allUsers = allUsersName;
- this.cfg = cfg;
- }
-
- @Override
- public Sequence get() {
- int accountBatchSize =
- cfg.getInt(
- RepoSequenceModule.SECTION_NOTE_DB,
- NAME_ACCOUNTS,
- RepoSequenceModule.KEY_SEQUENCE_BATCH_SIZE,
- RepoSequenceModule.DEFAULT_ACCOUNTS_SEQUENCE_BATCH_SIZE);
- return new RepoSequence(
- repoManager,
- GitReferenceUpdated.DISABLED,
- allUsers,
- NAME_ACCOUNTS,
- () -> Sequences.FIRST_ACCOUNT_ID,
- accountBatchSize);
- }
- }
-
@VisibleForTesting
static RetryerBuilder<ImmutableList<Integer>> retryerBuilder() {
return RetryerBuilder.<ImmutableList<Integer>>newBuilder()
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
index d812eef..9df01f4 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
@@ -25,13 +25,13 @@
import com.google.gerrit.index.query.QueryProcessor;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountLimits;
import com.google.gerrit.server.account.AccountState;
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;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index 2979170..305316d 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.DynamicOptions;
import com.google.gerrit.server.DynamicOptions.DynamicBean;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.AccountLimits;
import com.google.gerrit.server.change.ChangePluginDefinedInfoFactory;
import com.google.gerrit.server.change.PluginDefinedAttributesFactories;
@@ -40,7 +41,6 @@
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 com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java b/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
index 74c8d39..e08ff1c 100644
--- a/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
@@ -26,12 +26,12 @@
import com.google.gerrit.index.query.QueryProcessor;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.AccountLimits;
import com.google.gerrit.server.account.GroupControl;
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;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index b4946c4..c9ae7d3 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -36,6 +36,7 @@
import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.AccountExternalIdCreator;
import com.google.gerrit.server.account.AccountLoader;
@@ -50,7 +51,6 @@
import com.google.gerrit.server.group.db.GroupDelta;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.ssh.SshKeyCache;
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 8c9b7a6..4da8410 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -36,6 +36,7 @@
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ReviewerSet;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.approval.ApprovalsUtil;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.NotifyResolver;
@@ -52,7 +53,6 @@
import com.google.gerrit.server.git.MergeUtilFactory;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index 86bd35f..5146a97 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -56,6 +56,7 @@
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.change.ChangeFinder;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeJson;
@@ -70,7 +71,6 @@
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.MergeUtilFactory;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 3851e82..5bf0e8b 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -46,6 +46,7 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.change.ChangeMessages;
import com.google.gerrit.server.change.ChangeResource;
@@ -56,7 +57,6 @@
import com.google.gerrit.server.git.CommitUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index b1f1da5..be2fae3 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -22,7 +22,6 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
@@ -83,6 +82,7 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
@Singleton
@@ -278,7 +278,7 @@
}
}
- Collection<ChangeData> unmergeable = unmergeableChanges(cs);
+ Collection<ChangeData> unmergeable = getUnmergeableChanges(cs);
if (unmergeable == null) {
return CLICK_FAILURE_TOOLTIP;
} else if (!unmergeable.isEmpty()) {
@@ -375,36 +375,27 @@
}
@Nullable
- public Collection<ChangeData> unmergeableChanges(ChangeSet cs) throws IOException {
- Set<ChangeData> mergeabilityMap = new HashSet<>();
- Set<ObjectId> outDatedPatchsets = new HashSet<>();
+ public Collection<ChangeData> getUnmergeableChanges(ChangeSet cs) throws IOException {
+ Set<ChangeData> unmergeableChanges = new HashSet<>();
+ Set<ObjectId> outDatedPatchSets = new HashSet<>();
for (ChangeData change : cs.changes()) {
- mergeabilityMap.add(change);
- // Add all the patchsets commit ids except the current patchset.
- outDatedPatchsets.addAll(
- change.notes().getPatchSets().values().stream()
- .map(p -> p.commitId())
- .collect(Collectors.toSet()));
- outDatedPatchsets.remove(change.currentPatchSet().commitId());
+ unmergeableChanges.add(change);
+ addAllOutdatedPatchSets(outDatedPatchSets, change);
}
-
ListMultimap<BranchNameKey, ChangeData> cbb = cs.changesByBranch();
for (BranchNameKey branch : cbb.keySet()) {
Collection<ChangeData> targetBranch = cbb.get(branch);
- HashMap<Change.Id, RevCommit> commits = findCommits(targetBranch, branch.project());
-
- Set<ObjectId> allParents = Sets.newHashSetWithExpectedSize(cs.size());
- for (RevCommit commit : commits.values()) {
- for (RevCommit parent : commit.getParents()) {
- allParents.add(parent.getId());
- }
- }
+ HashMap<Change.Id, RevCommit> commits = mapToCommits(targetBranch, branch.project());
+ Set<ObjectId> allParents =
+ commits.values().stream()
+ .flatMap(c -> Arrays.stream(c.getParents()))
+ .map(RevObject::getId)
+ .collect(Collectors.toSet());
for (ChangeData change : targetBranch) {
-
RevCommit commit = commits.get(change.getId());
boolean isMergeCommit = commit.getParentCount() > 1;
boolean isLastInChain = !allParents.contains(commit.getId());
- if (Arrays.stream(commit.getParents()).anyMatch(c -> outDatedPatchsets.contains(c.getId()))
+ if (Arrays.stream(commit.getParents()).anyMatch(c -> outDatedPatchSets.contains(c.getId()))
&& !isCherryPickSubmit(change)) {
// Found a parent that depends on an outdated patchset and the submit strategy is not
// cherry-pick.
@@ -421,18 +412,26 @@
return null;
}
if (mergeable) {
- mergeabilityMap.remove(change);
+ unmergeableChanges.remove(change);
}
-
if (isLastInChain && isMergeCommit && mergeable) {
- for (ChangeData c : targetBranch) {
- mergeabilityMap.remove(c);
- }
+ targetBranch.stream().forEach(unmergeableChanges::remove);
break;
}
}
}
- return mergeabilityMap;
+ return unmergeableChanges;
+ }
+
+ /**
+ * Add all outdated patch-sets (non-last patch-sets) to the output set {@code outdatedPatchSets}.
+ */
+ private static void addAllOutdatedPatchSets(Set<ObjectId> outdatedPatchSets, ChangeData cd) {
+ outdatedPatchSets.addAll(
+ cd.notes().getPatchSets().values().stream()
+ .map(p -> p.commitId())
+ .collect(Collectors.toSet()));
+ outdatedPatchSets.remove(cd.currentPatchSet().commitId());
}
private boolean isCherryPickSubmit(ChangeData changeData) {
@@ -440,7 +439,8 @@
return submitTypeRecord.isOk() && submitTypeRecord.type == SubmitType.CHERRY_PICK;
}
- private HashMap<Change.Id, RevCommit> findCommits(
+ /** Map input {@code changes} to the commit SHA-1 of their latest patch-set. */
+ private HashMap<Change.Id, RevCommit> mapToCommits(
Collection<ChangeData> changes, Project.NameKey project) throws IOException {
HashMap<Change.Id, RevCommit> commits = new HashMap<>();
try (Repository repo = repoManager.openRepository(project);
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index 9d36aaa..4110eff 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -40,6 +40,7 @@
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.UserInitiated;
import com.google.gerrit.server.account.CreateGroupArgs;
import com.google.gerrit.server.account.GroupCache;
@@ -52,7 +53,6 @@
import com.google.gerrit.server.group.db.GroupDelta;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.group.db.InternalGroupCreation;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.util.time.TimeUtil;
diff --git a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
index 458ae4d..338ff0d 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
@@ -33,11 +33,11 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.approval.ApprovalsUtil;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
diff --git a/java/com/google/gerrit/server/schema/AllProjectsInput.java b/java/com/google/gerrit/server/schema/AllProjectsInput.java
index a079050..cfb9754 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsInput.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsInput.java
@@ -24,7 +24,7 @@
import com.google.gerrit.entities.LabelValue;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.Sequences;
import java.util.Optional;
@AutoValue
diff --git a/java/com/google/gerrit/sshd/commands/SequenceSetCommand.java b/java/com/google/gerrit/sshd/commands/SequenceSetCommand.java
index 197d61c..3ec34bc 100644
--- a/java/com/google/gerrit/sshd/commands/SequenceSetCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SequenceSetCommand.java
@@ -16,7 +16,7 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/sshd/commands/SequenceShowCommand.java b/java/com/google/gerrit/sshd/commands/SequenceShowCommand.java
index 490c7ca..e9058b4 100644
--- a/java/com/google/gerrit/sshd/commands/SequenceShowCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SequenceShowCommand.java
@@ -16,7 +16,7 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 5af87e8..a1c7a00 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -133,6 +133,7 @@
import com.google.gerrit.httpd.CacheBasedWebSession;
import com.google.gerrit.server.ExceptionHook;
import com.google.gerrit.server.Sequence;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountProperties;
@@ -155,7 +156,6 @@
import com.google.gerrit.server.group.testing.TestGroupBackend;
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.index.account.StalenessChecker;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.query.account.InternalAccountQuery;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
index 091d444..ee2db28 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountManagerIT.java
@@ -30,6 +30,7 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
@@ -45,7 +46,6 @@
import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdNotes;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.db.GroupsUpdate;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.inject.Inject;
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 6dbbe9a..db12e85 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -84,6 +84,7 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupIncludeCache;
@@ -98,7 +99,6 @@
import com.google.gerrit.server.group.db.InternalGroupCreation;
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.gerrit.server.index.group.StalenessChecker;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.GerritJUnit.ThrowingRunnable;
diff --git a/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java b/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
index 27962da..d48f41d 100644
--- a/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
@@ -40,11 +40,11 @@
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountProperties;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.ProjectWatches;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
diff --git a/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
index 1143e89..0393f2b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/RestApiServletIT.java
@@ -436,6 +436,32 @@
}
@Test
+ public void testCommentLinkWithPrefixRedirects() throws Exception {
+ int changeNumber = createChange().getChange().getId().get();
+ String commentId = "ff3303fd_8341647b";
+
+ String redirectUri =
+ String.format("/c/%s/+/%d/comment/%s", project.get(), changeNumber, commentId);
+
+ anonymousRestSession
+ .get(String.format("/c/%s/comment/%s", changeNumber, commentId))
+ .assertTemporaryRedirect(redirectUri);
+ }
+
+ @Test
+ public void testCommentLinkWithoutPrefixRedirects() throws Exception {
+ int changeNumber = createChange().getChange().getId().get();
+ String commentId = "ff3303fd_8341647b";
+
+ String redirectUri =
+ String.format("/c/%s/+/%d/comment/%s", project.get(), changeNumber, commentId);
+
+ anonymousRestSession
+ .get(String.format("/%s/comment/%s", changeNumber, commentId))
+ .assertTemporaryRedirect(redirectUri);
+ }
+
+ @Test
public void testNumericChangeIdRedirectWithoutPrefix() throws Exception {
int changeNumber = createChange().getChange().getId().get();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
index 0a9a098..fe220f2 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
@@ -310,7 +310,7 @@
mergeSuperSet
.get()
.completeChangeSet(change.change(), user(admin), /* includingTopicClosure= */ false);
- assertThat(submit.unmergeableChanges(cs).isEmpty()).isEqualTo(expected);
+ assertThat(submit.getUnmergeableChanges(cs).isEmpty()).isEqualTo(expected);
}
private void assertMergeable(ChangeData change) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java b/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
index 35ecceb..b150491 100644
--- a/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/account/AccountResolverIT.java
@@ -31,6 +31,7 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.common.AccountVisibility;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountResolver;
@@ -39,7 +40,6 @@
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
import com.google.inject.Provider;
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index 55f102f..07e4866 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -38,13 +38,13 @@
import com.google.gerrit.extensions.common.ProblemInfo;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ConsistencyChecker;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java
index f4dc798..4529f72 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/ExternalIdNotesUpsertPreprocessorIT.java
@@ -23,6 +23,7 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.externalids.ExternalId;
@@ -30,7 +31,6 @@
import com.google.gerrit.server.account.externalids.ExternalIdUpsertPreprocessor;
import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdNotes;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java
index 48fd38c..4241511 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImplTest.java
@@ -28,7 +28,7 @@
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.Sequences;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.concurrent.atomic.AtomicInteger;
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index c90f5d4..c7b1299 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -107,6 +107,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountManager;
@@ -132,7 +133,6 @@
import com.google.gerrit.server.index.change.IndexedChangeQuery;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.schema.SchemaCreator;
diff --git a/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java b/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
index e6a6497..6c79c43 100644
--- a/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
+++ b/javatests/com/google/gerrit/server/schema/AllProjectsCreatorTest.java
@@ -30,11 +30,11 @@
import com.google.gerrit.entities.LabelValue;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.GroupUuid;
import com.google.gerrit.server.account.ServiceUserClassifier;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.testing.InMemoryModule;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
diff --git a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
index ff1f6a3..f7a2afa 100644
--- a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -40,6 +40,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.change.AbandonOp;
@@ -50,7 +51,6 @@
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.patch.DiffSummary;
import com.google.gerrit.server.patch.DiffSummaryKey;
import com.google.gerrit.server.update.context.RefUpdateContext;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index 7a4caa7..3b1a824 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -546,7 +546,9 @@
allowConflicts: !!this.rebaseAllowConflicts?.checked,
rebaseChain: !!this.rebaseChain?.checked,
onBehalfOfUploader: this.rebaseOnBehalfOfUploader(),
- committerEmail: this.selectedEmailForRebase || null,
+ committerEmail: this.rebaseChain?.checked
+ ? null
+ : this.selectedEmailForRebase || null,
};
fireNoBubbleNoCompose(this, 'confirm-rebase', detail);
this.text = '';
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
index e6326b3..038fcd5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
@@ -389,6 +389,49 @@
);
});
+ test('committer email is sent when chain is not rebased', async () => {
+ const fireStub = sinon.stub(element, 'dispatchEvent');
+ element.text = '123';
+ element.selectedEmailForRebase = 'abc@def.com';
+ await element.updateComplete;
+ queryAndAssert(element, '#confirmDialog').dispatchEvent(
+ new CustomEvent('confirm', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.deepEqual((fireStub.lastCall.args[0] as CustomEvent).detail, {
+ allowConflicts: false,
+ base: '123',
+ rebaseChain: false,
+ onBehalfOfUploader: true,
+ committerEmail: 'abc@def.com',
+ });
+ });
+
+ test('committer email is not sent when chain is rebased', async () => {
+ const fireStub = sinon.stub(element, 'dispatchEvent');
+ element.text = '123';
+ element.selectedEmailForRebase = 'abc@def.com';
+ element.hasParent = true;
+ element.shouldRebaseChain = true;
+ await element.updateComplete;
+ queryAndAssert<HTMLInputElement>(element, '#rebaseChain').checked = true;
+ queryAndAssert(element, '#confirmDialog').dispatchEvent(
+ new CustomEvent('confirm', {
+ composed: true,
+ bubbles: true,
+ })
+ );
+ assert.deepEqual((fireStub.lastCall.args[0] as CustomEvent).detail, {
+ allowConflicts: false,
+ base: '123',
+ rebaseChain: true,
+ onBehalfOfUploader: true,
+ committerEmail: null,
+ });
+ });
+
test('input cleared on cancel or submit', async () => {
element.text = '123';
await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
index ceccdda..5131083 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
@@ -209,7 +209,16 @@
) {
// Figure out what the revert title should be.
const originalTitle = (commitMessage || '').split('\n')[0];
- const revertTitle = `Revert "${originalTitle}"`;
+ let revertTitle = `Revert "${originalTitle}"`;
+ const match = originalTitle.match(/^Revert(?:\^([0-9]+))? "(.*)"$/);
+ if (match) {
+ let revertNum = 2;
+ if (match[1]) {
+ revertNum = Number(match[1]) + 1;
+ }
+ revertTitle = `Revert^${revertNum} "${match[2]}"`;
+ }
+
if (!commitHash) {
fireAlert(this, ERR_COMMIT_NOT_FOUND);
return;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
index 8d71e15..920ff00 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.ts
@@ -105,7 +105,21 @@
'abcd123' as CommitId
);
const expected =
- 'Revert "Revert "one line commit""\n\n' +
+ 'Revert^2 "one line commit"\n\n' +
+ 'This reverts commit abcd123.\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>\n';
+ assert.equal(element.message, expected);
+ });
+
+ test('revert a revert of a revert', () => {
+ assert.isNotOk(element.message);
+ element.populateRevertSingleChangeMessage(
+ createParsedChange(),
+ 'Revert^2 "one line commit"\n\nChange-Id: abcdefg\n',
+ 'abcd123' as CommitId
+ );
+ const expected =
+ 'Revert^3 "one line commit"\n\n' +
'This reverts commit abcd123.\n\n' +
'Reason for revert: <INSERT REASONING HERE>\n';
assert.equal(element.message, expected);
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index b58f2fa..a0ad2f0 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -130,6 +130,7 @@
import {GrReviewerUpdatesParser} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser';
import {formStyles} from '../../../styles/form-styles';
import {navigationToken} from '../../core/gr-navigation/gr-navigation';
+import {getDocUrl} from '../../../utils/url-util';
export enum FocusTarget {
ANY = 'any',
@@ -208,6 +209,8 @@
@state() serverConfig?: ServerInfo;
+ @state() private docsBaseUrl = '';
+
@state()
patchsetLevelDraftMessage = '';
@@ -618,6 +621,11 @@
);
subscribe(
this,
+ () => this.getConfigModel().docsBaseUrl$,
+ docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
+ );
+ subscribe(
+ this,
() => this.getChangeModel().change$,
x => (this.change = x)
);
@@ -1009,7 +1017,7 @@
<div>
${this.renderModifyAttentionSetButton()}
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
+ href=${getDocUrl(this.docsBaseUrl, 'user-attention-set.html')}
target="_blank"
rel="noopener noreferrer"
>
@@ -1057,7 +1065,7 @@
<div>
${this.renderModifyAttentionSetButton()}
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
+ href=${getDocUrl(this.docsBaseUrl, 'user-attention-set.html')}
target="_blank"
rel="noopener noreferrer"
>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index f7e267c..500aa63 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -276,7 +276,7 @@
</div>
</gr-button>
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
+ href="/Documentation/user-attention-set.html"
target="_blank"
rel="noopener noreferrer"
>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index a55173c..34950b0 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -8,7 +8,7 @@
import '../../shared/gr-icon/gr-icon';
import '../gr-account-dropdown/gr-account-dropdown';
import '../gr-smart-search/gr-smart-search';
-import {getBaseUrl} from '../../../utils/url-util';
+import {getBaseUrl, getDocUrl} from '../../../utils/url-util';
import {getAdminLinks, NavLink} from '../../../models/views/admin';
import {
AccountDetailInfo,
@@ -85,6 +85,18 @@
},
];
+// visible for testing
+export function getDocLinks(docBaseUrl: string, docLinks: MainHeaderLink[]) {
+ if (!docBaseUrl) return [];
+ return docLinks.map(link => {
+ return {
+ url: getDocUrl(docBaseUrl, link.url),
+ name: link.name,
+ target: '_blank',
+ };
+ });
+}
+
// Set of authentication methods that can provide custom registration page.
const AUTH_TYPES_WITH_REGISTER_URL: Set<AuthType> = new Set([
AuthType.LDAP,
@@ -121,7 +133,7 @@
@state() private adminLinks: NavLink[] = [];
- @state() private docBaseUrl: string | null = null;
+ @state() private docsBaseUrl = '';
@state() private userLinks: MainHeaderLink[] = [];
@@ -165,7 +177,7 @@
subscribe(
this,
() => this.getConfigModel().docsBaseUrl$,
- docsBaseUrl => (this.docBaseUrl = docsBaseUrl)
+ docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
);
subscribe(
this,
@@ -359,12 +371,9 @@
</gr-endpoint-decorator>
</a>
<ul class="links">
- ${this.computeLinks(
- this.userLinks,
- this.adminLinks,
- this.topMenus,
- this.docBaseUrl
- ).map(linkGroup => this.renderLinkGroup(linkGroup))}
+ ${this.computeLinks(this.userLinks, this.adminLinks, this.topMenus).map(
+ linkGroup => this.renderLinkGroup(linkGroup)
+ )}
</ul>
<div class="rightItems">
<gr-endpoint-decorator
@@ -488,15 +497,13 @@
userLinks?: MainHeaderLink[],
adminLinks?: NavLink[],
topMenus?: TopMenuEntryInfo[],
- docBaseUrl?: string | null,
// defaultLinks parameter is used in tests only
defaultLinks = DEFAULT_LINKS
) {
if (
userLinks === undefined ||
adminLinks === undefined ||
- topMenus === undefined ||
- docBaseUrl === undefined
+ topMenus === undefined
) {
return [];
}
@@ -513,7 +520,7 @@
links: userLinks.slice(),
});
}
- const docLinks = this.getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
+ const docLinks = getDocLinks(this.docsBaseUrl, DOCUMENTATION_LINKS);
if (docLinks.length) {
links.push({
title: 'Documentation',
@@ -550,24 +557,6 @@
}
// private but used in test
- getDocLinks(docBaseUrl: string | null, docLinks: MainHeaderLink[]) {
- if (!docBaseUrl) {
- return [];
- }
- return docLinks.map(link => {
- let url = docBaseUrl;
- if (url && url[url.length - 1] === '/') {
- url = url.substring(0, url.length - 1);
- }
- return {
- url: url + link.url,
- name: link.name,
- target: '_blank',
- };
- });
- }
-
- // private but used in test
loadAccount() {
this.loading = true;
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
index 4b9c313..dfb44b70 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
@@ -11,7 +11,7 @@
stubRestApi,
} from '../../../test/test-utils';
import './gr-main-header';
-import {GrMainHeader} from './gr-main-header';
+import {GrMainHeader, getDocLinks} from './gr-main-header';
import {
createAccountDetailWithId,
createGerritInfo,
@@ -51,6 +51,11 @@
<span class="linksTitle" id="Changes"> Changes </span>
</gr-dropdown>
</li>
+ <li class="hideOnMobile">
+ <gr-dropdown down-arrow="" horizontal-align="left" link="">
+ <span class="linksTitle" id="Documentation">Documentation</span>
+ </gr-dropdown>
+ </li>
<li>
<gr-dropdown down-arrow="" horizontal-align="left" link="">
<span class="linksTitle" id="Browse"> Browse </span>
@@ -168,36 +173,24 @@
// When no admin links are passed, it should use the default.
assert.deepEqual(
- element.computeLinks(
- /* userLinks= */ [],
- adminLinks,
- /* topMenus= */ [],
- /* docBaseUrl= */ '',
- defaultLinks
- ),
- defaultLinks.concat({
- title: 'Browse',
- links: adminLinks,
- })
+ element
+ .computeLinks(
+ /* userLinks= */ [],
+ adminLinks,
+ /* topMenus= */ [],
+ defaultLinks
+ )
+ .find(i => i.title === 'Faves'),
+ defaultLinks[0]
);
assert.deepEqual(
- element.computeLinks(
- userLinks,
- adminLinks,
- /* topMenus= */ [],
- /* docBaseUrl= */ '',
- defaultLinks
- ),
- defaultLinks.concat([
- {
- title: 'Your',
- links: userLinks,
- },
- {
- title: 'Browse',
- links: adminLinks,
- },
- ])
+ element
+ .computeLinks(userLinks, adminLinks, /* topMenus= */ [], defaultLinks)
+ .find(i => i.title === 'Your'),
+ {
+ title: 'Your',
+ links: userLinks,
+ }
);
});
@@ -209,11 +202,10 @@
},
];
- assert.deepEqual(element.getDocLinks(null, docLinks), []);
- assert.deepEqual(element.getDocLinks('', docLinks), []);
- assert.deepEqual(element.getDocLinks('base', []), []);
+ assert.deepEqual(getDocLinks('', docLinks), []);
+ assert.deepEqual(getDocLinks('base', []), []);
- assert.deepEqual(element.getDocLinks('base', docLinks), [
+ assert.deepEqual(getDocLinks('base', docLinks), [
{
name: 'Table of Contents',
target: '_blank',
@@ -221,7 +213,7 @@
},
]);
- assert.deepEqual(element.getDocLinks('base/', docLinks), [
+ assert.deepEqual(getDocLinks('base/', docLinks), [
{
name: 'Table of Contents',
target: '_blank',
@@ -255,24 +247,17 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
/* defaultLinks= */ []
- ),
- [
- {
- title: 'Browse',
- links: adminLinks,
- },
- {
- title: 'Plugins',
- links: [
- {
- name: 'Manage',
- url: 'https://gerrit/plugins/plugin-manager/static/index.html',
- },
- ],
- },
- ]
+ )[2],
+ {
+ title: 'Plugins',
+ links: [
+ {
+ name: 'Manage',
+ url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+ },
+ ],
+ }
);
});
@@ -306,24 +291,17 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
/* defaultLinks= */ []
- ),
- [
- {
- title: 'Browse',
- links: adminLinks,
- },
- {
- title: 'Projects',
- links: [
- {
- name: 'Project List',
- url: '/plugins/myplugin/index.html',
- },
- ],
- },
- ]
+ )[2],
+ {
+ title: 'Projects',
+ links: [
+ {
+ name: 'Project List',
+ url: '/plugins/myplugin/index.html',
+ },
+ ],
+ }
);
});
@@ -362,28 +340,21 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
/* defaultLinks= */ []
- ),
- [
- {
- title: 'Browse',
- links: adminLinks,
- },
- {
- title: 'Plugins',
- links: [
- {
- name: 'Manage',
- url: 'https://gerrit/plugins/plugin-manager/static/index.html',
- },
- {
- name: 'Create',
- url: 'https://gerrit/plugins/plugin-manager/static/create.html',
- },
- ],
- },
- ]
+ )[2],
+ {
+ title: 'Plugins',
+ links: [
+ {
+ name: 'Manage',
+ url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+ },
+ {
+ name: 'Create',
+ url: 'https://gerrit/plugins/plugin-manager/static/create.html',
+ },
+ ],
+ }
);
});
@@ -416,24 +387,17 @@
/* userLinks= */ [],
/* adminLinks= */ [],
topMenus,
- /* baseDocUrl= */ '',
defaultLinks
- ),
- [
- {
- title: 'Faves',
- links: defaultLinks[0].links.concat([
- {
- name: 'Manage',
- url: 'https://gerrit/plugins/plugin-manager/static/index.html',
- },
- ]),
- },
- {
- title: 'Browse',
- links: [],
- },
- ]
+ )[0],
+ {
+ title: 'Faves',
+ links: defaultLinks[0].links.concat([
+ {
+ name: 'Manage',
+ url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+ },
+ ]),
+ }
);
});
@@ -462,29 +426,22 @@
userLinks,
/* adminLinks= */ [],
topMenus,
- /* baseDocUrl= */ '',
/* defaultLinks= */ []
- ),
- [
- {
- title: 'Your',
- links: [
- {
- name: 'Facebook',
- url: 'https://facebook.com',
- target: '',
- },
- {
- name: 'Manage',
- url: 'https://gerrit/plugins/plugin-manager/static/index.html',
- },
- ],
- },
- {
- title: 'Browse',
- links: [],
- },
- ]
+ )[0],
+ {
+ title: 'Your',
+ links: [
+ {
+ name: 'Facebook',
+ url: 'https://facebook.com',
+ target: '',
+ },
+ {
+ name: 'Manage',
+ url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+ },
+ ],
+ }
);
});
@@ -513,21 +470,18 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
/* defaultLinks= */ []
- ),
- [
- {
- title: 'Browse',
- links: [
- adminLinks[0],
- {
- name: 'Manage',
- url: 'https://gerrit/plugins/plugin-manager/static/index.html',
- },
- ],
- },
- ]
+ )[1],
+ {
+ title: 'Browse',
+ links: [
+ adminLinks[0],
+ {
+ name: 'Manage',
+ url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+ },
+ ],
+ }
);
});
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index a0187f9..98e9eba 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -30,6 +30,7 @@
ValueChangedEvent,
} from '../../../types/events';
import {fireNoBubbleNoCompose} from '../../../utils/event-util';
+import {getDocUrl} from '../../../utils/url-util';
// Possible static search options for auto complete, without negations.
const SEARCH_OPERATORS: ReadonlyArray<string> = [
@@ -168,7 +169,7 @@
@state() inputVal = '';
// private but used in test
- @state() docsBaseUrl: string | null = null;
+ @state() docsBaseUrl = '';
@state() private query: AutocompleteQuery;
@@ -240,7 +241,7 @@
<a
class="help"
slot="suffix"
- href=${this.computeHelpDocLink()}
+ href=${getDocUrl(this.docsBaseUrl, 'user-search.html')}
target="_blank"
rel="noopener noreferrer"
tabindex="-1"
@@ -276,18 +277,6 @@
return set;
}
- // private but used in test
- computeHelpDocLink() {
- // fallback to gerrit's official doc
- let baseUrl =
- this.docsBaseUrl ||
- 'https://gerrit-review.googlesource.com/Documentation/';
- if (baseUrl.endsWith('/')) {
- baseUrl = baseUrl.substring(0, baseUrl.length - 1);
- }
- return `${baseUrl}/user-search.html`;
- }
-
private handleInputCommit(e: AutocompleteCommitEvent) {
this.preventDefaultAndNavigateToInputVal(e);
}
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
index 2f955de..f67024f 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.ts
@@ -91,23 +91,6 @@
);
});
- test('falls back to gerrit docs url', async () => {
- const configWithoutDocsUrl = createServerInfo();
- configWithoutDocsUrl.gerrit.doc_url = undefined;
-
- configModel.updateServerConfig(configWithoutDocsUrl);
- await waitUntilObserved(
- configModel.docsBaseUrl$,
- docsBaseUrl => docsBaseUrl === 'https://mydocumentationurl.google.com/'
- );
- await element.updateComplete;
-
- assert.equal(
- queryAndAssert<HTMLAnchorElement>(element, 'a')!.href,
- 'https://mydocumentationurl.google.com/user-search.html'
- );
- });
-
test('value is propagated to inputVal', async () => {
element.value = 'foo';
await element.updateComplete;
@@ -303,29 +286,4 @@
});
});
});
-
- suite('doc url', () => {
- setup(async () => {
- element = await fixture(html`<gr-search-bar></gr-search-bar>`);
- });
-
- test('compute help doc url with correct path', async () => {
- element.docsBaseUrl = 'https://doc.com/';
- await element.updateComplete;
- assert.equal(
- element.computeHelpDocLink(),
- 'https://doc.com/user-search.html'
- );
- });
-
- test('compute help doc url fallback to gerrit url', async () => {
- element.docsBaseUrl = null;
- await element.updateComplete;
- assert.equal(
- element.computeHelpDocLink(),
- 'https://gerrit-review.googlesource.com/Documentation/' +
- 'user-search.html'
- );
- });
- });
});
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
index da6918b..e25b738 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
@@ -23,6 +23,10 @@
import {when} from 'lit/directives/when.js';
import {BindValueChangeEvent, ValueChangedEvent} from '../../../types/events';
import {formStyles} from '../../../styles/form-styles';
+import {getDocUrl} from '../../../utils/url-util';
+import {subscribe} from '../../lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
+import {configModelToken} from '../../../models/config/config-model';
@customElement('gr-account-info')
export class GrAccountInfo extends LitElement {
@@ -59,8 +63,12 @@
@state() private avatarChangeUrl = '';
+ @state() private docsBaseUrl = '';
+
private readonly restApiService = getAppContext().restApiService;
+ private readonly getConfigModel = resolve(this, configModelToken);
+
static override get styles() {
return [
sharedStyles,
@@ -100,6 +108,15 @@
];
}
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () => this.getConfigModel().docsBaseUrl$,
+ docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
+ );
+ }
+
override render() {
if (!this.account || this.loading) return nothing;
return html`<div class="gr-form-styles">
@@ -107,8 +124,7 @@
All profile fields below may be publicly displayed to others, including
on changes you are associated with, as well as in search and
autocompletion.
- <a
- href="https://gerrit-review.googlesource.com/Documentation/user-privacy.html"
+ <a href=${getDocUrl(this.docsBaseUrl, 'user-privacy.html')}
>Learn more</a
>
</p>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 7f1595d..4d4834e 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -69,15 +69,9 @@
} from '../../../models/user/user-model';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {navigationToken} from '../../core/gr-navigation/gr-navigation';
-import {rootUrl} from '../../../utils/url-util';
+import {getDocUrl, rootUrl} from '../../../utils/url-util';
import {configModelToken} from '../../../models/config/config-model';
-const GERRIT_DOCS_BASE_URL =
- 'https://gerrit-review.googlesource.com/' + 'Documentation';
-const GERRIT_DOCS_FILTER_PATH = '/user-notify.html';
-const ABSOLUTE_URL_PATTERN = /^https?:/;
-const TRAILING_SLASH_PATTERN = /\/$/;
-
const HTTP_AUTH = ['HTTP', 'HTTP_LDAP'];
enum CopyPrefsDirection {
@@ -188,9 +182,6 @@
// private but used in test
@state() serverConfig?: ServerInfo;
- // private but used in test
- @state() docsBaseUrl?: string | null;
-
@state() private emailsChanged = false;
// private but used in test
@@ -203,6 +194,8 @@
@state() isDeletingAccount = false;
+ @state() private docsBaseUrl = '';
+
// private but used in test
public _testOnly_loadingPromise?: Promise<void>;
@@ -210,8 +203,6 @@
private readonly getUserModel = resolve(this, userModelToken);
- private readonly getConfigModel = resolve(this, configModelToken);
-
// private but used in test
readonly flagsService = getAppContext().flagsService;
@@ -219,6 +210,8 @@
private readonly getNavigation = resolve(this, navigationToken);
+ private readonly getConfigModel = resolve(this, configModelToken);
+
constructor() {
super();
subscribe(
@@ -238,11 +231,6 @@
);
subscribe(
this,
- () => this.getConfigModel().docsBaseUrl$,
- docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
- );
- subscribe(
- this,
() => this.getUserModel().preferences$,
prefs => {
if (!prefs) {
@@ -255,6 +243,11 @@
this.localChangeTableColumns = changeTablePrefs(prefs);
}
);
+ subscribe(
+ this,
+ () => this.getConfigModel().docsBaseUrl$,
+ docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
+ );
}
// private, but used in tests
@@ -848,7 +841,10 @@
>Allow browser notifications</label
>
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html#_browser_notifications"
+ href=${getDocUrl(
+ this.docsBaseUrl,
+ 'user-attention-set.html#_browser_notifications'
+ )}
target="_blank"
rel="noopener noreferrer"
>
@@ -1171,19 +1167,6 @@
}
// private but used in test
- getFilterDocsLink(docsBaseUrl?: string | null) {
- let base = docsBaseUrl;
- if (!base || !ABSOLUTE_URL_PATTERN.test(base)) {
- base = GERRIT_DOCS_BASE_URL;
- }
-
- // Remove any trailing slash, since it is in the GERRIT_DOCS_FILTER_PATH.
- base = base.replace(TRAILING_SLASH_PATTERN, '');
-
- return base + GERRIT_DOCS_FILTER_PATH;
- }
-
- // private but used in test
showHttpAuth() {
if (this.serverConfig?.auth?.git_basic_auth_policy) {
return HTTP_AUTH.includes(
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
index 30a2922..f9b1738 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.ts
@@ -121,10 +121,6 @@
});
test('renders', async () => {
- sinon
- .stub(element, 'getFilterDocsLink')
- .returns('https://test.com/user-notify.html');
- element.docsBaseUrl = 'https://test.com';
await element.updateComplete;
// this cannot be formatted with /* HTML */, because it breaks test
assert.shadowDom.equal(
@@ -473,7 +469,7 @@
Allow browser notifications
</label>
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html#_browser_notifications"
+ href="/Documentation/user-attention-set.html#_browser_notifications"
target="_blank"
rel="noopener noreferrer"
>
@@ -777,45 +773,6 @@
assert.isFalse(element.showHttpAuth());
});
- suite('getFilterDocsLink', () => {
- test('with http: docs base URL', () => {
- const base = 'http://example.com/';
- const result = element.getFilterDocsLink(base);
- assert.equal(result, 'http://example.com/user-notify.html');
- });
-
- test('with http: docs base URL without slash', () => {
- const base = 'http://example.com';
- const result = element.getFilterDocsLink(base);
- assert.equal(result, 'http://example.com/user-notify.html');
- });
-
- test('with https: docs base URL', () => {
- const base = 'https://example.com/';
- const result = element.getFilterDocsLink(base);
- assert.equal(result, 'https://example.com/user-notify.html');
- });
-
- test('without docs base URL', () => {
- const result = element.getFilterDocsLink(null);
- assert.equal(
- result,
- 'https://gerrit-review.googlesource.com/' +
- 'Documentation/user-notify.html'
- );
- });
-
- test('ignores non HTTP links', () => {
- const base = 'javascript://alert("evil");';
- const result = element.getFilterDocsLink(base);
- assert.equal(
- result,
- 'https://gerrit-review.googlesource.com/' +
- 'Documentation/user-notify.html'
- );
- });
- });
-
suite('when email verification token is provided', () => {
let resolveConfirm: (
value: string | PromiseLike<string | null> | null
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
index c65a1fe..01e8a87 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
@@ -48,6 +48,7 @@
} from '../../../models/views/dashboard';
import {fire, fireReload} from '../../../utils/event-util';
import {userModelToken} from '../../../models/user/user-model';
+import {getDocUrl} from '../../../utils/url-util';
@customElement('gr-hovercard-account-contents')
export class GrHovercardAccountContents extends LitElement {
@@ -76,6 +77,8 @@
@state()
serverConfig?: ServerInfo;
+ @state() private docsBaseUrl = '';
+
private readonly restApiService = getAppContext().restApiService;
private readonly reporting = getAppContext().reportingService;
@@ -98,6 +101,11 @@
this.serverConfig = config;
}
);
+ subscribe(
+ this,
+ () => this.getConfigModel().docsBaseUrl$,
+ docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
+ );
}
static override get styles() {
@@ -310,7 +318,7 @@
></gr-icon>
<span> ${this.computePronoun()} turn to take action. </span>
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-attention-set.html"
+ href=${getDocUrl(this.docsBaseUrl, 'user-attention-set.html')}
target="_blank"
rel="noopener noreferrer"
>
diff --git a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
index f0e41b2..6322123 100644
--- a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
+++ b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
@@ -8,8 +8,12 @@
import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
import '../gr-suggestion-diff-preview/gr-suggestion-diff-preview';
import {css, html, LitElement, nothing} from 'lit';
-import {customElement} from 'lit/decorators.js';
+import {customElement, state} from 'lit/decorators.js';
import {fire} from '../../../utils/event-util';
+import {getDocUrl} from '../../../utils/url-util';
+import {subscribe} from '../../lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
+import {configModelToken} from '../../../models/config/config-model';
declare global {
interface HTMLElementEventMap {
@@ -25,8 +29,17 @@
@customElement('gr-user-suggestion-fix')
export class GrUserSuggestionsFix extends LitElement {
+ @state() private docsBaseUrl = '';
+
+ private readonly getConfigModel = resolve(this, configModelToken);
+
constructor() {
super();
+ subscribe(
+ this,
+ () => this.getConfigModel().docsBaseUrl$,
+ docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
+ );
}
static override get styles() {
@@ -58,7 +71,7 @@
<div class="title">
<span>Suggested edit</span>
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-suggest-edits.html"
+ href=${getDocUrl(this.docsBaseUrl, 'user-suggest-edits.html')}
target="_blank"
rel="noopener noreferrer"
><gr-icon icon="help" title="read documentation"></gr-icon
diff --git a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix_test.ts b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix_test.ts
index 91e9162..b7d73b3 100644
--- a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix_test.ts
@@ -44,7 +44,7 @@
<div class="title">
<span>Suggested edit</span>
<a
- href="https://gerrit-review.googlesource.com/Documentation/user-suggest-edits.html"
+ href="/Documentation/user-suggest-edits.html"
rel="noopener noreferrer"
target="_blank"
><gr-icon icon="help" title="read documentation"></gr-icon
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts
index f2f85af..00a1d65 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts
@@ -13,9 +13,6 @@
:host(.no-left) .sideBySide tr:not(.dividerRow) td:nth-child(-n + 4) {
display: none;
}
- :host(.disable-context-control-buttons) {
- --context-control-display: none;
- }
:host(.disable-context-control-buttons) .section {
border-right: none;
}
@@ -376,7 +373,7 @@
/* Context controls */
.contextControl {
- display: var(--context-control-display, table-row-group);
+ display: table-row-group;
background-color: transparent;
border: none;
--divider-height: var(--spacing-s);
@@ -405,6 +402,16 @@
height: calc(var(--line-height-normal) + var(--spacing-s));
}
+ /* Hide the actual context control buttons */
+ :host(.disable-context-control-buttons) .contextControl gr-context-controls {
+ display: none;
+ }
+ /* Maintain a small amount of padding at the edges of diff chunks */
+ :host(.disable-context-control-buttons) .contextControl .contextBackground {
+ height: var(--spacing-s);
+ border-right: none;
+ }
+
.dividerCell {
vertical-align: top;
}
diff --git a/polygerrit-ui/app/models/config/config-model.ts b/polygerrit-ui/app/models/config/config-model.ts
index 66ee2e8..dd7828b6 100644
--- a/polygerrit-ui/app/models/config/config-model.ts
+++ b/polygerrit-ui/app/models/config/config-model.ts
@@ -11,7 +11,10 @@
import {select} from '../../utils/observable-util';
import {Model} from '../base/model';
import {define} from '../dependency';
-import {loginUrl} from '../../utils/url-util';
+import {getBaseUrl, loginUrl} from '../../utils/url-util';
+
+export const PROBE_PATH = '/Documentation/index.html';
+export const DOCS_BASE_PATH = '/Documentation';
export interface ConfigState {
repoConfig?: ConfigInfo;
@@ -56,9 +59,7 @@
public docsBaseUrl$ = select(
this.serverConfig$.pipe(
- switchMap(serverConfig =>
- from(this.restApiService.getDocsBaseUrl(serverConfig))
- )
+ switchMap(serverConfig => from(this.getDocsBaseUrl(serverConfig)))
),
url => url
);
@@ -86,6 +87,16 @@
}
// visible for testing
+ async getDocsBaseUrl(config: ServerInfo | undefined): Promise<string> {
+ if (config?.gerrit?.doc_url) return config.gerrit.doc_url;
+
+ const ok = await this.restApiService.probePath(getBaseUrl() + PROBE_PATH);
+ if (ok) return getBaseUrl() + DOCS_BASE_PATH;
+
+ return 'https://gerrit-review.googlesource.com/Documentation';
+ }
+
+ // visible for testing
updateRepoConfig(repoConfig?: ConfigInfo) {
this.updateState({repoConfig});
}
diff --git a/polygerrit-ui/app/models/config/config-model_test.ts b/polygerrit-ui/app/models/config/config-model_test.ts
new file mode 100644
index 0000000..b78a933
--- /dev/null
+++ b/polygerrit-ui/app/models/config/config-model_test.ts
@@ -0,0 +1,84 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../test/common-test-setup';
+import {assert} from '@open-wc/testing';
+import {getBaseUrl} from '../../utils/url-util';
+import {
+ createGerritInfo,
+ createServerInfo,
+} from '../../test/test-data-generators';
+import {ConfigModel} from './config-model';
+import {testResolver} from '../../test/common-test-setup';
+import {getAppContext} from '../../services/app-context';
+import {changeModelToken} from '../change/change-model';
+import {ServerInfo} from '../../api/rest-api';
+
+suite('getDocsBaseUrl tests', () => {
+ let model: ConfigModel;
+
+ setup(async () => {
+ model = new ConfigModel(
+ testResolver(changeModelToken),
+ getAppContext().restApiService
+ );
+ });
+
+ test('null config', async () => {
+ const probePathMock = sinon
+ .stub(model.restApiService, 'probePath')
+ .resolves(true);
+ const docsBaseUrl = await model.getDocsBaseUrl(undefined);
+ assert.equal(
+ probePathMock.lastCall.args[0],
+ `${getBaseUrl()}/Documentation/index.html`
+ );
+ assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
+ });
+
+ test('no doc config', async () => {
+ const probePathMock = sinon
+ .stub(model.restApiService, 'probePath')
+ .resolves(true);
+ const config: ServerInfo = {
+ ...createServerInfo(),
+ gerrit: createGerritInfo(),
+ };
+ const docsBaseUrl = await model.getDocsBaseUrl(config);
+ assert.equal(
+ probePathMock.lastCall.args[0],
+ `${getBaseUrl()}/Documentation/index.html`
+ );
+ assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
+ });
+
+ test('has doc config', async () => {
+ const probePathMock = sinon
+ .stub(model.restApiService, 'probePath')
+ .resolves(true);
+ const config: ServerInfo = {
+ ...createServerInfo(),
+ gerrit: {...createGerritInfo(), doc_url: 'foobar'},
+ };
+ const docsBaseUrl = await model.getDocsBaseUrl(config);
+ assert.isFalse(probePathMock.called);
+ assert.equal(docsBaseUrl, 'foobar');
+ });
+
+ test('no probe', async () => {
+ const probePathMock = sinon
+ .stub(model.restApiService, 'probePath')
+ .resolves(false);
+ const docsBaseUrl = await model.getDocsBaseUrl(undefined);
+ assert.equal(
+ probePathMock.lastCall.args[0],
+ `${getBaseUrl()}/Documentation/index.html`
+ );
+ assert.equal(
+ docsBaseUrl,
+ 'https://gerrit-review.googlesource.com/Documentation'
+ );
+ });
+});
diff --git a/polygerrit-ui/app/models/views/documentation.ts b/polygerrit-ui/app/models/views/documentation.ts
index ac844fc..118fdf9 100644
--- a/polygerrit-ui/app/models/views/documentation.ts
+++ b/polygerrit-ui/app/models/views/documentation.ts
@@ -14,6 +14,10 @@
filter: string;
}
+/**
+ * This is just for documentation *searches*, not for static documentation
+ * URLs. See `getDocUrl()` in url-util.ts.
+ */
export function createDocumentationUrl() {
return `${getBaseUrl()}/Documentation`;
}
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 acc7cdd..2350594 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
@@ -151,8 +151,6 @@
import {FlagsService, KnownExperimentId} from '../flags/flags';
const MAX_PROJECT_RESULTS = 25;
-export const PROBE_PATH = '/Documentation/index.html';
-export const DOCS_BASE_PATH = '/Documentation';
const Requests = {
SEND_DIFF_DRAFT: 'sendDiffDraft',
@@ -290,8 +288,6 @@
readonly _etags = grEtagDecorator; // Shared across instances.
- getDocsBaseUrlCachedPromise: Promise<string | null> | undefined;
-
// readonly, but set in tests.
_projectLookup = projectLookup; // Shared across instances.
@@ -3399,26 +3395,6 @@
}) as Promise<DashboardInfo | undefined>;
}
- /**
- * Get the docs base URL from either the server config or by probing.
- *
- * @return A promise that resolves with the docs base URL.
- */
- getDocsBaseUrl(config: ServerInfo | undefined): Promise<string | null> {
- if (!this.getDocsBaseUrlCachedPromise) {
- this.getDocsBaseUrlCachedPromise = new Promise(resolve => {
- if (config?.gerrit?.doc_url) {
- resolve(config.gerrit.doc_url);
- } else {
- this.probePath(getBaseUrl() + PROBE_PATH).then(ok => {
- resolve(ok ? getBaseUrl() + DOCS_BASE_PATH : null);
- });
- }
- });
- }
- return this.getDocsBaseUrlCachedPromise;
- }
-
getDocumentationSearches(filter: string): Promise<DocResult[] | undefined> {
filter = filter.trim();
const encodedFilter = encodeURIComponent(filter);
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
index 624bcb5..1e1439d 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
@@ -17,7 +17,6 @@
createAccountDetailWithId,
createChange,
createComment,
- createGerritInfo,
createParsedChange,
createServerInfo,
} from '../../test/test-data-generators';
@@ -54,7 +53,6 @@
RevisionId,
RevisionPatchSetNum,
RobotCommentInfo,
- ServerInfo,
Timestamp,
UrlEncodedCommentId,
} from '../../types/common';
@@ -1629,51 +1627,4 @@
anonymizedUrl: '/accounts/self/starred.changes/*',
});
});
-
- suite('getDocsBaseUrl tests', () => {
- test('null config', async () => {
- const probePathMock = sinon.stub(element, 'probePath').resolves(true);
- const docsBaseUrl = await element.getDocsBaseUrl(undefined);
- assert.equal(
- probePathMock.lastCall.args[0],
- `${getBaseUrl()}/Documentation/index.html`
- );
- assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
- });
-
- test('no doc config', async () => {
- const probePathMock = sinon.stub(element, 'probePath').resolves(true);
- const config: ServerInfo = {
- ...createServerInfo(),
- gerrit: createGerritInfo(),
- };
- const docsBaseUrl = await element.getDocsBaseUrl(config);
- assert.equal(
- probePathMock.lastCall.args[0],
- `${getBaseUrl()}/Documentation/index.html`
- );
- assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
- });
-
- test('has doc config', async () => {
- const probePathMock = sinon.stub(element, 'probePath').resolves(true);
- const config: ServerInfo = {
- ...createServerInfo(),
- gerrit: {...createGerritInfo(), doc_url: 'foobar'},
- };
- const docsBaseUrl = await element.getDocsBaseUrl(config);
- assert.isFalse(probePathMock.called);
- assert.equal(docsBaseUrl, 'foobar');
- });
-
- test('no probe', async () => {
- const probePathMock = sinon.stub(element, 'probePath').resolves(false);
- const docsBaseUrl = await element.getDocsBaseUrl(undefined);
- assert.equal(
- probePathMock.lastCall.args[0],
- `${getBaseUrl()}/Documentation/index.html`
- );
- assert.isNotOk(docsBaseUrl);
- });
- });
});
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
index c70f780..c96e978 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
@@ -371,8 +371,6 @@
endpoint: string
): Promise<string>;
- getDocsBaseUrl(config?: ServerInfo): Promise<string | null>;
-
createChange(
repo: RepoName,
branch: BranchName,
diff --git a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
index e0a1682..7969264 100644
--- a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
+++ b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
@@ -77,11 +77,6 @@
createDefaultPreferences,
} from '../../constants/constants';
import {ParsedChangeInfo} from '../../types/types';
-import {getBaseUrl} from '../../utils/url-util';
-import {
- DOCS_BASE_PATH,
- PROBE_PATH,
-} from '../../services/gr-rest-api/gr-rest-api-impl';
export const grRestApiMock: RestApiService = {
addAccountEmail(): Promise<Response> {
@@ -317,16 +312,6 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Promise.resolve({}) as any;
},
- getDocsBaseUrl(config?: ServerInfo): Promise<string | null> {
- if (config?.gerrit?.doc_url) {
- return Promise.resolve(config.gerrit.doc_url);
- } else {
- return this.probePath(getBaseUrl() + PROBE_PATH).then(ok =>
- Promise.resolve(ok ? getBaseUrl() + DOCS_BASE_PATH : null)
- );
- }
- return Promise.resolve('');
- },
getDocumentationSearches(): Promise<DocResult[] | undefined> {
return Promise.resolve([]);
},
diff --git a/polygerrit-ui/app/utils/url-util.ts b/polygerrit-ui/app/utils/url-util.ts
index 3d1bf7e..96edc7e 100644
--- a/polygerrit-ui/app/utils/url-util.ts
+++ b/polygerrit-ui/app/utils/url-util.ts
@@ -17,6 +17,16 @@
return self.CANONICAL_PATH || '';
}
+export function getDocUrl(docsBaseUrl: string, relativeUrl: string): string {
+ if (docsBaseUrl.endsWith('/')) {
+ docsBaseUrl = docsBaseUrl.slice(0, -1);
+ }
+ if (relativeUrl.startsWith('/')) {
+ relativeUrl = relativeUrl.slice(1);
+ }
+ return `${docsBaseUrl}/${relativeUrl}`;
+}
+
/**
* Return the url to use for login. If the server configuration
* contains the `loginUrl` in the `auth` section then that custom url
diff --git a/polygerrit-ui/app/utils/url-util_test.ts b/polygerrit-ui/app/utils/url-util_test.ts
index f8be92a..a92d8b1 100644
--- a/polygerrit-ui/app/utils/url-util_test.ts
+++ b/polygerrit-ui/app/utils/url-util_test.ts
@@ -16,6 +16,7 @@
toPathname,
toSearchParams,
sameOrigin,
+ getDocUrl,
} from './url-util';
import {assert} from '@open-wc/testing';
import {createAuth} from '../test/test-data-generators';
@@ -38,6 +39,15 @@
});
});
+ suite('getDocUrl tests', () => {
+ test('getDocUrl', () => {
+ assert.deepEqual(getDocUrl('a', 'b'), 'a/b');
+ assert.deepEqual(getDocUrl('a/', 'b'), 'a/b');
+ assert.deepEqual(getDocUrl('a', '/b'), 'a/b');
+ assert.deepEqual(getDocUrl('a/', '/b'), 'a/b');
+ });
+ });
+
suite('loginUrl tests', () => {
const authConfig = createAuth();