Merge changes I119f7594,I2c8398d6
* changes:
Revert "Add project permission for removing votes/labels."
Revert "Fix ChangeInfoDiffer.getAddedForMap method"
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt
index 41f544d..dd82f27 100644
--- a/Documentation/pg-plugin-endpoints.txt
+++ b/Documentation/pg-plugin-endpoints.txt
@@ -132,6 +132,10 @@
=== settings-screen
This endpoint is situated at the end of the body of the settings screen.
+=== profile
+This endpoint is situated at the top of the Profile section of the settings
+screen below the section description text.
+
=== reply-text
This endpoint wraps the textarea in the reply dialog.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 950049b..359361c 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -7189,11 +7189,16 @@
Whether the new change should be set to work in progress.
|`base_change` |optional|
A link:#change-id[\{change-id\}] that identifies the base change for a create
-change operation. Mutually exclusive with `base_commit`.
+change operation. +
+Mutually exclusive with `base_commit`. +
+If neither `base_commit` nor `base_change` are set, the target branch tip will
+be used as the parent commit.
|`base_commit` |optional|
A 40-digit hex SHA-1 of the commit which will be the parent commit of the newly
-created change. If set, it must be a merged commit on the destination branch.
-Mutually exclusive with `base_change`.
+created change. If set, it must be a merged commit on the destination branch. +
+Mutually exclusive with `base_change`. +
+If neither `base_commit` nor `base_change` are set, the target branch tip will
+be used as the parent commit.
|`new_branch` |optional, default to `false`|
Allow creating a new branch when set to `true`. Using this option is
only possible for non-merge commits (if the `merge` field is not set).
@@ -8226,6 +8231,7 @@
ancestry, where the oldest ancestor is the first.
|`contains_git_conflicts` ||Whether any of the rebased changes has conflicts
due to rebasing.
+|===========================
[[related-change-and-commit-info]]
=== RelatedChangeAndCommitInfo
diff --git a/contrib/git-gc-preserve b/contrib/git-gc-preserve
index f3946ab..a886721 100755
--- a/contrib/git-gc-preserve
+++ b/contrib/git-gc-preserve
@@ -67,6 +67,7 @@
test -f "$LOCKFILE" || touch "$LOCKFILE"
exec 9> "$LOCKFILE"
if flock -nx 9; then
+ echo -n "$$ $USERNAME@$HOSTNAME" >&9
trap unlock EXIT
else
echo "$0 is already running"
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 0073ec2..df2c5cb 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -55,6 +55,7 @@
import com.google.gerrit.server.account.externalids.ExternalIdCaseSensitivityMigrator;
import com.google.gerrit.server.api.GerritApiModule;
import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.api.projects.ProjectQueryBuilderModule;
import com.google.gerrit.server.audit.AuditModule;
import com.google.gerrit.server.cache.h2.H2CacheModule;
import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
@@ -308,6 +309,7 @@
modules.add(new MimeUtil2Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
modules.add(new GerritApiModule());
+ modules.add(new ProjectQueryBuilderModule());
modules.add(new PluginApiModule());
modules.add(new SearchingChangeCacheImplModule());
modules.add(new InternalAccountDirectoryModule());
diff --git a/java/com/google/gerrit/index/project/ProjectField.java b/java/com/google/gerrit/index/project/ProjectField.java
index 3114b4c..e050f53 100644
--- a/java/com/google/gerrit/index/project/ProjectField.java
+++ b/java/com/google/gerrit/index/project/ProjectField.java
@@ -15,14 +15,12 @@
package com.google.gerrit.index.project;
import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.index.FieldDef.exact;
-import static com.google.gerrit.index.FieldDef.fullText;
-import static com.google.gerrit.index.FieldDef.prefix;
import static com.google.gerrit.index.FieldDef.storedOnly;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.IndexedField;
import com.google.gerrit.index.RefState;
import com.google.gerrit.index.SchemaUtil;
@@ -38,23 +36,53 @@
.toByteArray(project.getNameKey());
}
- public static final FieldDef<ProjectData, String> NAME =
- exact("name").stored().build(p -> p.getProject().getName());
+ public static final IndexedField<ProjectData, String> NAME_FIELD =
+ IndexedField.<ProjectData>stringBuilder("RepoName")
+ .required()
+ .size(200)
+ .stored()
+ .build(p -> p.getProject().getName());
- public static final FieldDef<ProjectData, String> DESCRIPTION =
- fullText("description").stored().build(p -> p.getProject().getDescription());
+ public static final IndexedField<ProjectData, String>.SearchSpec NAME_SPEC =
+ NAME_FIELD.exact("name");
- public static final FieldDef<ProjectData, String> PARENT_NAME =
- exact("parent_name").build(p -> p.getProject().getParentName());
+ public static final IndexedField<ProjectData, String> DESCRIPTION_FIELD =
+ IndexedField.<ProjectData>stringBuilder("Description")
+ .stored()
+ .build(p -> p.getProject().getDescription());
- public static final FieldDef<ProjectData, Iterable<String>> NAME_PART =
- prefix("name_part").buildRepeatable(p -> SchemaUtil.getNameParts(p.getProject().getName()));
+ public static final IndexedField<ProjectData, String>.SearchSpec DESCRIPTION_SPEC =
+ DESCRIPTION_FIELD.fullText("description");
- public static final FieldDef<ProjectData, String> STATE =
- exact("state").stored().build(p -> p.getProject().getState().name());
+ public static final IndexedField<ProjectData, String> PARENT_NAME_FIELD =
+ IndexedField.<ProjectData>stringBuilder("ParentName")
+ .build(p -> p.getProject().getParentName());
- public static final FieldDef<ProjectData, Iterable<String>> ANCESTOR_NAME =
- exact("ancestor_name").buildRepeatable(ProjectData::getParentNames);
+ public static final IndexedField<ProjectData, String>.SearchSpec PARENT_NAME_SPEC =
+ PARENT_NAME_FIELD.exact("parent_name");
+
+ public static final IndexedField<ProjectData, Iterable<String>> NAME_PART_FIELD =
+ IndexedField.<ProjectData>iterableStringBuilder("NamePart")
+ .size(200)
+ .build(p -> SchemaUtil.getNameParts(p.getProject().getName()));
+
+ public static final IndexedField<ProjectData, Iterable<String>>.SearchSpec NAME_PART_SPEC =
+ NAME_PART_FIELD.prefix("name_part");
+
+ public static final IndexedField<ProjectData, String> STATE_FIELD =
+ IndexedField.<ProjectData>stringBuilder("State")
+ .stored()
+ .build(p -> p.getProject().getState().name());
+
+ public static final IndexedField<ProjectData, String>.SearchSpec STATE_SPEC =
+ STATE_FIELD.exact("state");
+
+ public static final IndexedField<ProjectData, Iterable<String>> ANCESTOR_NAME_FIELD =
+ IndexedField.<ProjectData>iterableStringBuilder("AncestorName")
+ .build(ProjectData::getParentNames);
+
+ public static final IndexedField<ProjectData, Iterable<String>>.SearchSpec ANCESTOR_NAME_SPEC =
+ ANCESTOR_NAME_FIELD.exact("ancestor_name");
/**
* All values of all refs that were used in the course of indexing this document. This covers
diff --git a/java/com/google/gerrit/index/project/ProjectIndex.java b/java/com/google/gerrit/index/project/ProjectIndex.java
index 8687544..0aa7393 100644
--- a/java/com/google/gerrit/index/project/ProjectIndex.java
+++ b/java/com/google/gerrit/index/project/ProjectIndex.java
@@ -31,7 +31,7 @@
@Override
default Predicate<ProjectData> keyPredicate(Project.NameKey nameKey) {
- return new ProjectPredicate(ProjectField.NAME, nameKey.get());
+ return new ProjectPredicate(ProjectField.NAME_SPEC, nameKey.get());
}
Function<ProjectData, Project.NameKey> ENTITY_TO_KEY = (p) -> p.getProject().getNameKey();
diff --git a/java/com/google/gerrit/index/project/ProjectPredicate.java b/java/com/google/gerrit/index/project/ProjectPredicate.java
index 11875ef..0eaf2b6 100644
--- a/java/com/google/gerrit/index/project/ProjectPredicate.java
+++ b/java/com/google/gerrit/index/project/ProjectPredicate.java
@@ -14,12 +14,12 @@
package com.google.gerrit.index.project;
-import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaFieldDefs.SchemaField;
import com.google.gerrit.index.query.IndexPredicate;
/** Predicate that is mapped to a field in the project index. */
public class ProjectPredicate extends IndexPredicate<ProjectData> {
- public ProjectPredicate(FieldDef<ProjectData, ?> def, String value) {
+ public ProjectPredicate(SchemaField<ProjectData, ?> def, String value) {
super(def, value);
}
}
diff --git a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
index 0619566..ef2c3f5 100644
--- a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
+++ b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.index.SchemaUtil.schema;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.index.Schema;
import com.google.gerrit.index.SchemaDefinitions;
@@ -31,14 +32,26 @@
static final Schema<ProjectData> V1 =
schema(
/* version= */ 1,
- ProjectField.NAME,
- ProjectField.DESCRIPTION,
- ProjectField.PARENT_NAME,
- ProjectField.NAME_PART,
- ProjectField.ANCESTOR_NAME);
+ ImmutableList.of(
+ ProjectField.NAME_FIELD,
+ ProjectField.DESCRIPTION_FIELD,
+ ProjectField.PARENT_NAME_FIELD,
+ ProjectField.NAME_PART_FIELD,
+ ProjectField.ANCESTOR_NAME_FIELD),
+ ImmutableList.of(
+ ProjectField.NAME_SPEC,
+ ProjectField.DESCRIPTION_SPEC,
+ ProjectField.PARENT_NAME_SPEC,
+ ProjectField.NAME_PART_SPEC,
+ ProjectField.ANCESTOR_NAME_SPEC));
@Deprecated
- static final Schema<ProjectData> V2 = schema(V1, ProjectField.STATE, ProjectField.REF_STATE);
+ static final Schema<ProjectData> V2 =
+ schema(
+ V1,
+ ImmutableList.of(ProjectField.REF_STATE),
+ ImmutableList.of(ProjectField.STATE_FIELD),
+ ImmutableList.of(ProjectField.STATE_SPEC));
// Bump Lucene version requires reindexing
@Deprecated static final Schema<ProjectData> V3 = schema(V2);
diff --git a/java/com/google/gerrit/lucene/LuceneProjectIndex.java b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
index fae854e..911d91f 100644
--- a/java/com/google/gerrit/lucene/LuceneProjectIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
@@ -15,7 +15,7 @@
package com.google.gerrit.lucene;
import static com.google.common.collect.Iterables.getOnlyElement;
-import static com.google.gerrit.index.project.ProjectField.NAME;
+import static com.google.gerrit.index.project.ProjectField.NAME_SPEC;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
@@ -58,14 +58,14 @@
implements ProjectIndex {
private static final String PROJECTS = "projects";
- private static final String NAME_SORT_FIELD = sortFieldName(NAME);
+ private static final String NAME_SORT_FIELD = sortFieldName(NAME_SPEC);
private static Term idTerm(ProjectData projectState) {
return idTerm(projectState.getProject().getNameKey());
}
private static Term idTerm(Project.NameKey nameKey) {
- return QueryBuilder.stringTerm(NAME.getName(), nameKey.get());
+ return QueryBuilder.stringTerm(NAME_SPEC.getName(), nameKey.get());
}
private final GerritIndexWriterConfig indexWriterConfig;
@@ -110,7 +110,7 @@
void add(Document doc, Values<ProjectData> values) {
// Add separate DocValues field for the field that is needed for sorting.
SchemaField<ProjectData, ?> f = values.getField();
- if (f == NAME) {
+ if (f == NAME_SPEC) {
String value = (String) getOnlyElement(values.getValues());
doc.add(new SortedDocValuesField(NAME_SORT_FIELD, new BytesRef(value)));
}
@@ -156,7 +156,7 @@
@Nullable
@Override
protected ProjectData fromDocument(Document doc) {
- Project.NameKey nameKey = Project.nameKey(doc.getField(NAME.getName()).stringValue());
+ Project.NameKey nameKey = Project.nameKey(doc.getField(NAME_SPEC.getName()).stringValue());
return projectCache.get().get(nameKey).map(ProjectState::toProjectData).orElse(null);
}
}
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 75891fe..0342fe5 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -64,6 +64,7 @@
import com.google.gerrit.server.account.externalids.ExternalIdCaseSensitivityMigrator;
import com.google.gerrit.server.api.GerritApiModule;
import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.api.projects.ProjectQueryBuilderModule;
import com.google.gerrit.server.audit.AuditModule;
import com.google.gerrit.server.cache.h2.H2CacheModule;
import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
@@ -446,6 +447,7 @@
modules.add(new MimeUtil2Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
modules.add(new GerritApiModule());
+ modules.add(new ProjectQueryBuilderModule());
modules.add(new PluginApiModule());
modules.add(new SearchingChangeCacheImplModule(replica));
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 8d5fea4..d6ea294 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -202,7 +202,6 @@
private ExternalIdNotes externalIdNotes;
@AssistedInject
- @SuppressWarnings("BindingAnnotationWithoutInject")
AccountsUpdate(
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
@@ -228,7 +227,6 @@
}
@AssistedInject
- @SuppressWarnings("BindingAnnotationWithoutInject")
AccountsUpdate(
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
diff --git a/java/com/google/gerrit/server/account/GroupCache.java b/java/com/google/gerrit/server/account/GroupCache.java
index 1e28d7d..46c730c 100644
--- a/java/com/google/gerrit/server/account/GroupCache.java
+++ b/java/com/google/gerrit/server/account/GroupCache.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.common.UsedAt;
+import com.google.gerrit.common.UsedAt.Project;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
+import com.google.gerrit.exceptions.StorageException;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
/** Tracks group objects in memory for efficient access. */
public interface GroupCache {
@@ -62,6 +66,22 @@
Map<AccountGroup.UUID, InternalGroup> get(Collection<AccountGroup.UUID> groupUuids);
/**
+ * Returns an {@code InternalGroup} instance for the given {@code AccountGroup.UUID} at the given
+ * {@code metaId} of {@link com.google.gerrit.entities.RefNames#refsGroups} ref.
+ *
+ * <p>The caller is responsible to ensure the presence of {@code metaId} and the corresponding
+ * meta ref.
+ *
+ * @param groupUuid the UUID of the internal group
+ * @param metaId the sha1 of commit in {@link com.google.gerrit.entities.RefNames#refsGroups} ref.
+ * @return the internal group at specific sha1 {@code metaId}
+ * @throws StorageException if no internal group with this UUID exists on this server at the
+ * specific sha1, or if an error occurred during lookup.
+ */
+ @UsedAt(Project.GOOGLE)
+ InternalGroup getFromMetaId(AccountGroup.UUID groupUuid, ObjectId metaId) throws StorageException;
+
+ /**
* Removes the association of the given ID with a group.
*
* <p>The next call to {@link #get(AccountGroup.Id)} won't provide a cached value.
diff --git a/java/com/google/gerrit/server/account/GroupCacheImpl.java b/java/com/google/gerrit/server/account/GroupCacheImpl.java
index 2d947ba..6f4fce9 100644
--- a/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -27,6 +27,7 @@
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.proto.Protos;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.proto.Cache;
@@ -122,15 +123,19 @@
private final LoadingCache<AccountGroup.Id, Optional<InternalGroup>> byId;
private final LoadingCache<String, Optional<InternalGroup>> byName;
private final LoadingCache<String, Optional<InternalGroup>> byUUID;
+ private final LoadingCache<Cache.GroupKeyProto, InternalGroup> persistedByUuidCache;
@Inject
GroupCacheImpl(
@Named(BYID_NAME) LoadingCache<AccountGroup.Id, Optional<InternalGroup>> byId,
@Named(BYNAME_NAME) LoadingCache<String, Optional<InternalGroup>> byName,
- @Named(BYUUID_NAME) LoadingCache<String, Optional<InternalGroup>> byUUID) {
+ @Named(BYUUID_NAME) LoadingCache<String, Optional<InternalGroup>> byUUID,
+ @Named(BYUUID_NAME_PERSISTED)
+ LoadingCache<Cache.GroupKeyProto, InternalGroup> persistedByUuidCache) {
this.byId = byId;
this.byName = byName;
this.byUUID = byUUID;
+ this.persistedByUuidCache = persistedByUuidCache;
}
@Override
@@ -185,6 +190,21 @@
}
@Override
+ public InternalGroup getFromMetaId(AccountGroup.UUID groupUuid, ObjectId metaId)
+ throws StorageException {
+ Cache.GroupKeyProto key =
+ Cache.GroupKeyProto.newBuilder()
+ .setUuid(groupUuid.get())
+ .setRevision(ObjectIdConverter.create().toByteString(metaId))
+ .build();
+ try {
+ return persistedByUuidCache.get(key);
+ } catch (ExecutionException e) {
+ throw new StorageException(e);
+ }
+ }
+
+ @Override
public void evict(AccountGroup.Id groupId) {
if (groupId != null) {
logger.atFine().log("Evict group %s by ID", groupId.get());
diff --git a/java/com/google/gerrit/server/api/projects/ProjectQueryBuilderModule.java b/java/com/google/gerrit/server/api/projects/ProjectQueryBuilderModule.java
new file mode 100644
index 0000000..8ed1175
--- /dev/null
+++ b/java/com/google/gerrit/server/api/projects/ProjectQueryBuilderModule.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.projects;
+
+import com.google.gerrit.server.query.project.ProjectQueryBuilder;
+import com.google.gerrit.server.query.project.ProjectQueryBuilderImpl;
+import com.google.inject.AbstractModule;
+
+public class ProjectQueryBuilderModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(ProjectQueryBuilder.class).to(ProjectQueryBuilderImpl.class);
+ }
+}
diff --git a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
index 27eeae1..9a75469 100644
--- a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
+++ b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
@@ -91,8 +91,7 @@
}
private List<ChangeData> getUnsortedRelated(
- ChangeData changeData, PatchSet basePs, boolean alwaysIncludeOriginalChange)
- throws IOException, PermissionBackendException {
+ ChangeData changeData, PatchSet basePs, boolean alwaysIncludeOriginalChange) {
Set<String> groups = getAllGroups(changeData.patchSets());
logger.atFine().log("groups = %s", groups);
if (groups.isEmpty()) {
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index c0c934b..87d8db1 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -115,7 +115,6 @@
private final RetryHelper retryHelper;
@AssistedInject
- @SuppressWarnings("BindingAnnotationWithoutInject")
GroupsUpdate(
GitRepositoryManager repoManager,
AllUsersName allUsersName,
@@ -150,7 +149,6 @@
}
@AssistedInject
- @SuppressWarnings("BindingAnnotationWithoutInject")
GroupsUpdate(
GitRepositoryManager repoManager,
AllUsersName allUsersName,
@@ -185,7 +183,6 @@
Optional.of(currentUser));
}
- @SuppressWarnings("BindingAnnotationWithoutInject")
private GroupsUpdate(
GitRepositoryManager repoManager,
AllUsersName allUsersName,
diff --git a/java/com/google/gerrit/server/index/IndexUtils.java b/java/com/google/gerrit/server/index/IndexUtils.java
index 213094e..352d376 100644
--- a/java/com/google/gerrit/server/index/IndexUtils.java
+++ b/java/com/google/gerrit/server/index/IndexUtils.java
@@ -116,9 +116,9 @@
*/
public static Set<String> projectFields(QueryOptions opts) {
Set<String> fs = opts.fields();
- return fs.contains(ProjectField.NAME.getName())
+ return fs.contains(ProjectField.NAME_SPEC.getName())
? fs
- : Sets.union(fs, ImmutableSet.of(ProjectField.NAME.getName()));
+ : Sets.union(fs, ImmutableSet.of(ProjectField.NAME_SPEC.getName()));
}
private IndexUtils() {
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 3585d13..1c89566f 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -988,7 +988,7 @@
/** Serialized change object, used for pre-populating results. */
private static final TypeToken<Entities.Change> CHANGE_TYPE_TOKEN =
- new TypeToken<Entities.Change>() {
+ new TypeToken<>() {
private static final long serialVersionUID = 1L;
};
@@ -1007,7 +1007,7 @@
/** Serialized approvals for the current patch set, used for pre-populating results. */
private static final TypeToken<Iterable<Entities.PatchSetApproval>> APPROVAL_TYPE_TOKEN =
- new TypeToken<Iterable<Entities.PatchSetApproval>>() {
+ new TypeToken<>() {
private static final long serialVersionUID = 1L;
};
@@ -1300,7 +1300,7 @@
/** Serialized patch set object, used for pre-populating results. */
private static final TypeToken<Iterable<Entities.PatchSet>> PATCH_SET_TYPE_TOKEN =
- new TypeToken<Iterable<Entities.PatchSet>>() {
+ new TypeToken<>() {
private static final long serialVersionUID = 1L;
};
@@ -1621,7 +1621,7 @@
/** Serialized submit requirements, used for pre-populating results. */
private static final TypeToken<Iterable<Cache.SubmitRequirementResultProto>>
STORED_SUBMIT_REQUIREMENTS_TYPE_TOKEN =
- new TypeToken<Iterable<Cache.SubmitRequirementResultProto>>() {
+ new TypeToken<>() {
private static final long serialVersionUID = 1L;
};
diff --git a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index 26477a4..8f5e36e 100644
--- a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.index.change.ChangeField.CHANGE_SPEC;
+import static com.google.gerrit.server.index.change.ChangeField.NUMERIC_ID_STR_SPEC;
import static com.google.gerrit.server.index.change.ChangeField.PROJECT_SPEC;
import com.google.common.annotations.VisibleForTesting;
@@ -68,10 +69,13 @@
int pageSizeMultiplier,
int limit,
Set<String> fields) {
- // Always include project since it is needed to load the change from NoteDb.
- if (!fields.contains(CHANGE_SPEC.getName()) && !fields.contains(PROJECT_SPEC.getName())) {
+ // Always include project and change id since both are needed to load the change from NoteDb.
+ if (!fields.contains(CHANGE_SPEC.getName())
+ && !(fields.contains(PROJECT_SPEC.getName())
+ && fields.contains(NUMERIC_ID_STR_SPEC.getName()))) {
fields = new HashSet<>(fields);
fields.add(PROJECT_SPEC.getName());
+ fields.add(NUMERIC_ID_STR_SPEC.getName());
}
return QueryOptions.create(config, start, pageSize, pageSizeMultiplier, limit, fields);
}
diff --git a/java/com/google/gerrit/server/index/project/StalenessChecker.java b/java/com/google/gerrit/server/index/project/StalenessChecker.java
index 9c44c00..9f6bb31 100644
--- a/java/com/google/gerrit/server/index/project/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/project/StalenessChecker.java
@@ -40,7 +40,7 @@
*/
public class StalenessChecker {
private static final ImmutableSet<String> FIELDS =
- ImmutableSet.of(ProjectField.NAME.getName(), ProjectField.REF_STATE.getName());
+ ImmutableSet.of(ProjectField.NAME_SPEC.getName(), ProjectField.REF_STATE.getName());
private final ProjectCache projectCache;
private final ProjectIndexCollection indexes;
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
index 2b856fb..43a212c 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
@@ -28,6 +28,7 @@
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Streams;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
@@ -66,6 +67,7 @@
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.HistogramDiff;
import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
@@ -76,6 +78,7 @@
/** Implementation of the {@link GitFileDiffCache} */
@Singleton
public class GitFileDiffCacheImpl implements GitFileDiffCache {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String GIT_DIFF = "git_file_diff";
public static Module module() {
@@ -340,8 +343,7 @@
throws IOException {
if (!key.useTimeout()) {
try (CloseablePool<DiffFormatter>.Handle formatter = diffPool.get()) {
- FileHeader fileHeader = formatter.get().toFileHeader(diffEntry);
- return GitFileDiff.create(diffEntry, fileHeader);
+ return GitFileDiff.create(diffEntry, getFileHeader(formatter, diffEntry));
}
}
// This submits the DiffFormatter to a different thread. The CloseablePool and our usage of it
@@ -353,7 +355,7 @@
diffExecutor.submit(
() -> {
try (CloseablePool<DiffFormatter>.Handle formatter = diffPool.get()) {
- return GitFileDiff.create(diffEntry, formatter.get().toFileHeader(diffEntry));
+ return GitFileDiff.create(diffEntry, getFileHeader(formatter, diffEntry));
}
});
try {
@@ -385,6 +387,46 @@
? diffEntry.getOldPath()
: diffEntry.getNewPath();
}
+
+ private FileHeader getFileHeader(
+ CloseablePool<DiffFormatter>.Handle formatter, DiffEntry diffEntry) throws IOException {
+ logger.atFine().log("getting file header for %s", formatDiffEntryForLogging(diffEntry));
+ try {
+ return formatter.get().toFileHeader(diffEntry);
+ } catch (MissingObjectException e) {
+ throw new IOException(
+ String.format("Failed to get file header for %s", formatDiffEntryForLogging(diffEntry)),
+ e);
+ }
+ }
+
+ private String formatDiffEntryForLogging(DiffEntry diffEntry) {
+ StringBuilder buf = new StringBuilder();
+ buf.append("DiffEntry[");
+ buf.append(diffEntry.getChangeType());
+ buf.append(" ");
+ switch (diffEntry.getChangeType()) {
+ case ADD:
+ buf.append(String.format("%s (%s)", diffEntry.getNewPath(), diffEntry.getNewId().name()));
+ break;
+ case COPY:
+ case RENAME:
+ buf.append(
+ String.format(
+ "%s (%s) -> %s (%s)",
+ diffEntry.getOldPath(),
+ diffEntry.getOldId().name(),
+ diffEntry.getNewPath(),
+ diffEntry.getNewId().name()));
+ break;
+ case DELETE:
+ case MODIFY:
+ buf.append(String.format("%s (%s)", diffEntry.getOldPath(), diffEntry.getOldId().name()));
+ break;
+ }
+ buf.append("]");
+ return buf.toString();
+ }
}
/**
diff --git a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
index 85a3ab9..df2e1cf 100644
--- a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
+++ b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
@@ -92,7 +92,6 @@
}
@Override
- @SuppressWarnings("CheckReturnValue")
public void run() {
logger.atFine().log("Loading project_list cache");
cache.refreshProjectList();
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 7fdd113..0afaa3f 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -76,6 +76,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Stream;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
@@ -300,17 +301,22 @@
@Override
public Set<AccountGroup.UUID> guessRelevantGroupUUIDs() {
try (Timer0.Context ignored = guessRelevantGroupsLatency.start()) {
- return Streams.concat(
- Arrays.stream(config.getStringList("groups", /* subsection= */ null, "relevantGroup"))
- .map(AccountGroup::uuid),
- all().stream()
- .map(n -> inMemoryProjectCache.getIfPresent(n))
- .filter(Objects::nonNull)
- .flatMap(p -> p.getAllGroupUUIDs().stream())
- // getAllGroupUUIDs shouldn't really return null UUIDs, but harden
- // against them just in case there is a bug or corner case.
- .filter(id -> id != null && id.get() != null))
- .collect(toSet());
+ Stream<AccountGroup.UUID> configuredRelevantGroups =
+ Arrays.stream(config.getStringList("groups", /* subsection= */ null, "relevantGroup"))
+ .map(AccountGroup::uuid);
+
+ Stream<AccountGroup.UUID> guessedRelevantGroups =
+ inMemoryProjectCache.asMap().values().stream()
+ .filter(Objects::nonNull)
+ .flatMap(p -> p.getAllGroupUUIDs().stream())
+ // getAllGroupUUIDs shouldn't really return null UUIDs, but harden
+ // against them just in case there is a bug or corner case.
+ .filter(id -> id != null && id.get() != null);
+
+ Set<AccountGroup.UUID> relevantGroupUuids =
+ Streams.concat(configuredRelevantGroups, guessedRelevantGroups).collect(toSet());
+ logger.atFine().log("relevant group UUIDs: %s", relevantGroupUuids);
+ return relevantGroupUuids;
}
}
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index e31411c..d5c4a97 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -159,7 +159,7 @@
return AccountPredicates.preferredEmail(email);
}
- throw new QueryParseException("'email' operator is not supported by account index version");
+ throw new QueryParseException("'email' operator is not supported on this gerrit host");
}
@Operator
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 78615bf..fc4df49 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -966,7 +966,7 @@
* submit requirements are evaluated online.
*
* <p>For changes loaded from the index, the value will be set from index field {@link
- * com.google.gerrit.server.index.change.ChangeField#STORED_SUBMIT_REQUIREMENTS}.
+ * com.google.gerrit.server.index.change.ChangeField#STORED_SUBMIT_REQUIREMENTS_FIELD}.
*/
public Map<SubmitRequirement, SubmitRequirementResult> submitRequirements() {
if (submitRequirements == null) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 24d205d..da75057 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -694,7 +694,8 @@
if ("mergeable".equalsIgnoreCase(value)) {
if (!args.indexMergeable) {
- throw new QueryParseException("'is:mergeable' operator is not supported by server");
+ throw new QueryParseException(
+ "'is:mergeable' operator is not supported on this gerrit host");
}
return new BooleanPredicate(ChangeField.MERGEABLE_SPEC);
}
@@ -775,7 +776,7 @@
@Operator
public Predicate<ChangeData> conflicts(String value) throws QueryParseException {
if (!args.conflictsPredicateEnabled) {
- throw new QueryParseException("'conflicts:' operator is not supported by server");
+ throw new QueryParseException("'conflicts:' operator is not supported on this gerrit host");
}
List<Change> changes = parseChange(value);
List<Predicate<ChangeData>> or = new ArrayList<>(changes.size());
@@ -1140,7 +1141,10 @@
@Operator
public Predicate<ChangeData> message(String text) throws QueryParseException {
if (text.startsWith("^")) {
- checkFieldAvailable(ChangeField.COMMIT_MESSAGE_EXACT, "messageexact");
+ if (!args.index.getSchema().hasField(ChangeField.COMMIT_MESSAGE_EXACT)) {
+ throw new QueryParseException(
+ "'message' operator with regular expression is not supported on this gerrit host");
+ }
return new RegexMessagePredicate(text);
}
return ChangePredicates.message(text);
@@ -1645,7 +1649,7 @@
throws QueryParseException {
if (!args.index.getSchema().hasField(field)) {
throw new QueryParseException(
- String.format("'%s' operator is not supported by change index version", operator));
+ String.format("'%s' operator is not supported on this gerrit host", operator));
}
}
diff --git a/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
index 8b4048f..a7b0743 100644
--- a/java/com/google/gerrit/server/query/project/ProjectPredicates.java
+++ b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -25,23 +25,23 @@
/** Utility class to create predicates for project index queries. */
public class ProjectPredicates {
public static Predicate<ProjectData> name(Project.NameKey nameKey) {
- return new ProjectPredicate(ProjectField.NAME, nameKey.get());
+ return new ProjectPredicate(ProjectField.NAME_SPEC, nameKey.get());
}
public static Predicate<ProjectData> parent(Project.NameKey parentNameKey) {
- return new ProjectPredicate(ProjectField.PARENT_NAME, parentNameKey.get());
+ return new ProjectPredicate(ProjectField.PARENT_NAME_SPEC, parentNameKey.get());
}
public static Predicate<ProjectData> inname(String name) {
- return new ProjectPredicate(ProjectField.NAME_PART, name.toLowerCase(Locale.US));
+ return new ProjectPredicate(ProjectField.NAME_PART_SPEC, name.toLowerCase(Locale.US));
}
public static Predicate<ProjectData> description(String description) {
- return new ProjectPredicate(ProjectField.DESCRIPTION, description);
+ return new ProjectPredicate(ProjectField.DESCRIPTION_SPEC, description);
}
public static Predicate<ProjectData> state(ProjectState state) {
- return new ProjectPredicate(ProjectField.STATE, state.name());
+ return new ProjectPredicate(ProjectField.STATE_SPEC, state.name());
}
private ProjectPredicates() {}
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
index d234546..edb12ec 100644
--- a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,93 +14,21 @@
package com.google.gerrit.server.query.project;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.primitives.Ints;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.index.project.ProjectData;
-import com.google.gerrit.index.query.LimitPredicate;
import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryBuilder;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.inject.Inject;
import java.util.List;
-/** Parses a query string meant to be applied to project objects. */
-public class ProjectQueryBuilder extends QueryBuilder<ProjectData, ProjectQueryBuilder> {
- public static final String FIELD_LIMIT = "limit";
+/**
+ * Provides methods required for parsing projects queries.
+ *
+ * <p>Internally (at google), this interface has a different implementation, comparing to upstream.
+ */
+public interface ProjectQueryBuilder {
+ String FIELD_LIMIT = "limit";
- private static final QueryBuilder.Definition<ProjectData, ProjectQueryBuilder> mydef =
- new QueryBuilder.Definition<>(ProjectQueryBuilder.class);
-
- @Inject
- ProjectQueryBuilder() {
- super(mydef, null);
- }
-
- @Operator
- public Predicate<ProjectData> name(String name) {
- return ProjectPredicates.name(Project.nameKey(name));
- }
-
- @Operator
- public Predicate<ProjectData> parent(String parentName) {
- return ProjectPredicates.parent(Project.nameKey(parentName));
- }
-
- @Operator
- public Predicate<ProjectData> inname(String namePart) {
- if (namePart.isEmpty()) {
- return name(namePart);
- }
- return ProjectPredicates.inname(namePart);
- }
-
- @Operator
- public Predicate<ProjectData> description(String description) throws QueryParseException {
- if (Strings.isNullOrEmpty(description)) {
- throw error("description operator requires a value");
- }
-
- return ProjectPredicates.description(description);
- }
-
- @Operator
- public Predicate<ProjectData> state(String state) throws QueryParseException {
- if (Strings.isNullOrEmpty(state)) {
- throw error("state operator requires a value");
- }
- ProjectState parsedState;
- try {
- parsedState = ProjectState.valueOf(state.replace('-', '_').toUpperCase());
- } catch (IllegalArgumentException e) {
- throw error("state operator must be either 'active' or 'read-only'", e);
- }
- if (parsedState == ProjectState.HIDDEN) {
- throw error("state operator must be either 'active' or 'read-only'");
- }
- return ProjectPredicates.state(parsedState);
- }
-
- @Override
- protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
- // Adapt the capacity of this list when adding more default predicates.
- List<Predicate<ProjectData>> preds = Lists.newArrayListWithCapacity(3);
- preds.add(name(query));
- preds.add(inname(query));
- if (!Strings.isNullOrEmpty(query)) {
- preds.add(description(query));
- }
- return Predicate.or(preds);
- }
-
- @Operator
- public Predicate<ProjectData> limit(String query) throws QueryParseException {
- Integer limit = Ints.tryParse(query);
- if (limit == null) {
- throw error("Invalid limit: " + query);
- }
- return new LimitPredicate<>(FIELD_LIMIT, limit);
- }
+ /** See {@link com.google.gerrit.index.query.QueryBuilder#parse(String)}. */
+ Predicate<ProjectData> parse(String query) throws QueryParseException;
+ /** See {@link com.google.gerrit.index.query.QueryBuilder#parse(List<String>)}. */
+ List<Predicate<ProjectData>> parse(List<String> queries) throws QueryParseException;
}
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryBuilderImpl.java b/java/com/google/gerrit/server/query/project/ProjectQueryBuilderImpl.java
new file mode 100644
index 0000000..f7135982
--- /dev/null
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryBuilderImpl.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.client.ProjectState;
+import com.google.gerrit.index.project.ProjectData;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.inject.Inject;
+import java.util.List;
+
+/** Parses a query string meant to be applied to project objects. */
+public class ProjectQueryBuilderImpl extends QueryBuilder<ProjectData, ProjectQueryBuilderImpl>
+ implements ProjectQueryBuilder {
+ private static final QueryBuilder.Definition<ProjectData, ProjectQueryBuilderImpl> mydef =
+ new QueryBuilder.Definition<>(ProjectQueryBuilderImpl.class);
+
+ @Inject
+ ProjectQueryBuilderImpl() {
+ super(mydef, null);
+ }
+
+ @Operator
+ public Predicate<ProjectData> name(String name) {
+ return ProjectPredicates.name(Project.nameKey(name));
+ }
+
+ @Operator
+ public Predicate<ProjectData> parent(String parentName) {
+ return ProjectPredicates.parent(Project.nameKey(parentName));
+ }
+
+ @Operator
+ public Predicate<ProjectData> inname(String namePart) {
+ if (namePart.isEmpty()) {
+ return name(namePart);
+ }
+ return ProjectPredicates.inname(namePart);
+ }
+
+ @Operator
+ public Predicate<ProjectData> description(String description) throws QueryParseException {
+ if (Strings.isNullOrEmpty(description)) {
+ throw error("description operator requires a value");
+ }
+
+ return ProjectPredicates.description(description);
+ }
+
+ @Operator
+ public Predicate<ProjectData> state(String state) throws QueryParseException {
+ if (Strings.isNullOrEmpty(state)) {
+ throw error("state operator requires a value");
+ }
+ ProjectState parsedState;
+ try {
+ parsedState = ProjectState.valueOf(state.replace('-', '_').toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw error("state operator must be either 'active' or 'read-only'", e);
+ }
+ if (parsedState == ProjectState.HIDDEN) {
+ throw error("state operator must be either 'active' or 'read-only'");
+ }
+ return ProjectPredicates.state(parsedState);
+ }
+
+ @Override
+ protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
+ // Adapt the capacity of this list when adding more default predicates.
+ List<Predicate<ProjectData>> preds = Lists.newArrayListWithCapacity(3);
+ preds.add(name(query));
+ preds.add(inname(query));
+ if (!Strings.isNullOrEmpty(query)) {
+ preds.add(description(query));
+ }
+ return Predicate.or(preds);
+ }
+
+ @Operator
+ public Predicate<ProjectData> limit(String query) throws QueryParseException {
+ Integer limit = Ints.tryParse(query);
+ if (limit == null) {
+ throw error("Invalid limit: " + query);
+ }
+ return new LimitPredicate<>(FIELD_LIMIT, limit);
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 9a6b03e..259e71d 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -379,7 +379,8 @@
// Add the review ops.
logger.atFine().log("posting review");
PostReviewOp postReviewOp =
- postReviewOpFactory.create(projectState, revision.getPatchSet().id(), input);
+ postReviewOpFactory.create(
+ projectState, revision.getPatchSet().id(), input, revision.getAccountId());
bu.addOp(revision.getChange().getId(), postReviewOp);
// Adjust the attention set based on the input
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewOp.java b/java/com/google/gerrit/server/restapi/change/PostReviewOp.java
index b7d17f2..29e453b 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReviewOp.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReviewOp.java
@@ -97,7 +97,8 @@
public class PostReviewOp implements BatchUpdateOp {
interface Factory {
- PostReviewOp create(ProjectState projectState, PatchSet.Id psId, ReviewInput in);
+ PostReviewOp create(
+ ProjectState projectState, PatchSet.Id psId, ReviewInput in, Account.Id reviewerId);
}
/**
@@ -192,6 +193,7 @@
private final ProjectState projectState;
private final PatchSet.Id psId;
private final ReviewInput in;
+ private final Account.Id reviewerId;
private final boolean publishPatchSetLevelComment;
private IdentifiedUser user;
@@ -220,7 +222,8 @@
PluginSetContext<OnPostReview> onPostReviews,
@Assisted ProjectState projectState,
@Assisted PatchSet.Id psId,
- @Assisted ReviewInput in) {
+ @Assisted ReviewInput in,
+ @Assisted Account.Id reviewerId) {
this.approvalCopier = approvalCopier;
this.approvalsUtil = approvalsUtil;
this.publishCommentUtil = publishCommentUtil;
@@ -237,6 +240,7 @@
this.projectState = projectState;
this.psId = psId;
this.in = in;
+ this.reviewerId = reviewerId;
}
@Override
@@ -645,10 +649,11 @@
del.add(c);
update.putApproval(normName, (short) 0);
}
- // Only allow voting again if the vote is copied over from a past patch-set, or the
- // values are different.
+ // Only allow voting again the values are different, if the real account differs or if the
+ // vote is copied over from a past patch-set.
} else if (c != null
&& (c.value() != ent.getValue()
+ || !c.realAccountId().equals(reviewerId)
|| (inLabels.containsKey(c.label()) && isApprovalCopiedOver(c, ctx.getNotes())))) {
PatchSetApproval.Builder b =
c.toBuilder()
diff --git a/java/com/google/gerrit/server/restapi/project/CreateChange.java b/java/com/google/gerrit/server/restapi/project/CreateChange.java
index 59efd06..2f1153e 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateChange.java
@@ -24,6 +24,7 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.ProjectUtil;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.ProjectResource;
@@ -59,7 +60,8 @@
throw new AuthException("Authentication required");
}
- if (!Strings.isNullOrEmpty(input.project) && !rsrc.getName().equals(input.project)) {
+ if (!Strings.isNullOrEmpty(input.project)
+ && !rsrc.getName().equals(ProjectUtil.sanitizeProjectName(input.project))) {
throw new BadRequestException("project must match URL");
}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index aadf6d4..b828037 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -44,6 +44,7 @@
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.api.GerritApiModule;
import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.api.projects.ProjectQueryBuilderModule;
import com.google.gerrit.server.audit.AuditModule;
import com.google.gerrit.server.cache.h2.H2CacheModule;
import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
@@ -191,6 +192,7 @@
AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
install(new AuthModule(authConfig));
install(new GerritApiModule());
+ install(new ProjectQueryBuilderModule());
factory(PluginUser.Factory.class);
install(new PluginApiModule());
install(new DefaultPermissionBackendModule());
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
index 4248ac5..74bd94e 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
@@ -844,9 +844,11 @@
public void setUp() throws Exception {
init(
id -> {
+ @SuppressWarnings("unused")
Object unused = gApi.changes().id(id).rebaseChain();
},
(id, in) -> {
+ @SuppressWarnings("unused")
Object unused = gApi.changes().id(id).rebaseChain(in);
});
}
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index d630296..04bdf15 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -601,6 +601,20 @@
}
@Test
+ public void getGroupFromMetaId() throws Exception {
+ AccountGroup.UUID uuid = groupOperations.newGroup().create();
+ InternalGroup preUpdateState = groupCache.get(uuid).get();
+ gApi.groups().id(uuid.toString()).description("New description");
+
+ InternalGroup postUpdateState = groupCache.get(uuid).get();
+ assertThat(postUpdateState).isNotEqualTo(preUpdateState);
+ assertThat(groupCache.getFromMetaId(uuid, preUpdateState.getRefState()))
+ .isEqualTo(preUpdateState);
+ assertThat(groupCache.getFromMetaId(uuid, postUpdateState.getRefState()))
+ .isEqualTo(postUpdateState);
+ }
+
+ @Test
@GerritConfig(name = "groups.global:Anonymous-Users.name", value = "All Users")
public void getSystemGroupByConfiguredName() throws Exception {
GroupReference anonymousUsersGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS);
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
index a625a70..93f91dd 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
@@ -54,7 +54,7 @@
@Inject private IndexOperations.Project projectIndexOperations;
private static final ImmutableSet<String> FIELDS =
- ImmutableSet.of(ProjectField.NAME.getName(), ProjectField.REF_STATE.getName());
+ ImmutableSet.of(ProjectField.NAME_SPEC.getName(), ProjectField.REF_STATE.getName());
@Test
public void indexProject_indexesRefStateOfProjectAndParents() throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
index eb827c0..3531d1c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -20,6 +20,8 @@
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
+import static com.google.gerrit.entities.RefNames.changeMetaRef;
+import static com.google.gerrit.entities.RefNames.patchSetRef;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -28,11 +30,13 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.RestSession;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -43,8 +47,10 @@
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -55,6 +61,7 @@
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.client.Side;
+import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -71,8 +78,15 @@
import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
+import java.io.File;
+import java.io.IOException;
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -105,28 +119,246 @@
}
@Test
+ @UseLocalDisk
public void voteOnBehalfOf() throws Exception {
allowCodeReviewOnBehalfOf();
+ TestAccount realUser = admin;
+ TestAccount impersonatedUser = user;
PushOneCommit.Result r = createChange();
RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+ try (Repository repo = repoManager.openRepository(project)) {
+ String changeMetaRef = changeMetaRef(r.getChange().getId());
+ createRefLogFileIfMissing(repo, changeMetaRef);
+
+ ReviewInput in = ReviewInput.recommend();
+ in.onBehalfOf = impersonatedUser.id().toString();
+ in.message = "Message on behalf of";
+ revision.review(in);
+
+ PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+ ChangeData cd = r.getChange();
+ ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+ // The change meta commit is created by the server and has the impersonated user as the
+ // author.
+ // Person idents of users in NoteDb commits are obfuscated due to privacy reasons.
+ RevCommit changeMetaCommit = projectOperations.project(project).getHead(changeMetaRef);
+ assertThat(changeMetaCommit.getCommitterIdent().getEmailAddress())
+ .isEqualTo(serverIdent.get().getEmailAddress());
+ assertThat(changeMetaCommit.getAuthorIdent().getEmailAddress())
+ .isEqualTo(changeNoteUtil.getAccountIdAsEmailAddress(impersonatedUser.id()));
+
+ // The ref log for the change meta ref records the impersonated user.
+ ReflogEntry changeMetaRefLogEntry = repo.getReflogReader(changeMetaRef).getLastEntry();
+ assertThat(changeMetaRefLogEntry.getWho().getEmailAddress())
+ .isEqualTo(impersonatedUser.email());
+ }
+ }
+
+ @Test
+ public void overrideImpersonatedVoteWithOtherImpersonatedVote_sameValue() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ TestAccount realUser = admin;
+ TestAccount realUser2 = admin2;
+ TestAccount impersonatedUser = user;
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+ // realUser votes Code-Review+1 on behalf of impersonatedUser
ReviewInput in = ReviewInput.recommend();
- in.onBehalfOf = user.id().toString();
+ in.onBehalfOf = impersonatedUser.id().toString();
in.message = "Message on behalf of";
revision.review(in);
PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
assertThat(psa.patchSetId().get()).isEqualTo(1);
assertThat(psa.label()).isEqualTo("Code-Review");
- assertThat(psa.accountId()).isEqualTo(user.id());
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
assertThat(psa.value()).isEqualTo(1);
- assertThat(psa.realAccountId()).isEqualTo(admin.id());
+ assertThat(psa.realAccountId()).isEqualTo(realUser.id());
ChangeData cd = r.getChange();
ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
assertThat(m.getMessage()).endsWith(in.message);
- assertThat(m.getAuthor()).isEqualTo(user.id());
- assertThat(m.getRealAuthor()).isEqualTo(admin.id());
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+ // realUser2 votes Code-Review+1 on behalf of impersonatedUser, this should override the
+ // impersonated Code-Review+1 of realUser with an impersonated Code-Review+1 of realUser2
+ requestScopeOperations.setApiUser(realUser2.id());
+ in = ReviewInput.recommend();
+ in.onBehalfOf = impersonatedUser.id().toString();
+ in.message = "Another message on behalf of";
+ gApi.changes().id(r.getChangeId()).current().review(in);
+
+ psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(realUser2.id());
+
+ cd = r.getChange();
+ m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(realUser2.id());
+ }
+
+ @Test
+ public void overrideImpersonatedVoteWithOtherImpersonatedVote_differentValue() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ TestAccount realUser = admin;
+ TestAccount realUser2 = admin2;
+ TestAccount impersonatedUser = user;
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+ // realUser votes Code-Review+1 on behalf of impersonatedUser
+ ReviewInput in = ReviewInput.recommend();
+ in.onBehalfOf = impersonatedUser.id().toString();
+ in.message = "Message on behalf of";
+ revision.review(in);
+
+ PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+ ChangeData cd = r.getChange();
+ ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+ // realUser2 votes Code-Review-1 on behalf of impersonatedUser, this should override the
+ // impersonated Code-Review+1 of realUser with an impersonated Code-Review-1 of realUser2
+ requestScopeOperations.setApiUser(realUser2.id());
+ in = ReviewInput.dislike();
+ in.onBehalfOf = impersonatedUser.id().toString();
+ in.message = "Another message on behalf of";
+ gApi.changes().id(r.getChangeId()).current().review(in);
+
+ psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(-1);
+ assertThat(psa.realAccountId()).isEqualTo(realUser2.id());
+
+ cd = r.getChange();
+ m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(realUser2.id());
+ }
+
+ @Test
+ public void overrideImpersonatedVoteWithNonImpersonatedVote_sameValue() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ TestAccount realUser = admin;
+ TestAccount impersonatedUser = user;
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+ // realUser votes Code-Review+1 on behalf of impersonatedUser
+ ReviewInput in = ReviewInput.recommend();
+ in.onBehalfOf = impersonatedUser.id().toString();
+ in.message = "Message on behalf of";
+ revision.review(in);
+
+ PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+ ChangeData cd = r.getChange();
+ ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+ // impersonatedUser votes Code-Review+1 themselves, this should override the impersonated
+ // Code-Review+1 with a non-impersonated Code-Review+1
+ requestScopeOperations.setApiUser(impersonatedUser.id());
+ in = ReviewInput.recommend();
+ in.message = "Message";
+ gApi.changes().id(r.getChangeId()).current().review(in);
+
+ psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(impersonatedUser.id());
+
+ cd = r.getChange();
+ m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(impersonatedUser.id());
+ }
+
+ @Test
+ public void overrideImpersonatedVoteWithNonImpersonatedVote_differentValue() throws Exception {
+ allowCodeReviewOnBehalfOf();
+ TestAccount realUser = admin;
+ TestAccount impersonatedUser = user;
+ PushOneCommit.Result r = createChange();
+ RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+
+ // realUser votes Code-Review+1 on behalf of impersonatedUser
+ ReviewInput in = ReviewInput.recommend();
+ in.onBehalfOf = impersonatedUser.id().toString();
+ in.message = "Message on behalf of";
+ revision.review(in);
+
+ PatchSetApproval psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(1);
+ assertThat(psa.realAccountId()).isEqualTo(realUser.id());
+
+ ChangeData cd = r.getChange();
+ ChangeMessage m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(realUser.id());
+
+ // impersonatedUser votes Code-Review-1 themselves, this should override the impersonated
+ // Code-Review+1 with a non-impersonated Code-Review-1
+ requestScopeOperations.setApiUser(impersonatedUser.id());
+ in = ReviewInput.dislike();
+ in.message = "Message";
+ gApi.changes().id(r.getChangeId()).current().review(in);
+
+ psa = Iterables.getOnlyElement(r.getChange().approvals().values());
+ assertThat(psa.patchSetId().get()).isEqualTo(1);
+ assertThat(psa.label()).isEqualTo("Code-Review");
+ assertThat(psa.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(psa.value()).isEqualTo(-1);
+ assertThat(psa.realAccountId()).isEqualTo(impersonatedUser.id());
+
+ cd = r.getChange();
+ m = Iterables.getLast(cmUtil.byChange(cd.notes()));
+ assertThat(m.getMessage()).endsWith(in.message);
+ assertThat(m.getAuthor()).isEqualTo(impersonatedUser.id());
+ assertThat(m.getRealAuthor()).isEqualTo(impersonatedUser.id());
}
@Test
@@ -342,21 +574,120 @@
}
@Test
- public void submitOnBehalfOf() throws Exception {
- allowSubmitOnBehalfOf();
- PushOneCommit.Result r = createChange();
+ @UseLocalDisk
+ public void submitOnBehalfOf_mergeAlways() throws Exception {
+ TestAccount realUser = admin;
+ TestAccount impersonatedUser = admin2;
+
+ // Create a project with MERGE_ALWAYS submit strategy so that a merge commit is created on
+ // submit and we can verify its committer and author and the ref log for the update of the
+ // target branch.
+ Project.NameKey project =
+ projectOperations.newProject().submitType(SubmitType.MERGE_ALWAYS).create();
+
+ testSubmitOnBehalfOf(project, realUser, impersonatedUser);
+
+ // The merge commit is created by the server and has the impersonated user as the author.
+ RevCommit mergeCommit = projectOperations.project(project).getHead("refs/heads/master");
+ assertThat(mergeCommit.getCommitterIdent().getEmailAddress())
+ .isEqualTo(serverIdent.get().getEmailAddress());
+ assertThat(mergeCommit.getAuthorIdent().getEmailAddress()).isEqualTo(impersonatedUser.email());
+
+ // The ref log for the target branch records the impersonated user.
+ try (Repository repo = repoManager.openRepository(project)) {
+ ReflogEntry targetBranchRefLogEntry =
+ repo.getReflogReader("refs/heads/master").getLastEntry();
+ assertThat(targetBranchRefLogEntry.getWho().getEmailAddress())
+ .isEqualTo(impersonatedUser.email());
+ }
+ }
+
+ @Test
+ @UseLocalDisk
+ public void submitOnBehalfOf_rebaseAlways() throws Exception {
+ TestAccount realUser = admin;
+ TestAccount impersonatedUser = admin2;
+
+ // Create a project with REBASE_ALWAYS submit strategy so that a new patch set is created on
+ // submit and we can verify its committer and author and the ref log for the update of the
+ // patch set ref and the target branch.
+ Project.NameKey project =
+ projectOperations.newProject().submitType(SubmitType.REBASE_ALWAYS).create();
+
+ ChangeData cd = testSubmitOnBehalfOf(project, realUser, impersonatedUser);
+
+ // Rebase on submit is expected to create a new patch set.
+ assertThat(cd.currentPatchSet().id().get()).isEqualTo(2);
+
+ // The patch set commit is created by the impersonated user and has the real user as the author.
+ // Recording the real user as the author seems to a bug, we would expect the author to be the
+ // impersonated user.
+ RevCommit newPatchSetCommit =
+ projectOperations.project(project).getHead(cd.currentPatchSet().refName());
+ assertThat(newPatchSetCommit.getCommitterIdent().getEmailAddress())
+ .isEqualTo(impersonatedUser.email());
+ assertThat(newPatchSetCommit.getAuthorIdent().getEmailAddress()).isEqualTo(realUser.email());
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ // The ref log for the patch set ref records the impersonated user.
+ ReflogEntry patchSetRefLogEntry =
+ repo.getReflogReader(cd.currentPatchSet().refName()).getLastEntry();
+ assertThat(patchSetRefLogEntry.getWho().getEmailAddress())
+ .isEqualTo(impersonatedUser.email());
+
+ // The ref log for the target branch records the impersonated user.
+ ReflogEntry targetBranchRefLogEntry =
+ repo.getReflogReader("refs/heads/master").getLastEntry();
+ assertThat(targetBranchRefLogEntry.getWho().getEmailAddress())
+ .isEqualTo(impersonatedUser.email());
+ }
+ }
+
+ @CanIgnoreReturnValue
+ private ChangeData testSubmitOnBehalfOf(
+ Project.NameKey project, TestAccount realUser, TestAccount impersonatedUser)
+ throws Exception {
+ allowSubmitOnBehalfOf(project);
+
+ TestRepository<InMemoryRepository> testRepo = cloneProject(project, realUser);
+
+ PushOneCommit.Result r = createChange(testRepo);
String changeId = project.get() + "~master~" + r.getChangeId();
gApi.changes().id(changeId).current().review(ReviewInput.approve());
SubmitInput in = new SubmitInput();
- in.onBehalfOf = admin2.email();
- gApi.changes().id(changeId).current().submit(in);
+ in.onBehalfOf = impersonatedUser.email();
- ChangeData cd = r.getChange();
- assertThat(cd.change().isMerged()).isTrue();
- PatchSetApproval submitter =
- approvalsUtil.getSubmitter(cd.notes(), cd.change().currentPatchSetId());
- assertThat(submitter.accountId()).isEqualTo(admin2.id());
- assertThat(submitter.realAccountId()).isEqualTo(admin.id());
+ try (Repository repo = repoManager.openRepository(project)) {
+ String changeMetaRef = changeMetaRef(r.getChange().getId());
+ createRefLogFileIfMissing(repo, changeMetaRef);
+ createRefLogFileIfMissing(repo, "refs/heads/master");
+ createRefLogFileIfMissing(repo, patchSetRef(PatchSet.id(r.getChange().getId(), 2)));
+
+ gApi.changes().id(changeId).current().submit(in);
+
+ ChangeData cd = r.getChange();
+ assertThat(cd.change().isMerged()).isTrue();
+ PatchSetApproval submitter =
+ approvalsUtil.getSubmitter(cd.notes(), cd.change().currentPatchSetId());
+ assertThat(submitter.accountId()).isEqualTo(impersonatedUser.id());
+ assertThat(submitter.realAccountId()).isEqualTo(realUser.id());
+
+ // The change meta commit is created by the server and has the impersonated user as the
+ // author.
+ // Person idents of users in NoteDb commits are obfuscated due to privacy reasons.
+ RevCommit changeMetaCommit = projectOperations.project(project).getHead(changeMetaRef);
+ assertThat(changeMetaCommit.getCommitterIdent().getEmailAddress())
+ .isEqualTo(serverIdent.get().getEmailAddress());
+ assertThat(changeMetaCommit.getAuthorIdent().getEmailAddress())
+ .isEqualTo(changeNoteUtil.getAccountIdAsEmailAddress(impersonatedUser.id()));
+
+ // The ref log for the change meta ref records the impersonated user.
+ ReflogEntry changeMetaRefLogEntry = repo.getReflogReader(changeMetaRef).getLastEntry();
+ assertThat(changeMetaRefLogEntry.getWho().getEmailAddress())
+ .isEqualTo(impersonatedUser.email());
+
+ return cd;
+ }
}
@Test
@@ -591,6 +922,10 @@
}
private void allowSubmitOnBehalfOf() throws Exception {
+ allowSubmitOnBehalfOf(project);
+ }
+
+ private void allowSubmitOnBehalfOf(Project.NameKey project) throws Exception {
String heads = "refs/heads/*";
projectOperations
.project(project)
@@ -630,4 +965,12 @@
private static Header runAsHeader(Object user) {
return new BasicHeader("X-Gerrit-RunAs", user.toString());
}
+
+ private void createRefLogFileIfMissing(Repository repo, String ref) throws IOException {
+ File log = new File(repo.getDirectory(), "logs/" + ref);
+ if (!log.exists()) {
+ log.getParentFile().mkdirs();
+ assertThat(log.createNewFile()).isTrue();
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateChangeIT.java
index 0c221aa..7b42d93 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateChangeIT.java
@@ -14,6 +14,7 @@
package com.google.gerrit.acceptance.rest.project;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.entities.RefNames.REFS_HEADS;
@@ -21,6 +22,7 @@
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.extensions.restapi.IdString;
import org.junit.Test;
public class CreateChangeIT extends AbstractDaemonTest {
@@ -43,7 +45,44 @@
ChangeInput input = new ChangeInput();
input.branch = "foo";
input.subject = "subject";
- RestResponse cr = adminRestSession.post("/projects/" + project.get() + "/create.change", input);
- cr.assertCreated();
+ RestResponse response =
+ adminRestSession.post("/projects/" + project.get() + "/create.change", input);
+ response.assertCreated();
+ }
+
+ @Test
+ public void nonMatchingProjectIsRejected() throws Exception {
+ ChangeInput input = new ChangeInput();
+ input.project = "non-matching-project";
+ input.branch = "master";
+ input.subject = "subject";
+ RestResponse response =
+ adminRestSession.post("/projects/" + project.get() + "/create.change", input);
+ response.assertBadRequest();
+ assertThat(response.getEntityContent()).isEqualTo("project must match URL");
+ }
+
+ @Test
+ public void matchingProjectIsAccepted() throws Exception {
+ ChangeInput input = new ChangeInput();
+ input.project = project.get();
+ input.branch = "master";
+ input.subject = "subject";
+ RestResponse response =
+ adminRestSession.post("/projects/" + project.get() + "/create.change", input);
+ response.assertCreated();
+ }
+
+ @Test
+ public void matchingProjectWithTrailingSlashIsAccepted() throws Exception {
+ ChangeInput input = new ChangeInput();
+ input.project = project.get() + "/";
+ input.branch = "master";
+ input.subject = "subject";
+ RestResponse response =
+ adminRestSession.post(
+ "/projects/" + IdString.fromDecoded(project.get() + "/").encoded() + "/create.change",
+ input);
+ response.assertCreated();
}
}
diff --git a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
index 65eb3b8..a40afe8 100644
--- a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
@@ -40,8 +40,7 @@
String metaId = "0e39795bb25dc914118224995c53c5c36923a461";
account.setMetaId(metaId);
Iterable<byte[]> refStates =
- (Iterable<byte[]>)
- AccountField.REF_STATE_SPEC.get(AccountState.forAccount(account.build()));
+ AccountField.REF_STATE_SPEC.get(AccountState.forAccount(account.build()));
List<String> values = toStrings(refStates);
String expectedValue =
allUsersName.get() + ":" + RefNames.refsUsers(account.id()) + ":" + metaId;
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 8919e04..fbf9c87 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -46,6 +46,7 @@
import com.google.common.collect.Multimaps;
import com.google.common.collect.Streams;
import com.google.common.truth.ThrowableSubject;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.acceptance.ExtensionRegistry;
import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.FakeSubmitRule;
@@ -109,12 +110,14 @@
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.account.VersionedAccountQueries;
+import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdFactory;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.testing.TestGroupBackend;
import com.google.gerrit.server.index.change.ChangeField;
@@ -134,8 +137,6 @@
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.GerritServerTests;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
import com.google.gerrit.testing.TestChanges;
import com.google.gerrit.testing.TestTimeUtil;
import com.google.inject.Inject;
@@ -160,6 +161,7 @@
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.SystemReader;
@@ -184,7 +186,7 @@
@Inject protected ChangeIndexer indexer;
@Inject protected ExtensionRegistry extensionRegistry;
@Inject protected IndexConfig indexConfig;
- @Inject protected InMemoryRepositoryManager repoManager;
+ @Inject protected GitRepositoryManager repoManager;
@Inject protected Provider<AnonymousUser> anonymousUserProvider;
@Inject protected Provider<InternalChangeQuery> queryProvider;
@Inject protected ChangeNotes.Factory notesFactory;
@@ -207,11 +209,20 @@
protected Injector injector;
protected LifecycleManager lifecycle;
+
+ /**
+ * Index tests should not use username in query assert, since some backends do not use {@link
+ * ExternalId#SCHEME_USERNAME}
+ */
protected Account.Id userId;
+
protected CurrentUser user;
+ protected Account userAccount;
private String systemTimeZone;
+ protected TestRepository<Repository> repo;
+
protected abstract Injector createInjector();
@Before
@@ -227,6 +238,10 @@
@After
public void cleanUp() {
+ if (repo != null) {
+ repo.close();
+ repo = null;
+ }
lifecycle.stop();
}
@@ -253,8 +268,9 @@
return () -> requestUser;
}
- protected void resetUser() {
+ protected void resetUser() throws ConfigInvalidException, IOException {
user = userFactory.create(userId);
+ userAccount = accounts.get(userId).get().account();
requestContext.setContext(newRequestContext(userId));
}
@@ -290,9 +306,9 @@
@Test
public void byId() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
assertQuery("12345");
assertQuery(change1.getId().get(), change1);
@@ -301,8 +317,8 @@
@Test
public void byKey() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
String key = change.getKey().get();
assertQuery("I0000000000000000000000000000000000000000");
@@ -314,8 +330,8 @@
@Test
public void byTriplet() throws Exception {
- TestRepository<Repo> repo = createProject("iabcde");
- Change change = insert(repo, newChangeForBranch(repo, "branch"));
+ repo = createAndOpenProject("iabcde");
+ Change change = insert("iabcde", newChangeForBranch(repo, "branch"));
String k = change.getKey().get();
assertQuery("iabcde~branch~" + k, change);
@@ -337,11 +353,11 @@
@Test
public void byStatus() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
- Change change1 = insert(repo, ins1);
+ Change change1 = insert("repo", ins1);
ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.MERGED);
- Change change2 = insert(repo, ins2);
+ Change change2 = insert("repo", ins2);
assertQuery("status:new", change1);
assertQuery("status:NEW", change1);
@@ -356,11 +372,11 @@
@Test
public void byStatusOr() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
- Change change1 = insert(repo, ins1);
+ Change change1 = insert("repo", ins1);
ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.MERGED);
- Change change2 = insert(repo, ins2);
+ Change change2 = insert("repo", ins2);
assertQuery("status:new OR status:merged", change2, change1);
assertQuery("status:new or status:merged", change2, change1);
@@ -368,10 +384,10 @@
@Test
public void byStatusOpen() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
- Change change1 = insert(repo, ins1);
- insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+ Change change1 = insert("repo", ins1);
+ insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
Change[] expected = new Change[] {change1};
assertQuery("status:open", expected);
@@ -390,12 +406,12 @@
@Test
public void byStatusClosed() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.MERGED);
- Change change1 = insert(repo, ins1);
+ Change change1 = insert("repo", ins1);
ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.ABANDONED);
- Change change2 = insert(repo, ins2);
- insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+ Change change2 = insert("repo", ins2);
+ insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
Change[] expected = new Change[] {change2, change1};
assertQuery("status:closed", expected);
@@ -411,12 +427,12 @@
@Test
public void byStatusAbandoned() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.MERGED);
- insert(repo, ins1);
+ insert("repo", ins1);
ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.ABANDONED);
- Change change1 = insert(repo, ins2);
- insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+ Change change1 = insert("repo", ins2);
+ insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
assertQuery("status:abandoned", change1);
assertQuery("status:ABANDONED", change1);
@@ -425,10 +441,10 @@
@Test
public void byStatusPrefix() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
- Change change1 = insert(repo, ins1);
- insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+ Change change1 = insert("repo", ins1);
+ Change change2 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
assertQuery("status:n", change1);
assertQuery("status:ne", change1);
@@ -436,6 +452,7 @@
assertQuery("status:N", change1);
assertQuery("status:nE", change1);
assertQuery("status:neW", change1);
+ assertQuery("status:m", change2);
Exception thrown = assertThrows(BadRequestException.class, () -> assertQuery("status:newx"));
assertThat(thrown).hasMessageThat().isEqualTo("Unrecognized value: newx");
thrown = assertThrows(BadRequestException.class, () -> assertQuery("status:nx"));
@@ -444,11 +461,11 @@
@Test
public void byPrivate() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo), userId);
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
- Change change2 = insert(repo, newChange(repo), user2);
+ Change change2 = insert("repo", newChange(repo), user2);
// No private changes.
assertQuery("is:open", change2, change1);
@@ -468,8 +485,8 @@
@Test
public void byWip() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo), userId);
assertQuery("is:open", change1);
assertQuery("is:wip");
@@ -486,8 +503,8 @@
@Test
public void excludeWipChangeFromReviewersDashboards() throws Exception {
Account.Id user1 = createAccount("user1");
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWorkInProgress(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWorkInProgress(repo), userId);
assertQuery("is:wip", change1);
assertQuery("reviewer:" + user1);
@@ -503,8 +520,8 @@
@Test
public void byStarted() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWorkInProgress(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWorkInProgress(repo));
assertQuery("is:started");
@@ -539,11 +556,11 @@
@Test
public void restorePendingReviewers() throws Exception {
Project.NameKey project = Project.nameKey("repo");
- TestRepository<Repo> repo = createProject(project.get());
+ repo = createAndOpenProject(project.get());
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
gApi.projects().name(project.get()).config(conf);
- Change change1 = insert(repo, newChangeWorkInProgress(repo));
+ Change change1 = insert("repo", newChangeWorkInProgress(repo));
Account.Id user1 = createAccount("user1");
Account.Id user2 = createAccount("user2");
String email1 = "email1@example.com";
@@ -596,9 +613,9 @@
@Test
public void byCommit() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins = newChange(repo);
- Change change = insert(repo, ins);
+ Change change = insert("repo", ins);
String sha = ins.getCommitId().name();
assertQuery("0000000000000000000000000000000000000000");
@@ -612,11 +629,11 @@
@Test
public void byOwner() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo), userId);
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
- Change change2 = insert(repo, newChange(repo), user2);
+ Change change2 = insert("repo", newChange(repo), user2);
assertQuery("is:owner", change1);
assertQuery("owner:" + userId.get(), change1);
@@ -629,15 +646,16 @@
@Test
public void byUploader() throws Exception {
assume().that(getSchema().hasField(ChangeField.UPLOADER_SPEC)).isTrue();
- Account.Id user2 =
- accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
- CurrentUser user2CurrentUser = userFactory.create(user2);
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo), userId);
assertQuery("is:uploader", change1);
assertQuery("uploader:" + userId.get(), change1);
- change1 = newPatchSet(repo, change1, user2CurrentUser, /* message= */ Optional.empty());
+
+ Account.Id user2 = createAccount("anotheruser");
+ CurrentUser user2CurrentUser = userFactory.create(user2);
+
+ change1 = newPatchSet("repo", change1, user2CurrentUser, /* message= */ Optional.empty());
// Uploader has changed
assertQuery("uploader:" + userId.get());
assertQuery("uploader:" + user2.get(), change1);
@@ -670,7 +688,7 @@
}
private void byAuthorOrCommitterExact(String searchOperator) throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ createProject("repo");
PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
PersonIdent john = new PersonIdent("John", "john@example.com");
PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
@@ -678,10 +696,10 @@
PersonIdent myself = new PersonIdent("I Am", ua.preferredEmail());
PersonIdent selfName = new PersonIdent("My Self", "my.self@example.com");
- Change change1 = createChange(repo, johnDoe);
- Change change2 = createChange(repo, john);
- Change change3 = createChange(repo, doeSmith);
- createChange(repo, selfName);
+ Change change1 = createChange("repo", johnDoe);
+ Change change2 = createChange("repo", john);
+ Change change3 = createChange("repo", doeSmith);
+ createChange("repo", selfName);
// Only email address.
assertQuery(searchOperator + "john.doe@example.com", change1);
@@ -706,19 +724,19 @@
assertQuery(searchOperator + "self");
// ':self' matches a change created with the current user's email address
- Change change5 = createChange(repo, myself);
+ Change change5 = createChange("repo", myself);
assertQuery(searchOperator + "me", change5);
assertQuery(searchOperator + "self", change5);
}
private void byAuthorOrCommitterFullText(String searchOperator) throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ createProject("repo");
PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
PersonIdent john = new PersonIdent("John", "john@example.com");
PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
- Change change1 = createChange(repo, johnDoe);
- Change change2 = createChange(repo, john);
- Change change3 = createChange(repo, doeSmith);
+ Change change1 = createChange("repo", johnDoe);
+ Change change2 = createChange("repo", john);
+ Change change3 = createChange("repo", doeSmith);
// By exact name.
assertQuery(searchOperator + "\"John Doe\"", change1);
@@ -739,20 +757,25 @@
assertThat(thrown).hasMessageThat().contains("invalid value");
}
- protected Change createChange(TestRepository<Repo> repo, PersonIdent person) throws Exception {
- RevCommit commit =
- repo.parseBody(repo.commit().message("message").author(person).committer(person).create());
- return insert(repo, newChangeForCommit(repo, commit), null);
+ @CanIgnoreReturnValue
+ protected Change createChange(String repoName, PersonIdent person) throws Exception {
+ try (TestRepository<Repository> repo =
+ new TestRepository<>(repoManager.openRepository(Project.nameKey(repoName)))) {
+ RevCommit commit =
+ repo.parseBody(
+ repo.commit().message("message").author(person).committer(person).create());
+ return insert("repo", newChangeForCommit(repo, commit), null);
+ }
}
@Test
public void byOwnerIn() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo), userId);
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
- Change change2 = insert(repo, newChange(repo), user2);
- Change change3 = insert(repo, newChange(repo), user2);
+ Change change2 = insert("repo", newChange(repo), user2);
+ Change change3 = insert("repo", newChange(repo), user2);
gApi.changes().id(change3.getId().get()).current().review(ReviewInput.approve());
gApi.changes().id(change3.getId().get()).current().submit();
@@ -765,24 +788,25 @@
@Test
public void byUploaderIn() throws Exception {
assume().that(getSchema().hasField(ChangeField.UPLOADER_SPEC)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo), userId);
+
assertQuery("uploaderin:Administrators", change1);
- Account.Id user2 =
- accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+ Account.Id user2 = createAccount("anotheruser");
CurrentUser user2CurrentUser = userFactory.create(user2);
- change1 = newPatchSet(repo, change1, user2CurrentUser, /* message= */ Optional.empty());
+ change1 = newPatchSet("repo", change1, user2CurrentUser, /* message= */ Optional.empty());
+
assertQuery("uploaderin:Administrators");
assertQuery("uploaderin:\"Registered Users\"", change1);
}
@Test
public void byProject() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("project:foo");
assertQuery("project:repo");
@@ -792,16 +816,16 @@
@Test
public void byProjectWithHidden() throws Exception {
- TestRepository<Repo> hiddenProject = createProject("hiddenProject");
- insert(hiddenProject, newChange(hiddenProject));
+ createProject("hiddenProject");
+ insert("hiddenProject", newChange("hiddenProject"));
projectOperations
.project(Project.nameKey("hiddenProject"))
.forUpdate()
.add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
.update();
- TestRepository<Repo> visibleProject = createProject("visibleProject");
- Change visibleChange = insert(visibleProject, newChange(visibleProject));
+ createProject("visibleProject");
+ Change visibleChange = insert("visibleProject", newChange("visibleProject"));
assertQuery("project:visibleProject", visibleChange);
assertQuery("project:hiddenProject");
assertQuery("project:visibleProject OR project:hiddenProject", visibleChange);
@@ -809,13 +833,13 @@
@Test
public void byParentOf() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- RevCommit commit1 = repo1.parseBody(repo1.commit().message("message").create());
- Change change1 = insert(repo1, newChangeForCommit(repo1, commit1));
- RevCommit commit2 = repo1.parseBody(repo1.commit(commit1));
- Change change2 = insert(repo1, newChangeForCommit(repo1, commit2));
- RevCommit commit3 = repo1.parseBody(repo1.commit(commit1, commit2));
- Change change3 = insert(repo1, newChangeForCommit(repo1, commit3));
+ repo = createAndOpenProject("repo1");
+ RevCommit commit1 = repo.parseBody(repo.commit().message("message").create());
+ Change change1 = insert("repo1", newChangeForCommit(repo, commit1));
+ RevCommit commit2 = repo.parseBody(repo.commit(commit1));
+ Change change2 = insert("repo1", newChangeForCommit(repo, commit2));
+ RevCommit commit3 = repo.parseBody(repo.commit(commit1, commit2));
+ Change change3 = insert("repo1", newChangeForCommit(repo, commit3));
assertQuery("parentof:" + change1.getId().get());
assertQuery("parentof:" + change1.getKey().get());
@@ -827,10 +851,10 @@
@Test
public void byParentProject() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2", "repo1");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2", "repo1");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("parentproject:repo1", change2, change1);
assertQuery("parentproject:repo2", change2);
@@ -838,10 +862,10 @@
@Test
public void byProjectPrefix() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("projects:foo");
assertQuery("projects:repo1", change1);
@@ -851,10 +875,10 @@
@Test
public void byRepository() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("repository:foo");
assertQuery("repository:repo");
@@ -864,10 +888,10 @@
@Test
public void byParentRepository() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2", "repo1");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2", "repo1");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("parentrepository:repo1", change2, change1);
assertQuery("parentrepository:repo2", change2);
@@ -875,10 +899,10 @@
@Test
public void byRepositoryPrefix() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("repositories:foo");
assertQuery("repositories:repo1", change1);
@@ -888,10 +912,10 @@
@Test
public void byRepo() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("repo:foo");
assertQuery("repo:repo");
@@ -901,10 +925,10 @@
@Test
public void byParentRepo() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2", "repo1");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2", "repo1");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("parentrepo:repo1", change2, change1);
assertQuery("parentrepo:repo2", change2);
@@ -912,10 +936,10 @@
@Test
public void byRepoPrefix() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- TestRepository<Repo> repo2 = createProject("repo2");
- Change change1 = insert(repo1, newChange(repo1));
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ createProject("repo2");
+ Change change1 = insert("repo1", newChange("repo1"));
+ Change change2 = insert("repo2", newChange("repo2"));
assertQuery("repos:foo");
assertQuery("repos:repo1", change1);
@@ -925,9 +949,9 @@
@Test
public void byBranchAndRef() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeForBranch(repo, "master"));
- Change change2 = insert(repo, newChangeForBranch(repo, "branch"));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeForBranch(repo, "master"));
+ Change change2 = insert("repo", newChangeForBranch(repo, "branch"));
assertQuery("branch:foo");
assertQuery("branch:master", change1);
@@ -944,26 +968,26 @@
@Test
public void byTopic() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithTopic(repo, "feature1");
- Change change1 = insert(repo, ins1);
+ Change change1 = insert("repo", ins1);
ChangeInserter ins2 = newChangeWithTopic(repo, "feature2");
- Change change2 = insert(repo, ins2);
+ Change change2 = insert("repo", ins2);
ChangeInserter ins3 = newChangeWithTopic(repo, "Cherrypick-feature2");
- Change change3 = insert(repo, ins3);
+ Change change3 = insert("repo", ins3);
ChangeInserter ins4 = newChangeWithTopic(repo, "feature2-fixup");
- Change change4 = insert(repo, ins4);
+ Change change4 = insert("repo", ins4);
ChangeInserter ins5 = newChangeWithTopic(repo, "https://gerrit.local");
- Change change5 = insert(repo, ins5);
+ Change change5 = insert("repo", ins5);
ChangeInserter ins6 = newChangeWithTopic(repo, "git_gerrit_training");
- Change change6 = insert(repo, ins6);
+ Change change6 = insert("repo", ins6);
- Change change_no_topic = insert(repo, newChange(repo));
+ Change changeNoTopic = insert("repo", newChange(repo));
assertQuery("intopic:foo");
assertQuery("intopic:feature1", change1);
@@ -972,8 +996,8 @@
assertQuery("intopic:feature2", change4, change3, change2);
assertQuery("intopic:fixup", change4);
assertQuery("intopic:gerrit", change6, change5);
- assertQuery("topic:\"\"", change_no_topic);
- assertQuery("intopic:\"\"", change_no_topic);
+ assertQuery("topic:\"\"", changeNoTopic);
+ assertQuery("intopic:\"\"", changeNoTopic);
assume().that(getSchema().hasField(ChangeField.PREFIX_TOPIC)).isTrue();
assertQuery("prefixtopic:feature", change4, change2, change1);
@@ -983,16 +1007,16 @@
@Test
public void byTopicRegex() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChangeWithTopic(repo, "feature1");
- Change change1 = insert(repo, ins1);
+ Change change1 = insert("repo", ins1);
ChangeInserter ins2 = newChangeWithTopic(repo, "Cherrypick-feature1");
- Change change2 = insert(repo, ins2);
+ Change change2 = insert("repo", ins2);
ChangeInserter ins3 = newChangeWithTopic(repo, "feature1-fixup");
- Change change3 = insert(repo, ins3);
+ Change change3 = insert("repo", ins3);
assertQuery("intopic:^feature1.*", change3, change1);
assertQuery("intopic:{^.*feature1$}", change2, change1);
@@ -1000,13 +1024,13 @@
@Test
public void byMessageExact() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("one").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 = repo.parseBody(repo.commit().message("A great \"fix\" to my bug").create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
assertQuery("message:foo");
assertQuery("message:one", change1);
@@ -1017,16 +1041,16 @@
@Test
public void byMessageRegEx() throws Exception {
assume().that(getSchema().hasField(ChangeField.COMMIT_MESSAGE_EXACT)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("aaaabcc").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("aaaacc").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 = repo.parseBody(repo.commit().message("Title\n\nHELLO WORLD").create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
RevCommit commit4 =
repo.parseBody(repo.commit().message("Title\n\nfoobar hello WORLD").create());
- Change change4 = insert(repo, newChangeForCommit(repo, commit4));
+ Change change4 = insert("repo", newChangeForCommit(repo, commit4));
assertQuery("message:\"^aaaa(b|c)*\"", change2, change1);
assertQuery("message:\"^aaaa(c)*c.*\"", change2);
@@ -1038,7 +1062,7 @@
@Test
public void bySubject() throws Exception {
assume().that(getSchema().hasField(ChangeField.SUBJECT_SPEC)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 =
repo.parseBody(
repo.commit()
@@ -1047,7 +1071,7 @@
+ "Message body\n\n"
+ "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b920")
.create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 =
repo.parseBody(
repo.commit()
@@ -1056,7 +1080,7 @@
+ "Message body for another commit\n\n"
+ "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
.create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 =
repo.parseBody(
repo.commit()
@@ -1065,7 +1089,7 @@
+ "Last message body\n\n"
+ "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
.create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
assertQuery("subject:First", change1);
assertQuery("subject:Second", change2);
@@ -1075,7 +1099,7 @@
assertQuery("subject:body");
change1 =
newPatchSet(
- repo,
+ "repo",
change1,
user,
Optional.of("Rework of commit with test subject\n\n" + "Message body\n\n"));
@@ -1087,7 +1111,7 @@
@Test
public void bySubjectPrefix() throws Exception {
assume().that(getSchema().hasField(ChangeField.PREFIX_SUBJECT_SPEC)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 =
repo.parseBody(
repo.commit()
@@ -1096,7 +1120,7 @@
+ "Message body\n\n"
+ "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b920")
.create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 =
repo.parseBody(
repo.commit()
@@ -1105,7 +1129,7 @@
+ "Message body for another commit\n\n"
+ "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
.create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 =
repo.parseBody(
repo.commit()
@@ -1114,7 +1138,7 @@
+ "Last message body\n\n"
+ "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
.create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
assertQuery("prefixsubject:\"[FOO\"", change3, change1);
assertQuery("prefixsubject:\"[BAR\"", change2);
@@ -1124,7 +1148,7 @@
assertQuery("prefixsubject:FOO");
change1 =
newPatchSet(
- repo,
+ "repo",
change1,
user,
Optional.of("[BAR123] Rework of commit with test subject\n\n" + "Message body\n\n"));
@@ -1134,11 +1158,11 @@
@Test
public void fullTextWithNumbers() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("12345 67890").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("12346 67891").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
assertQuery("message:1234");
assertQuery("message:12345", change1);
@@ -1147,13 +1171,13 @@
@Test
public void fullTextMultipleTerms() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("Signed-off: owner").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("Signed by owner").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 = repo.parseBody(repo.commit().message("This change is off").create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
assertQuery("message:\"Signed-off: owner\"", change1);
assertQuery("message:\"Signed\"", change2, change1);
@@ -1162,11 +1186,11 @@
@Test
public void byMessageMixedCase() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("Hello gerrit").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("Hello Gerrit").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
assertQuery("message:gerrit", change2, change1);
assertQuery("message:Gerrit", change2, change1);
@@ -1174,16 +1198,16 @@
@Test
public void byMessageSubstring() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("https://gerrit.local").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
assertQuery("message:gerrit", change1);
}
@Test
public void byLabel() throws Exception {
- accountManager.authenticate(authRequestFactory.createForUser("anotheruser"));
- TestRepository<Repo> repo = createProject("repo");
+ Account.Id anotherUser = createAccount("anotheruser");
+ repo = createAndOpenProject("repo");
ChangeInserter ins = newChange(repo);
ChangeInserter ins2 = newChange(repo);
ChangeInserter ins3 = newChange(repo);
@@ -1191,24 +1215,24 @@
ChangeInserter ins5 = newChange(repo);
ChangeInserter ins6 = newChange(repo);
- Change reviewMinus2Change = insert(repo, ins);
+ Change reviewMinus2Change = insert("repo", ins);
gApi.changes().id(reviewMinus2Change.getId().get()).current().review(ReviewInput.reject());
- Change reviewMinus1Change = insert(repo, ins2);
+ Change reviewMinus1Change = insert("repo", ins2);
gApi.changes().id(reviewMinus1Change.getId().get()).current().review(ReviewInput.dislike());
- Change noLabelChange = insert(repo, ins3);
+ Change noLabelChange = insert("repo", ins3);
- Change reviewPlus1Change = insert(repo, ins4);
+ Change reviewPlus1Change = insert("repo", ins4);
gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
- Change reviewTwoPlus1Change = insert(repo, ins5);
+ Change reviewTwoPlus1Change = insert("repo", ins5);
gApi.changes().id(reviewTwoPlus1Change.getId().get()).current().review(ReviewInput.recommend());
requestContext.setContext(newRequestContext(createAccount("user1")));
gApi.changes().id(reviewTwoPlus1Change.getId().get()).current().review(ReviewInput.recommend());
requestContext.setContext(newRequestContext(userId));
- Change reviewPlus2Change = insert(repo, ins6);
+ Change reviewPlus2Change = insert("repo", ins6);
gApi.changes().id(reviewPlus2Change.getId().get()).current().review(ReviewInput.approve());
Map<String, Short> m =
@@ -1272,9 +1296,15 @@
assertQuery("label:Code-Review<=-2", reviewMinus2Change);
assertQuery("label:Code-Review<-2");
- assertQuery("label:Code-Review=+1,anotheruser");
- assertQuery("label:Code-Review=+1,user", reviewTwoPlus1Change, reviewPlus1Change);
- assertQuery("label:Code-Review=+1,user=user", reviewTwoPlus1Change, reviewPlus1Change);
+ assertQuery("label:Code-Review=+1," + anotherUser);
+ assertQuery(
+ String.format("label:Code-Review=+1,%s", userAccount.preferredEmail()),
+ reviewTwoPlus1Change,
+ reviewPlus1Change);
+ assertQuery(
+ String.format("label:Code-Review=+1,user=%s", userAccount.preferredEmail()),
+ reviewTwoPlus1Change,
+ reviewPlus1Change);
assertQuery("label:Code-Review=+1,Administrators", reviewTwoPlus1Change, reviewPlus1Change);
assertQuery(
"label:Code-Review=+1,group=Administrators", reviewTwoPlus1Change, reviewPlus1Change);
@@ -1341,9 +1371,8 @@
@Test
public void byLabelMulti() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Project.NameKey project =
- Project.nameKey(repo.getRepository().getDescription().getRepositoryName());
+ Project.NameKey project = Project.nameKey("repo");
+ repo = createAndOpenProject(project.get());
LabelType verified =
label(LabelId.VERIFIED, value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
@@ -1370,25 +1399,25 @@
ChangeInserter ins5 = newChange(repo);
// CR+1
- Change reviewCRplus1 = insert(repo, ins);
+ Change reviewCRplus1 = insert(project.get(), ins);
gApi.changes().id(reviewCRplus1.getId().get()).current().review(ReviewInput.recommend());
// CR+2
- Change reviewCRplus2 = insert(repo, ins2);
+ Change reviewCRplus2 = insert(project.get(), ins2);
gApi.changes().id(reviewCRplus2.getId().get()).current().review(ReviewInput.approve());
// CR+1 VR+1
- Change reviewCRplus1VRplus1 = insert(repo, ins3);
+ Change reviewCRplus1VRplus1 = insert(project.get(), ins3);
gApi.changes().id(reviewCRplus1VRplus1.getId().get()).current().review(ReviewInput.recommend());
gApi.changes().id(reviewCRplus1VRplus1.getId().get()).current().review(reviewVerified);
// CR+2 VR+1
- Change reviewCRplus2VRplus1 = insert(repo, ins4);
+ Change reviewCRplus2VRplus1 = insert(project.get(), ins4);
gApi.changes().id(reviewCRplus2VRplus1.getId().get()).current().review(ReviewInput.approve());
gApi.changes().id(reviewCRplus2VRplus1.getId().get()).current().review(reviewVerified);
// VR+1
- Change reviewVRplus1 = insert(repo, ins5);
+ Change reviewVRplus1 = insert(project.get(), ins5);
gApi.changes().id(reviewVRplus1.getId().get()).current().review(reviewVerified);
assertQuery("label:Code-Review=+1", reviewCRplus1VRplus1, reviewCRplus1);
@@ -1407,28 +1436,28 @@
@Test
public void byLabelNotOwner() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins = newChange(repo);
Account.Id user1 = createAccount("user1");
- Change reviewPlus1Change = insert(repo, ins);
+ Change reviewPlus1Change = insert("repo", ins);
// post a review with user1
requestContext.setContext(newRequestContext(user1));
gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
- assertQuery("label:Code-Review=+1,user=user1", reviewPlus1Change);
+ assertQuery("label:Code-Review=+1,user=" + user1, reviewPlus1Change);
assertQuery("label:Code-Review=+1,owner");
}
@Test
public void byLabelNonUploader() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins = newChange(repo);
Account.Id user1 = createAccount("user1");
// create a change with "user"
- Change reviewPlus1Change = insert(repo, ins);
+ Change reviewPlus1Change = insert("repo", ins);
// add a +1 vote with "user". Query doesn't match since voter is the uploader.
gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
@@ -1467,8 +1496,8 @@
@Test
public void byLabelGroup() throws Exception {
Account.Id user1 = createAccount("user1");
- createAccount("user2");
- TestRepository<Repo> repo = createProject("repo");
+ Account.Id user2 = createAccount("user2");
+ repo = createAndOpenProject("repo");
// create group and add users
String g1 = createGroup("group1", "Administrators");
@@ -1477,7 +1506,7 @@
gApi.groups().id(g2).addMembers("user2");
// create a change
- Change change1 = insert(repo, newChange(repo), user1);
+ Change change1 = insert("repo", newChange(repo), user1);
// post a review with user1
requestContext.setContext(newRequestContext(user1));
@@ -1490,8 +1519,8 @@
requestContext.setContext(newRequestContext(userId));
assertQuery("label:Code-Review=+1,group1", change1);
assertQuery("label:Code-Review=+1,group=group1", change1);
- assertQuery("label:Code-Review=+1,user=user1", change1);
- assertQuery("label:Code-Review=+1,user=user2");
+ assertQuery("label:Code-Review=+1,user=" + user1, change1);
+ assertQuery("label:Code-Review=+1,user=" + user2);
assertQuery("label:Code-Review=+1,group=group2");
}
@@ -1499,7 +1528,7 @@
public void byLabelExternalGroup() throws Exception {
Account.Id user1 = createAccount("user1");
Account.Id user2 = createAccount("user2");
- TestRepository<InMemoryRepositoryManager.Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
// create group and add users
AccountGroup.UUID external_group1 = AccountGroup.uuid("testbackend:group1");
@@ -1511,8 +1540,8 @@
testGroupBackend.setMembershipsOf(
user2, new ListGroupMembership(ImmutableList.of(external_group2)));
- Change change1 = insert(repo, newChange(repo), user1);
- Change change2 = insert(repo, newChange(repo), user1);
+ Change change1 = insert("repo", newChange(repo), user1);
+ Change change2 = insert("repo", newChange(repo), user1);
// post a review with user1 and other_user
requestContext.setContext(newRequestContext(user1));
@@ -1528,18 +1557,18 @@
assertQuery("label:Code-Review=+1," + external_group1.get(), change1);
assertQuery("label:Code-Review=+1,group=" + external_group1.get(), change1);
- assertQuery("label:Code-Review=+1,user=user1", change1);
- assertQuery("label:Code-Review=+1,user=user2");
+ assertQuery("label:Code-Review=+1,user=" + user1, change1);
+ assertQuery("label:Code-Review=+1,user=" + user2);
assertQuery("label:Code-Review=+1,group=" + external_group2.get());
}
@Test
public void limit() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Change last = null;
int n = 5;
for (int i = 0; i < n; i++) {
- last = insert(repo, newChange(repo));
+ last = insert("repo", newChange(repo));
}
for (int i = 1; i <= n + 2; i++) {
@@ -1564,10 +1593,10 @@
@Test
public void start() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
List<Change> changes = new ArrayList<>();
for (int i = 0; i < 2; i++) {
- changes.add(insert(repo, newChange(repo)));
+ changes.add(insert("repo", newChange(repo)));
}
assertQuery("status:new", changes.get(1), changes.get(0));
@@ -1584,10 +1613,10 @@
@Test
public void startWithLimit() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
List<Change> changes = new ArrayList<>();
for (int i = 0; i < 3; i++) {
- changes.add(insert(repo, newChange(repo)));
+ changes.add(insert("repo", newChange(repo)));
}
assertQuery("status:new limit:2", changes.get(2), changes.get(1));
@@ -1598,8 +1627,8 @@
@Test
public void maxPages() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
QueryRequest query = newQuery("status:new").withLimit(10);
assertQuery(query, change);
@@ -1614,12 +1643,12 @@
@Test
public void updateOrder() throws Exception {
resetTimeWithClockStep(2, MINUTES);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
List<ChangeInserter> inserters = new ArrayList<>();
List<Change> changes = new ArrayList<>();
for (int i = 0; i < 5; i++) {
inserters.add(newChange(repo));
- changes.add(insert(repo, inserters.get(i)));
+ changes.add(insert("repo", inserters.get(i)));
}
for (int i : ImmutableList.of(2, 0, 1, 4, 3)) {
@@ -1641,10 +1670,10 @@
@Test
public void updatedOrder() throws Exception {
resetTimeWithClockStep(1, SECONDS);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins1 = newChange(repo);
- Change change1 = insert(repo, ins1);
- Change change2 = insert(repo, newChange(repo));
+ Change change1 = insert("repo", ins1);
+ Change change2 = insert("repo", newChange(repo));
assertThat(lastUpdatedMs(change1)).isLessThan(lastUpdatedMs(change2));
assertQuery("status:new", change2, change1);
@@ -1662,12 +1691,12 @@
@Test
public void filterOutMoreThanOnePageOfResults() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo), userId);
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
for (int i = 0; i < 5; i++) {
- insert(repo, newChange(repo), user2);
+ insert("repo", newChange(repo), user2);
}
assertQuery("status:new ownerin:Administrators", change);
@@ -1676,11 +1705,11 @@
@Test
public void filterOutAllResults() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
for (int i = 0; i < 5; i++) {
- insert(repo, newChange(repo), user2);
+ insert("repo", newChange(repo), user2);
}
assertQuery("status:new ownerin:Administrators");
@@ -1689,8 +1718,8 @@
@Test
public void byFileExact() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("file:file");
assertQuery("file:dir", change);
@@ -1702,8 +1731,8 @@
@Test
public void byFileRegex() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("file:.*file.*");
assertQuery("file:^file.*"); // Whole path only.
@@ -1712,8 +1741,8 @@
@Test
public void byPathExact() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("path:file");
assertQuery("path:dir");
@@ -1725,8 +1754,8 @@
@Test
public void byPathRegex() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("path:.*file.*");
assertQuery("path:^dir.file.*", change);
@@ -1734,12 +1763,12 @@
@Test
public void byExtension() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWithFiles(repo, "foo.h", "foo.cc"));
- Change change2 = insert(repo, newChangeWithFiles(repo, "bar.H", "bar.CC"));
- Change change3 = insert(repo, newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
- Change change4 = insert(repo, newChangeWithFiles(repo, "Quux.java", "foo"));
- Change change5 = insert(repo, newChangeWithFiles(repo, "foo"));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWithFiles(repo, "foo.h", "foo.cc"));
+ Change change2 = insert("repo", newChangeWithFiles(repo, "bar.H", "bar.CC"));
+ Change change3 = insert("repo", newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
+ Change change4 = insert("repo", newChangeWithFiles(repo, "Quux.java", "foo"));
+ Change change5 = insert("repo", newChangeWithFiles(repo, "foo"));
assertQuery("extension:java", change4);
assertQuery("ext:java", change4);
@@ -1755,14 +1784,14 @@
@Test
public void byOnlyExtensions() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWithFiles(repo, "foo.h", "foo.cc", "bar.cc"));
- Change change2 = insert(repo, newChangeWithFiles(repo, "bar.H", "bar.CC", "foo.H"));
- Change change3 = insert(repo, newChangeWithFiles(repo, "foo.CC", "bar.cc"));
- Change change4 = insert(repo, newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
- Change change5 = insert(repo, newChangeWithFiles(repo, "Quux.java"));
- Change change6 = insert(repo, newChangeWithFiles(repo, "foo.txt", "foo"));
- Change change7 = insert(repo, newChangeWithFiles(repo, "foo"));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWithFiles(repo, "foo.h", "foo.cc", "bar.cc"));
+ Change change2 = insert("repo", newChangeWithFiles(repo, "bar.H", "bar.CC", "foo.H"));
+ Change change3 = insert("repo", newChangeWithFiles(repo, "foo.CC", "bar.cc"));
+ Change change4 = insert("repo", newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
+ Change change5 = insert("repo", newChangeWithFiles(repo, "Quux.java"));
+ Change change6 = insert("repo", newChangeWithFiles(repo, "foo.txt", "foo"));
+ Change change7 = insert("repo", newChangeWithFiles(repo, "foo"));
// case doesn't matter
assertQuery("onlyextensions:cc,h", change4, change2, change1);
@@ -1802,23 +1831,23 @@
@Test
public void byFooter() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("Test\n\nfoo: baz").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar\nfoo:baz").create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
RevCommit commit4 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar=baz").create());
- Change change4 = insert(repo, newChangeForCommit(repo, commit4));
+ Change change4 = insert("repo", newChangeForCommit(repo, commit4));
// create a changes with lines that look like footers, but which are not
RevCommit commit5 =
repo.parseBody(
repo.commit().message("Test\n\nfoo: bar\n\nfoo=bar").insertChangeId().create());
- Change change5 = insert(repo, newChangeForCommit(repo, commit5));
+ Change change5 = insert("repo", newChangeForCommit(repo, commit5));
RevCommit commit6 = repo.parseBody(repo.commit().message("Test\n\na=b: c").create());
- insert(repo, newChangeForCommit(repo, commit6));
+ insert("repo", newChangeForCommit(repo, commit6));
// matching by 'key=value' works
assertQuery("footer:foo=bar", change3, change1);
@@ -1852,15 +1881,15 @@
@Test
public void byFooterName() throws Exception {
assume().that(getSchema().hasField(ChangeField.FOOTER_NAME)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("Test\n\nBaR: baz").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
// create a changes with lines that look like footers, but which are not
RevCommit commit6 = repo.parseBody(repo.commit().message("Test\n\na=b: c").create());
- insert(repo, newChangeForCommit(repo, commit6));
+ insert("repo", newChangeForCommit(repo, commit6));
// matching by 'key=value' works
assertQuery("hasfooter:foo", change1);
@@ -1872,14 +1901,14 @@
@Test
public void byDirectory() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWithFiles(repo, "src/foo.h", "src/foo.cc"));
- Change change2 = insert(repo, newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWithFiles(repo, "src/foo.h", "src/foo.cc"));
+ Change change2 = insert("repo", newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
Change change3 =
- insert(repo, newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
- Change change4 = insert(repo, newChangeWithFiles(repo, "a.txt"));
- Change change5 = insert(repo, newChangeWithFiles(repo, "a/b/c/d/e/foo.txt"));
- Change change6 = insert(repo, newChangeWithFiles(repo, "all/caps/DIRECTORY/file.txt"));
+ insert("repo", newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
+ Change change4 = insert("repo", newChangeWithFiles(repo, "a.txt"));
+ Change change5 = insert("repo", newChangeWithFiles(repo, "a/b/c/d/e/foo.txt"));
+ Change change6 = insert("repo", newChangeWithFiles(repo, "all/caps/DIRECTORY/file.txt"));
// matching by directory prefix works
assertQuery("directory:src", change2, change1);
@@ -1940,10 +1969,10 @@
@Test
public void byDirectoryRegex() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
Change change2 =
- insert(repo, newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
+ insert("repo", newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
// match by regexp
assertQuery("directory:^.*va.*", change1);
@@ -1953,9 +1982,9 @@
@Test
public void byComment() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ChangeInserter ins = newChange(repo);
- Change change = insert(repo, ins);
+ Change change = insert("repo", ins);
ReviewInput input = new ReviewInput();
input.message = "toplevel";
@@ -1983,11 +2012,11 @@
public void byAge() throws Exception {
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
long startMs = TestTimeUtil.START.toEpochMilli();
- Change change1 = insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs));
+ Change change1 = insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs));
Change change2 =
- insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
+ insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
// Stop time so age queries use the same endpoint.
TestTimeUtil.setClockStep(0, MILLISECONDS);
@@ -2024,11 +2053,11 @@
public void byBeforeUntil() throws Exception {
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
long startMs = TestTimeUtil.START.toEpochMilli();
- Change change1 = insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs));
+ Change change1 = insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs));
Change change2 =
- insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
+ insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
TestTimeUtil.setClockStep(0, MILLISECONDS);
// Change1 was last updated on 2009-09-30 21:00:00 -0000
@@ -2076,11 +2105,11 @@
public void byAfterSince() throws Exception {
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
long startMs = TestTimeUtil.START.toEpochMilli();
- Change change1 = insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs));
+ Change change1 = insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs));
Change change2 =
- insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
+ insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
TestTimeUtil.setClockStep(0, MILLISECONDS);
// Change1 was last updated on 2009-09-30 21:00:00 -0000
@@ -2120,12 +2149,12 @@
// Stop the clock, will set time to specific test values.
resetTimeWithClockStep(0, MILLISECONDS);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
long startMs = TestTimeUtil.START.toEpochMilli();
TestTimeUtil.setClock(new Timestamp(startMs));
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
TestTimeUtil.setClock(new Timestamp(startMs + thirtyHoursInMs));
submit(change3);
@@ -2180,12 +2209,12 @@
// Stop the clock, will set time to specific test values.
resetTimeWithClockStep(0, MILLISECONDS);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
long startMs = TestTimeUtil.START.toEpochMilli();
TestTimeUtil.setClock(new Timestamp(startMs));
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
assertThat(TimeUtil.nowMs()).isEqualTo(startMs);
TestTimeUtil.setClock(new Timestamp(startMs + thirtyHoursInMs));
@@ -2250,13 +2279,13 @@
// Stop the clock, will set time to specific test values.
resetTimeWithClockStep(0, MILLISECONDS);
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
long startMs = TestTimeUtil.START.toEpochMilli();
TestTimeUtil.setClock(new Timestamp(startMs));
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
TestTimeUtil.setClock(new Timestamp(startMs + thirtyHoursInMs));
submit(change2);
@@ -2283,15 +2312,15 @@
@Test
public void bySize() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
// added = 3, deleted = 0, delta = 3
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "foo\n\foo\nfoo").create());
// added = 0, deleted = 2, delta = 2
RevCommit commit2 = repo.parseBody(repo.commit().parent(commit1).add("file1", "foo").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
assertQuery("added:>4");
assertQuery("-added:<=4");
@@ -2339,9 +2368,9 @@
}
private List<Change> setUpHashtagChanges() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
addHashtags(change1.getId(), "foo", "aaa-bbb-ccc");
addHashtags(change2.getId(), "foo", "bar", "a tag", "ACamelCaseTag");
@@ -2388,10 +2417,10 @@
@Test
public void byHashtagRegex() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
addHashtags(change1.getId(), "feature1");
addHashtags(change1.getId(), "trending");
addHashtags(change2.getId(), "Cherrypick-feature1");
@@ -2404,27 +2433,27 @@
@Test
public void byDefault() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
- Change change1 = insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
RevCommit commit2 = repo.parseBody(repo.commit().message("foosubject").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 = repo.parseBody(repo.commit().add("Foo.java", "foo contents").create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
ChangeInserter ins4 = newChange(repo);
- Change change4 = insert(repo, ins4);
+ Change change4 = insert("repo", ins4);
ReviewInput ri4 = new ReviewInput();
ri4.message = "toplevel";
ri4.labels = ImmutableMap.of("Code-Review", (short) 1);
gApi.changes().id(change4.getId().get()).current().review(ri4);
ChangeInserter ins5 = newChangeWithTopic(repo, "feature5");
- Change change5 = insert(repo, ins5);
+ Change change5 = insert("repo", ins5);
- Change change6 = insert(repo, newChangeForBranch(repo, "branch6"));
+ Change change6 = insert("repo", newChangeForBranch(repo, "branch6"));
assertQuery(change1.getId().get(), change1);
assertQuery(ChangeTriplet.format(change1), change1);
@@ -2445,18 +2474,18 @@
@Test
public void byDefaultWithCommitPrefix() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit = repo.parseBody(repo.commit().message("message").create());
- Change change = insert(repo, newChangeForCommit(repo, commit));
+ Change change = insert("repo", newChangeForCommit(repo, commit));
assertQuery(commit.getId().getName().substring(0, 6), change);
}
@Test
public void visible() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChangePrivate(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChangePrivate(repo));
String q = "project:repo";
@@ -2510,8 +2539,8 @@
// Switch to user3
requestContext.setContext(newRequestContext(user3));
- Change change3 = insert(repo, newChange(repo), user3);
- Change change4 = insert(repo, newChangePrivate(repo), user3);
+ Change change3 = insert("repo", newChange(repo), user3);
+ Change change4 = insert("repo", newChangePrivate(repo), user3);
// User3 can see both their changes and the first user's change
assertQuery(q + " visibleto:" + user3.get(), change4, change3, change1);
@@ -2551,9 +2580,9 @@
@Test
public void visibleToSelf() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
gApi.changes().id(change2.getChangeId()).setPrivate(true, "private");
@@ -2569,16 +2598,12 @@
@Test
public void byCommentBy() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- int user2 =
- accountManager
- .authenticate(authRequestFactory.createForUser("anotheruser"))
- .getAccountId()
- .get();
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Account.Id user2 = createAccount("anotheruser");
ReviewInput input = new ReviewInput();
input.message = "toplevel";
ReviewInput.CommentInput comment = new ReviewInput.CommentInput();
@@ -2599,8 +2624,8 @@
public void bySubmitRuleResult() throws Exception {
try (Registration registration =
extensionRegistry.newRegistration().add(new FakeSubmitRule())) {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
// The fake submit rule exports its ruleName as "FakeSubmitRule"
assertQuery("rule:FakeSubmitRule");
@@ -2619,17 +2644,17 @@
public void byNonExistingSubmitRule_returnsEmpty() throws Exception {
try (Registration registration =
extensionRegistry.newRegistration().add(new FakeSubmitRule())) {
- TestRepository<Repo> repo = createProject("repo");
- insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ insert("repo", newChange(repo));
assertQuery("rule:non-existent-rule");
}
}
@Test
public void byHasDraft() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
assertQuery("has:draft");
@@ -2661,8 +2686,8 @@
*/
public void byHasDraftExcludesZombieDrafts() throws Exception {
Project.NameKey project = Project.nameKey("repo");
- TestRepository<Repo> repo = createProject(project.get());
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject(project.get());
+ Change change = insert("repo", newChange(repo));
Change.Id id = change.getId();
DraftInput in = new DraftInput();
@@ -2674,7 +2699,7 @@
assertQuery("has:draft", change);
assertQuery("commentby:" + userId);
- try (TestRepository<Repo> allUsers =
+ try (TestRepository<Repository> allUsers =
new TestRepository<>(repoManager.openRepository(allUsersName))) {
Ref draftsRef = allUsers.getRepository().exactRef(RefNames.refsDraftComments(id, userId));
assertThat(draftsRef).isNotNull();
@@ -2698,15 +2723,15 @@
@Test
public void byHasDraftWithManyDrafts() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Change[] changesWithDrafts = new Change[30];
// unrelated change not shown in the result.
- insert(repo, newChange(repo));
+ insert("repo", newChange(repo));
for (int i = 0; i < changesWithDrafts.length; i++) {
// put the changes in reverse order since this is the order we receive them from the index.
- changesWithDrafts[changesWithDrafts.length - 1 - i] = insert(repo, newChange(repo));
+ changesWithDrafts[changesWithDrafts.length - 1 - i] = insert("repo", newChange(repo));
DraftInput in = new DraftInput();
in.line = 1;
in.message = "nit: trailing whitespace";
@@ -2726,10 +2751,10 @@
@Test
public void byStarredBy() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ insert("repo", newChange(repo));
gApi.accounts().self().starChange(change1.getId().toString());
gApi.accounts().self().starChange(change2.getId().toString());
@@ -2745,8 +2770,8 @@
@Test
public void byStar() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
@@ -2761,11 +2786,11 @@
@Test
public void byStarWithManyStars() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Change[] changesWithDrafts = new Change[30];
for (int i = 0; i < changesWithDrafts.length; i++) {
// put the changes in reverse order since this is the order we receive them from the index.
- changesWithDrafts[changesWithDrafts.length - 1 - i] = insert(repo, newChange(repo));
+ changesWithDrafts[changesWithDrafts.length - 1 - i] = insert("repo", newChange(repo));
// star the change
gApi.accounts()
@@ -2779,12 +2804,12 @@
@Test
public void byFrom() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
- Change change2 = insert(repo, newChange(repo), user2);
+ Change change2 = insert("repo", newChange(repo), user2);
ReviewInput input = new ReviewInput();
input.message = "toplevel";
@@ -2800,7 +2825,7 @@
@Test
public void conflicts() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 =
repo.parseBody(
repo.commit()
@@ -2812,10 +2837,10 @@
RevCommit commit3 =
repo.parseBody(repo.commit().add("dir/file2", "contents2 different").create());
RevCommit commit4 = repo.parseBody(repo.commit().add("file4", "contents4").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
- Change change4 = insert(repo, newChangeForCommit(repo, commit4));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
+ Change change4 = insert("repo", newChangeForCommit(repo, commit4));
assertQuery("conflicts:" + change1.getId().get(), change3);
assertQuery("conflicts:" + change2.getId().get());
@@ -2828,11 +2853,12 @@
name = "change.mergeabilityComputationBehavior",
value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
public void mergeable() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ assume().that(getSchema().hasField(ChangeField.MERGEABLE_SPEC)).isTrue();
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
RevCommit commit2 = repo.parseBody(repo.commit().add("file1", "contents2").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
assertQuery("conflicts:" + change1.getId().get(), change2);
assertQuery("conflicts:" + change2.getId().get(), change1);
@@ -2856,9 +2882,9 @@
@Test
public void cherrypick() throws Exception {
assume().that(getSchema().hasField(ChangeField.CHERRY_PICK_SPEC)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newCherryPickChange(repo, "foo", change1.currentPatchSetId()));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newCherryPickChange(repo, "foo", change1.currentPatchSetId()));
assertQuery("is:cherrypick", change2);
assertQuery("-is:cherrypick", change1);
@@ -2867,14 +2893,14 @@
@Test
public void merge() throws Exception {
assume().that(getSchema().hasField(ChangeField.MERGE_SPEC)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
RevCommit commit2 = repo.parseBody(repo.commit().add("file1", "contents2").create());
RevCommit commit3 =
repo.parseBody(repo.commit().parent(commit2).add("file1", "contents3").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
RevCommit mergeCommit =
repo.branch("master")
.commit()
@@ -2883,7 +2909,7 @@
.parent(commit3)
.insertChangeId()
.create();
- Change mergeChange = insert(repo, newChangeForCommit(repo, mergeCommit));
+ Change mergeChange = insert("repo", newChangeForCommit(repo, mergeCommit));
assertQuery("status:open is:merge", mergeChange);
assertQuery("status:open -is:merge", change3, change2, change1);
@@ -2893,10 +2919,10 @@
@Test
public void reviewedBy() throws Exception {
resetTimeWithClockStep(2, MINUTES);
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
gApi.changes().id(change1.getId().get()).current().review(new ReviewInput().message("comment"));
@@ -2907,7 +2933,7 @@
gApi.changes().id(change2.getId().get()).current().review(new ReviewInput().message("comment"));
PatchSet.Id ps3_1 = change3.currentPatchSetId();
- change3 = newPatchSet(repo, change3, user, /* message= */ Optional.empty());
+ change3 = newPatchSet("repo", change3, user, /* message= */ Optional.empty());
assertThat(change3.currentPatchSetId()).isNotEqualTo(ps3_1);
// Response to previous patch set still counts as reviewing.
gApi.changes()
@@ -2934,11 +2960,11 @@
@Test
public void reviewerAndCc() throws Exception {
Account.Id user1 = createAccount("user1");
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
- insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
+ insert("repo", newChange(repo));
ReviewerInput rin = new ReviewerInput();
rin.reviewer = user1.toString();
@@ -2965,11 +2991,11 @@
@Test
public void byReviewed() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Account.Id otherUser =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
assertQuery("is:reviewed");
assertQuery("status:reviewed");
@@ -2993,11 +3019,11 @@
accountManager.authenticate(authRequestFactory.createForUser("user2")).getAccountId();
Account.Id user3 =
accountManager.authenticate(authRequestFactory.createForUser("user3")).getAccountId();
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
ReviewerInput rin = new ReviewerInput();
rin.reviewer = user1.toString();
@@ -3037,7 +3063,7 @@
@Test
public void reviewerAndCcByEmail() throws Exception {
Project.NameKey project = Project.nameKey("repo");
- TestRepository<Repo> repo = createProject(project.get());
+ repo = createAndOpenProject(project.get());
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
gApi.projects().name(project.get()).config(conf);
@@ -3045,9 +3071,9 @@
String userByEmail = "un.registered@reviewer.com";
String userByEmailWithName = "John Doe <" + userByEmail + ">";
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ insert("repo", newChange(repo));
ReviewerInput rin = new ReviewerInput();
rin.reviewer = userByEmailWithName;
@@ -3070,16 +3096,16 @@
@Test
public void reviewerAndCcByEmailWithQueryForDifferentUser() throws Exception {
Project.NameKey project = Project.nameKey("repo");
- TestRepository<Repo> repo = createProject(project.get());
+ repo = createAndOpenProject(project.get());
ConfigInput conf = new ConfigInput();
conf.enableReviewerByEmail = InheritableBoolean.TRUE;
gApi.projects().name(project.get()).config(conf);
String userByEmail = "John Doe <un.registered@reviewer.com>";
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- insert(repo, newChange(repo));
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ insert("repo", newChange(repo));
ReviewerInput rin = new ReviewerInput();
rin.reviewer = userByEmail;
@@ -3098,9 +3124,9 @@
@Test
public void submitRecords() throws Exception {
Account.Id user1 = createAccount("user1");
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
gApi.changes().id(change1.getId().get()).current().review(ReviewInput.approve());
requestContext.setContext(newRequestContext(user1));
@@ -3111,7 +3137,7 @@
assertQuery("-is:submittable", change2);
assertQuery("label:CodE-RevieW=ok", change1);
- assertQuery("label:CodE-RevieW=ok,user=user", change1);
+ assertQuery("label:CodE-RevieW=ok,user=" + userAccount.preferredEmail(), change1);
assertQuery("label:CodE-RevieW=ok,Administrators", change1);
assertQuery("label:CodE-RevieW=ok,group=Administrators", change1);
assertQuery("label:CodE-RevieW=ok,owner", change1);
@@ -3130,10 +3156,10 @@
public void hasEdit() throws Exception {
Account.Id user1 = createAccount("user1");
Account.Id user2 = createAccount("user2");
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
String changeId1 = change1.getKey().get();
- Change change2 = insert(repo, newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
String changeId2 = change2.getKey().get();
requestContext.setContext(newRequestContext(user1));
@@ -3154,10 +3180,10 @@
@Test
public void byUnresolved() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
- Change change3 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
+ Change change3 = insert("repo", newChange(repo));
// Change1 has one resolved comment (unresolvedcount = 0)
// Change2 has one unresolved comment (unresolvedcount = 1)
@@ -3185,13 +3211,13 @@
@Test
public void byCommitsOnBranchNotMerged() throws Exception {
- TestRepository<Repo> tr = createProject("repo");
- testByCommitsOnBranchNotMerged(tr, ImmutableSet.of());
+ createProject("repo");
+ testByCommitsOnBranchNotMerged("repo", ImmutableSet.of());
}
@Test
public void byCommitsOnBranchNotMergedSkipsMissingChanges() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
ObjectId missing =
repo.branch(PatchSet.id(Change.id(987654), 1).toRefName())
.commit()
@@ -3199,41 +3225,45 @@
.insertChangeId()
.create()
.copy();
- testByCommitsOnBranchNotMerged(repo, ImmutableSet.of(missing));
+ testByCommitsOnBranchNotMerged("repo", ImmutableSet.of(missing));
}
- private void testByCommitsOnBranchNotMerged(TestRepository<Repo> repo, Collection<ObjectId> extra)
+ private void testByCommitsOnBranchNotMerged(String repo, Collection<ObjectId> extra)
throws Exception {
int n = 10;
List<String> shas = new ArrayList<>(n + extra.size());
extra.forEach(i -> shas.add(i.name()));
List<Integer> expectedIds = new ArrayList<>(n);
BranchNameKey dest = null;
- for (int i = 0; i < n; i++) {
- ChangeInserter ins = newChange(repo);
- insert(repo, ins);
- if (dest == null) {
- dest = ins.getChange().getDest();
+ try (TestRepository<Repository> repository =
+ new TestRepository<>(repoManager.openRepository(Project.nameKey(repo)))) {
+ for (int i = 0; i < n; i++) {
+ ChangeInserter ins = newChange(repository);
+ insert("repo", ins);
+ if (dest == null) {
+ dest = ins.getChange().getDest();
+ }
+ shas.add(ins.getCommitId().name());
+ expectedIds.add(ins.getChange().getId().get());
}
- shas.add(ins.getCommitId().name());
- expectedIds.add(ins.getChange().getId().get());
}
-
- for (int i = 1; i <= 11; i++) {
- Iterable<ChangeData> cds =
- queryProvider.get().byCommitsOnBranchNotMerged(repo.getRepository(), dest, shas, i);
- Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
- String name = "limit " + i;
- assertWithMessage(name).that(ids).hasSize(n);
- assertWithMessage(name).that(ids).containsExactlyElementsIn(expectedIds);
+ try (Repository repository = repoManager.openRepository(Project.nameKey(repo))) {
+ for (int i = 1; i <= 11; i++) {
+ Iterable<ChangeData> cds =
+ queryProvider.get().byCommitsOnBranchNotMerged(repository, dest, shas, i);
+ Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
+ String name = "limit " + i;
+ assertWithMessage(name).that(ids).hasSize(n);
+ assertWithMessage(name).that(ids).containsExactlyElementsIn(expectedIds);
+ }
}
}
@Test
public void reindexIfStale() throws Exception {
Project.NameKey project = Project.nameKey("repo");
- TestRepository<Repo> repo = createProject(project.get());
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject(project.get());
+ Change change = insert("repo", newChange(repo));
String changeId = change.getKey().get();
Account.Id anotherUser = createAccount("another-user");
@@ -3256,14 +3286,14 @@
@Test
public void watched() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
- Change change1 = insert(repo, ins1);
+ createProject("repo");
+ ChangeInserter ins1 = newChangeWithStatus("repo", Change.Status.NEW);
+ Change change1 = insert("repo", ins1);
- TestRepository<Repo> repo2 = createProject("repo2");
+ createProject("repo2");
- ChangeInserter ins2 = newChangeWithStatus(repo2, Change.Status.NEW);
- insert(repo2, ins2);
+ ChangeInserter ins2 = newChangeWithStatus("repo2", Change.Status.NEW);
+ insert("repo2", ins2);
assertQuery("is:watched");
@@ -3283,17 +3313,17 @@
@Test
public void trackingid() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 =
repo.parseBody(repo.commit().message("Change one\n\nBug:QUERY123").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 =
repo.parseBody(repo.commit().message("Change two\n\nIssue: Issue 16038\n").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
RevCommit commit3 =
repo.parseBody(repo.commit().message("Change two\n\nGoogle-Bug-Id: b/16039\n").create());
- Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+ Change change3 = insert("repo", newChangeForCommit(repo, commit3));
assertQuery("tr:QUERY123", change1);
assertQuery("bug:QUERY123", change1);
@@ -3319,9 +3349,9 @@
@Test
public void revertOf() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
// Create two commits and revert second commit (initial commit can't be reverted)
- Change initial = insert(repo, newChange(repo));
+ Change initial = insert("repo", newChange(repo));
gApi.changes().id(initial.getChangeId()).current().review(ReviewInput.approve());
gApi.changes().id(initial.getChangeId()).current().submit();
@@ -3336,10 +3366,10 @@
@Test
public void submissionId() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
// create irrelevant change
- insert(repo, newChange(repo));
+ insert("repo", newChange(repo));
gApi.changes().id(change.getChangeId()).current().review(ReviewInput.approve());
gApi.changes().id(change.getChangeId()).current().submit();
String submissionId = gApi.changes().id(change.getChangeId()).get().submissionId;
@@ -3409,9 +3439,9 @@
return this;
}
- DashboardChangeState create(TestRepository<Repo> repo) throws Exception {
+ DashboardChangeState create(TestRepository<Repository> repo) throws Exception {
requestContext.setContext(newRequestContext(ownerId));
- Change change = insert(repo, newChange(repo), ownerId);
+ Change change = insert("repo", newChange(repo), ownerId);
id = change.getId();
ChangeApi cApi = gApi.changes().id(change.getChangeId());
if (assigneeId != null) {
@@ -3479,7 +3509,7 @@
@Test
public void dashboardHasUnpublishedDrafts() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Account.Id otherAccountId = createAccount("other");
DashboardChangeState hasUnpublishedDraft =
new DashboardChangeState(otherAccountId).draftCommentBy(user.getAccountId()).create(repo);
@@ -3497,7 +3527,7 @@
@Test
public void dashboardAssignedReviews() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Account.Id otherAccountId = createAccount("other");
DashboardChangeState otherOpenWip =
new DashboardChangeState(otherAccountId).wip().assignTo(user.getAccountId()).create(repo);
@@ -3519,12 +3549,12 @@
// Viewing another user's dashboard.
requestContext.setContext(newRequestContext(otherAccountId));
assertDashboardQuery(
- user.getUserName().get(), IndexPreloadingUtil.DASHBOARD_ASSIGNED_QUERY, otherOpenWip);
+ userId.toString(), IndexPreloadingUtil.DASHBOARD_ASSIGNED_QUERY, otherOpenWip);
}
@Test
public void dashboardWorkInProgressReviews() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
DashboardChangeState ownedOpenWip =
new DashboardChangeState(user.getAccountId()).wip().create(repo);
@@ -3539,7 +3569,7 @@
@Test
public void dashboardOutgoingReviews() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Account.Id otherAccountId = createAccount("other");
DashboardChangeState ownedOpenReviewable =
new DashboardChangeState(user.getAccountId()).create(repo);
@@ -3554,14 +3584,12 @@
// Viewing another user's dashboard.
requestContext.setContext(newRequestContext(otherAccountId));
assertDashboardQuery(
- user.getUserName().get(),
- IndexPreloadingUtil.DASHBOARD_OUTGOING_QUERY,
- ownedOpenReviewable);
+ userId.toString(), IndexPreloadingUtil.DASHBOARD_OUTGOING_QUERY, ownedOpenReviewable);
}
@Test
public void dashboardIncomingReviews() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Account.Id otherAccountId = createAccount("other");
DashboardChangeState reviewingReviewable =
new DashboardChangeState(otherAccountId).addReviewer(user.getAccountId()).create(repo);
@@ -3587,7 +3615,7 @@
// Viewing another user's dashboard.
requestContext.setContext(newRequestContext(otherAccountId));
assertDashboardQuery(
- user.getUserName().get(),
+ userId.toString(),
IndexPreloadingUtil.DASHBOARD_INCOMING_QUERY,
assignedReviewable,
reviewingReviewable);
@@ -3595,7 +3623,7 @@
@Test
public void dashboardRecentlyClosedReviews() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
Account.Id otherAccountId = createAccount("other");
DashboardChangeState mergedOwned =
new DashboardChangeState(user.getAccountId()).mergeBy(user.getAccountId()).create(repo);
@@ -3658,7 +3686,7 @@
// Viewing another user's dashboard.
requestContext.setContext(newRequestContext(otherAccountId));
assertDashboardQuery(
- user.getUserName().get(),
+ userId.toString(),
IndexPreloadingUtil.DASHBOARD_RECENTLY_CLOSED_QUERY,
abandonedAssignedWip,
abandonedAssigned,
@@ -3674,9 +3702,9 @@
public void attentionSetIndexed() throws Exception {
assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS)).isTrue();
assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS_COUNT)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
AttentionSetInput input = new AttentionSetInput(userId.toString(), "some reason");
gApi.changes().id(change1.getChangeId()).addToAttentionSet(input);
@@ -3685,7 +3713,7 @@
assertQuery("-is:attention", change2);
assertQuery("has:attention", change1);
assertQuery("-has:attention", change2);
- assertQuery("attention:" + user.getUserName().get(), change1);
+ assertQuery("attention:" + userAccount.preferredEmail(), change1);
assertQuery("-attention:" + userId.toString(), change2);
gApi.changes()
@@ -3698,8 +3726,8 @@
@Test
public void attentionSetStored() throws Exception {
assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
AttentionSetInput input = new AttentionSetInput(userId.toString(), "reason 1");
gApi.changes().id(change.getChangeId()).addToAttentionSet(input);
@@ -3727,29 +3755,29 @@
@Test
public void assignee() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChange(repo));
AssigneeInput input = new AssigneeInput();
- input.assignee = user.getUserName().get();
+ input.assignee = user.getAccountId().toString();
gApi.changes().id(change1.getChangeId()).setAssignee(input);
assertQuery("is:assigned", change1);
assertQuery("-is:assigned", change2);
assertQuery("is:unassigned", change2);
assertQuery("-is:unassigned", change1);
- assertQuery("assignee:" + user.getUserName().get(), change1);
- assertQuery("-assignee:" + user.getUserName().get(), change2);
+ assertQuery("assignee:" + user.getAccountId(), change1);
+ assertQuery("-assignee:" + user.getAccountId(), change2);
}
@GerritConfig(name = "accounts.visibility", value = "NONE")
@Test
public void userDestination() throws Exception {
- TestRepository<Repo> repo1 = createProject("repo1");
- Change change1 = insert(repo1, newChange(repo1));
- TestRepository<Repo> repo2 = createProject("repo2");
- Change change2 = insert(repo2, newChange(repo2));
+ createProject("repo1");
+ Change change1 = insert("repo1", newChange("repo1"));
+ createProject("repo2");
+ Change change2 = insert("repo2", newChange("repo2"));
assertThatQueryException("destination:foo")
.hasMessageThat()
@@ -3763,7 +3791,7 @@
String destination4 = "refs/heads/master\trepo3";
String destination5 = "refs/heads/other\trepo1";
- try (TestRepository<Repo> allUsers =
+ try (TestRepository<Repository> allUsers =
new TestRepository<>(repoManager.openRepository(allUsersName))) {
String refsUsers = RefNames.refsUsers(userId);
allUsers.branch(refsUsers).commit().add("destinations/destination1", destination1).create();
@@ -3814,26 +3842,25 @@
assertThatQueryException("destination:destination3,user=" + anotherUserId)
.hasMessageThat()
.isEqualTo("Unknown named destination: destination3");
- assertThatQueryException("destination:destination3,user=test")
+ assertThatQueryException("destination:destination3,user=non-existent")
.hasMessageThat()
- .isEqualTo("Account 'test' not found");
+ .isEqualTo("Account 'non-existent' not found");
requestContext.setContext(newRequestContext(anotherUserId));
- // account 1000000 is not visible to 'anotheruser' as they are not an admin
+ // account userId is not visible to 'anotheruser' as they are not an admin
assertThatQueryException("destination:destination3,user=" + userId)
.hasMessageThat()
- .isEqualTo("Account '1000000' not found");
+ .isEqualTo(String.format("Account '%s' not found", userId));
}
@GerritConfig(name = "accounts.visibility", value = "NONE")
@Test
public void userQuery() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo));
- Change change2 = insert(repo, newChangeForBranch(repo, "stable"));
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo));
+ Change change2 = insert("repo", newChangeForBranch(repo, "stable"));
- Account.Id anotherUserId =
- accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+ Account.Id anotherUserId = createAccount("anotheruser");
String queryListText =
"query1\tproject:repo\n"
+ "query2\tproject:repo status:open\n"
@@ -3845,7 +3872,7 @@
+ "query7\tproject:repo branch:stable\n"
+ "query8\tproject:repo branch:other";
- try (TestRepository<Repo> allUsers =
+ try (TestRepository<Repository> allUsers =
new TestRepository<>(repoManager.openRepository(allUsersName));
MetaDataUpdate md = metaDataUpdateFactory.create(allUsersName);
MetaDataUpdate anotherMd = metaDataUpdateFactory.create(allUsersName)) {
@@ -3859,19 +3886,20 @@
anotherQueries.commit(anotherMd);
}
+ assertThat(gApi.accounts().self().get()._accountId).isEqualTo(userId.get());
assertThatQueryException("query:foo").hasMessageThat().isEqualTo("Unknown named query: foo");
assertThatQueryException("query:query1,user=" + anotherUserId)
.hasMessageThat()
.isEqualTo("Unknown named query: query1");
- assertThatQueryException("query:query1,user=test")
+ assertThatQueryException("query:query1,user=non-existent")
.hasMessageThat()
- .isEqualTo("Account 'test' not found");
+ .isEqualTo("Account 'non-existent' not found");
requestContext.setContext(newRequestContext(anotherUserId));
// account 1000000 is not visible to 'anotheruser' as they are not an admin
assertThatQueryException("query:query1,user=" + userId)
.hasMessageThat()
- .isEqualTo("Account '1000000' not found");
+ .isEqualTo(String.format("Account '%s' not found", userId));
requestContext.setContext(newRequestContext(userId));
assertQuery("query:query1", change2, change1);
@@ -3890,16 +3918,16 @@
@Test
public void byOwnerInvalidQuery() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ insert("repo", newChange(repo), userId);
String nameEmail = user.asIdentifiedUser().getNameEmail();
assertQuery("owner: \"" + nameEmail + "\"\\");
}
@Test
public void byDeletedChange() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
String query = "change:" + change.getId();
assertQuery(query, change);
@@ -3910,8 +3938,8 @@
@Test
public void byUrlEncodedProject() throws Exception {
- TestRepository<Repo> repo = createProject("repo+foo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo+foo");
+ Change change = insert("repo+foo", newChange(repo));
assertQuery("project:repo+foo", change);
}
@@ -3944,9 +3972,9 @@
@Test
public void isPureRevert() throws Exception {
assume().that(getSchema().hasField(ChangeField.IS_PURE_REVERT_SPEC)).isTrue();
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
// Create two commits and revert second commit (initial commit can't be reverted)
- Change initial = insert(repo, newChange(repo));
+ Change initial = insert("repo", newChange(repo));
gApi.changes().id(initial.getChangeId()).current().review(ReviewInput.approve());
gApi.changes().id(initial.getChangeId()).current().submit();
@@ -3990,8 +4018,8 @@
Account.Id user2 =
accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
gApi.changes().id(change.getId().get()).addReviewer(user2.toString());
RequestContext adminContext = requestContext.setContext(newRequestContext(user2));
@@ -4006,8 +4034,8 @@
@Test
public void none() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change = insert(repo, newChange(repo));
+ repo = createAndOpenProject("repo");
+ Change change = insert("repo", newChange(repo));
assertQuery(ChangeIndexPredicate.none());
@@ -4027,27 +4055,24 @@
@Test
@GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
public void mergeableFailsWhenNotIndexed() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ assume().that(getSchema().hasField(ChangeField.MERGE_SPEC)).isTrue();
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
- insert(repo, newChangeForCommit(repo, commit1));
+ insert("repo", newChangeForCommit(repo, commit1));
Throwable thrown = assertThrows(Throwable.class, () -> assertQuery("status:open is:mergeable"));
assertThat(thrown.getCause()).isInstanceOf(QueryParseException.class);
assertThat(thrown)
.hasMessageThat()
- .contains("'is:mergeable' operator is not supported by server");
+ .contains("'is:mergeable' operator is not supported on this gerrit host");
}
- protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
- return newChange(repo, null, null, null, null, null, false, false);
- }
-
- protected ChangeInserter newChangeForCommit(TestRepository<Repo> repo, RevCommit commit)
+ protected ChangeInserter newChangeForCommit(TestRepository<Repository> repo, RevCommit commit)
throws Exception {
return newChange(repo, commit, null, null, null, null, false, false);
}
- protected ChangeInserter newChangeWithFiles(TestRepository<Repo> repo, String... paths)
+ protected ChangeInserter newChangeWithFiles(TestRepository<Repository> repo, String... paths)
throws Exception {
TestRepository<?>.CommitBuilder b = repo.commit().message("Change with files");
for (String path : paths) {
@@ -4056,36 +4081,67 @@
return newChangeForCommit(repo, repo.parseBody(b.create()));
}
- protected ChangeInserter newChangeForBranch(TestRepository<Repo> repo, String branch)
+ protected ChangeInserter newChangeForBranch(TestRepository<Repository> repo, String branch)
throws Exception {
return newChange(repo, null, branch, null, null, null, false, false);
}
- protected ChangeInserter newChangeWithStatus(TestRepository<Repo> repo, Change.Status status)
- throws Exception {
+ protected ChangeInserter newChangeWithStatus(
+ TestRepository<Repository> repo, Change.Status status) throws Exception {
return newChange(repo, null, null, status, null, null, false, false);
}
- protected ChangeInserter newChangeWithTopic(TestRepository<Repo> repo, String topic)
+ protected ChangeInserter newChangeWithStatus(String repoName, Change.Status status)
+ throws Exception {
+ return newChange(repoName, null, null, status, null, null, false, false);
+ }
+
+ protected ChangeInserter newChangeWithTopic(TestRepository<Repository> repo, String topic)
throws Exception {
return newChange(repo, null, null, null, topic, null, false, false);
}
- protected ChangeInserter newChangeWorkInProgress(TestRepository<Repo> repo) throws Exception {
+ protected ChangeInserter newChangeWorkInProgress(TestRepository<Repository> repo)
+ throws Exception {
return newChange(repo, null, null, null, null, null, true, false);
}
- protected ChangeInserter newChangePrivate(TestRepository<Repo> repo) throws Exception {
+ protected ChangeInserter newChangePrivate(TestRepository<Repository> repo) throws Exception {
return newChange(repo, null, null, null, null, null, false, true);
}
protected ChangeInserter newCherryPickChange(
- TestRepository<Repo> repo, String branch, PatchSet.Id cherryPickOf) throws Exception {
+ TestRepository<Repository> repo, String branch, PatchSet.Id cherryPickOf) throws Exception {
return newChange(repo, null, branch, null, null, cherryPickOf, false, true);
}
+ protected ChangeInserter newChange(String repoName) throws Exception {
+ return newChange(repoName, null, null, null, null, null, false, false);
+ }
+
protected ChangeInserter newChange(
- TestRepository<Repo> repo,
+ String repoName,
+ @Nullable RevCommit commit,
+ @Nullable String branch,
+ @Nullable Change.Status status,
+ @Nullable String topic,
+ @Nullable PatchSet.Id cherryPickOf,
+ boolean workInProgress,
+ boolean isPrivate)
+ throws Exception {
+ try (TestRepository<Repository> repo =
+ new TestRepository<>(repoManager.openRepository(Project.nameKey(repoName)))) {
+ return newChange(
+ repo, commit, branch, status, topic, cherryPickOf, workInProgress, isPrivate);
+ }
+ }
+
+ protected ChangeInserter newChange(TestRepository<Repository> repo) throws Exception {
+ return newChange(repo, null, null, null, null, null, false, false);
+ }
+
+ protected ChangeInserter newChange(
+ TestRepository<Repository> repo,
@Nullable RevCommit commit,
@Nullable String branch,
@Nullable Change.Status status,
@@ -4095,7 +4151,7 @@
boolean isPrivate)
throws Exception {
if (commit == null) {
- commit = repo.parseBody(repo.commit().message("message").create());
+ commit = repo.parseBody(repo.commit().message("initial message").create());
}
branch = MoreObjects.firstNonNull(branch, "refs/heads/master");
@@ -4104,32 +4160,32 @@
}
Change.Id id = Change.id(seq.nextChangeId());
- ChangeInserter ins =
- changeFactory
- .create(id, commit, branch)
- .setValidate(false)
- .setStatus(status)
- .setTopic(topic)
- .setWorkInProgress(workInProgress)
- .setPrivate(isPrivate)
- .setCherryPickOf(cherryPickOf);
- return ins;
+ return changeFactory
+ .create(id, commit, branch)
+ .setValidate(false)
+ .setStatus(status)
+ .setTopic(topic)
+ .setWorkInProgress(workInProgress)
+ .setPrivate(isPrivate)
+ .setCherryPickOf(cherryPickOf);
}
- protected Change insert(TestRepository<Repo> repo, ChangeInserter ins) throws Exception {
- return insert(repo, ins, null, TimeUtil.now());
- }
-
- protected Change insert(TestRepository<Repo> repo, ChangeInserter ins, @Nullable Account.Id owner)
+ @CanIgnoreReturnValue
+ protected Change insert(String repoName, ChangeInserter ins, @Nullable Account.Id owner)
throws Exception {
- return insert(repo, ins, owner, TimeUtil.now());
+ return insert(repoName, ins, owner, TimeUtil.now());
}
+ @CanIgnoreReturnValue
+ protected Change insert(String repoName, ChangeInserter ins) throws Exception {
+ return insert(repoName, ins, null, TimeUtil.now());
+ }
+
+ @CanIgnoreReturnValue
protected Change insert(
- TestRepository<Repo> repo, ChangeInserter ins, @Nullable Account.Id owner, Instant createdOn)
+ String repoName, ChangeInserter ins, @Nullable Account.Id owner, Instant createdOn)
throws Exception {
- Project.NameKey project =
- Project.nameKey(repo.getRepository().getDescription().getRepositoryName());
+ Project.NameKey project = Project.nameKey(repoName);
Account.Id ownerId = owner != null ? owner : userId;
IdentifiedUser user = userFactory.create(ownerId);
try (BatchUpdate bu = updateFactory.create(project, user, createdOn)) {
@@ -4140,34 +4196,36 @@
}
protected Change newPatchSet(
- TestRepository<Repo> repo, Change c, CurrentUser user, Optional<String> message)
- throws Exception {
- // Add a new file so the patch set is not a trivial rebase, to avoid default
- // Code-Review label copying.
- int n = c.currentPatchSetId().get() + 1;
- RevCommit commit =
- repo.parseBody(
- repo.commit()
- .message(message.orElse("message"))
- .add("file" + n, "contents " + n)
- .create());
+ String repoName, Change c, CurrentUser user, Optional<String> message) throws Exception {
+ try (TestRepository<Repository> repo =
+ new TestRepository<>(repoManager.openRepository(Project.nameKey(repoName)))) {
+ // Add a new file so the patch set is not a trivial rebase, to avoid default
+ // Code-Review label copying.
+ int n = c.currentPatchSetId().get() + 1;
+ RevCommit commit =
+ repo.parseBody(
+ repo.commit()
+ .message(message.orElse("updated message"))
+ .add("file" + n, "contents " + n)
+ .create());
- PatchSetInserter inserter =
- patchSetFactory
- .create(changeNotesFactory.createChecked(c), PatchSet.id(c.getId(), n), commit)
- .setFireRevisionCreated(false)
- .setValidate(false);
- try (BatchUpdate bu = updateFactory.create(c.getProject(), user, TimeUtil.now());
- ObjectInserter oi = repo.getRepository().newObjectInserter();
- ObjectReader reader = oi.newReader();
- RevWalk rw = new RevWalk(reader)) {
- bu.setRepository(repo.getRepository(), rw, oi);
- bu.setNotify(NotifyResolver.Result.none());
- bu.addOp(c.getId(), inserter);
- bu.execute();
+ PatchSetInserter inserter =
+ patchSetFactory
+ .create(changeNotesFactory.createChecked(c), PatchSet.id(c.getId(), n), commit)
+ .setFireRevisionCreated(false)
+ .setValidate(false);
+ try (BatchUpdate bu = updateFactory.create(c.getProject(), user, TimeUtil.now());
+ ObjectInserter oi = repo.getRepository().newObjectInserter();
+ ObjectReader reader = oi.newReader();
+ RevWalk rw = new RevWalk(reader)) {
+ bu.setRepository(repo.getRepository(), rw, oi);
+ bu.setNotify(NotifyResolver.Result.none());
+ bu.addOp(c.getId(), inserter);
+ bu.execute();
+ }
+
+ return inserter.getChange();
}
-
- return inserter.getChange();
}
protected ThrowableSubject assertThatQueryException(Object query) throws Exception {
@@ -4192,17 +4250,27 @@
}
}
- protected TestRepository<Repo> createProject(String name) throws Exception {
- gApi.projects().create(name).get();
+ @CanIgnoreReturnValue
+ protected TestRepository<Repository> createAndOpenProject(String name) throws Exception {
+ createProject(name);
return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
}
- protected TestRepository<Repo> createProject(String name, String parent) throws Exception {
+ protected TestRepository<Repository> createAndOpenProject(String name, String parent)
+ throws Exception {
+ createProject(name, parent);
+ return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
+ }
+
+ protected void createProject(String name) throws Exception {
+ gApi.projects().create(name).get();
+ }
+
+ protected void createProject(String name, String parent) throws Exception {
ProjectInput input = new ProjectInput();
input.name = name;
input.parent = parent;
gApi.projects().create(input).get();
- return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
}
protected QueryRequest newQuery(Object query) {
diff --git a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
index fe60119..6b17bb6 100644
--- a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
@@ -27,14 +27,13 @@
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.List;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
/**
@@ -58,19 +57,21 @@
@UseClockStep
public void stopQueryIfNoMoreResults() throws Exception {
// create 2 visible changes
- TestRepository<InMemoryRepositoryManager.Repo> testRepo = createProject("repo");
- insert(testRepo, newChange(testRepo));
- insert(testRepo, newChange(testRepo));
+ try (TestRepository<Repository> testRepo = createAndOpenProject("repo")) {
+ insert("repo", newChange(testRepo));
+ insert("repo", newChange(testRepo));
+ }
// create 2 invisible changes
- TestRepository<Repo> hiddenProject = createProject("hiddenProject");
- insert(hiddenProject, newChange(hiddenProject));
- insert(hiddenProject, newChange(hiddenProject));
- projectOperations
- .project(Project.nameKey("hiddenProject"))
- .forUpdate()
- .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
- .update();
+ try (TestRepository<Repository> hiddenProject = createAndOpenProject("hiddenProject")) {
+ insert("hiddenProject", newChange(hiddenProject));
+ insert("hiddenProject", newChange(hiddenProject));
+ projectOperations
+ .project(Project.nameKey("hiddenProject"))
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+ .update();
+ }
AbstractFakeIndex<?, ?, ?> idx =
(AbstractFakeIndex<?, ?, ?>) changeIndexCollection.getSearchIndex();
@@ -83,12 +84,13 @@
@Test
@UseClockStep
public void noLimitQueryPaginates() throws Exception {
- TestRepository<InMemoryRepositoryManager.Repo> testRepo = createProject("repo");
- // create 4 changes
- insert(testRepo, newChange(testRepo));
- insert(testRepo, newChange(testRepo));
- insert(testRepo, newChange(testRepo));
- insert(testRepo, newChange(testRepo));
+ try (TestRepository<Repository> testRepo = createAndOpenProject("repo")) {
+ // create 4 changes
+ insert("repo", newChange(testRepo));
+ insert("repo", newChange(testRepo));
+ insert("repo", newChange(testRepo));
+ insert("repo", newChange(testRepo));
+ }
// Set queryLimit to 2
projectOperations
@@ -111,11 +113,12 @@
@UseClockStep
public void internalQueriesPaginate() throws Exception {
// create 4 changes
- TestRepository<InMemoryRepositoryManager.Repo> testRepo = createProject("repo");
- insert(testRepo, newChange(testRepo));
- insert(testRepo, newChange(testRepo));
- insert(testRepo, newChange(testRepo));
- insert(testRepo, newChange(testRepo));
+ try (TestRepository<Repository> testRepo = createAndOpenProject("repo")) {
+ insert("repo", newChange(testRepo));
+ insert("repo", newChange(testRepo));
+ insert("repo", newChange(testRepo));
+ insert("repo", newChange(testRepo));
+ }
// Set queryLimit to 2
projectOperations
diff --git a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 9717bfb..5cae012 100644
--- a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -24,11 +24,9 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
-import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
@@ -45,11 +43,11 @@
@Test
public void fullTextWithSpecialChars() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
RevCommit commit1 = repo.parseBody(repo.commit().message("foo_bar_foo").create());
- Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ Change change1 = insert("repo", newChangeForCommit(repo, commit1));
RevCommit commit2 = repo.parseBody(repo.commit().message("one.two.three").create());
- Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+ Change change2 = insert("repo", newChangeForCommit(repo, commit2));
assertQuery("message:foo_ba");
assertQuery("message:bar", change1);
@@ -63,8 +61,8 @@
@Test
@Override
public void byOwnerInvalidQuery() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
- Change change1 = insert(repo, newChange(repo), userId);
+ repo = createAndOpenProject("repo");
+ Change change1 = insert("repo", newChange(repo), userId);
String nameEmail = user.asIdentifiedUser().getNameEmail();
BadRequestException thrown =
@@ -76,17 +74,17 @@
@Test
public void openAndClosedChanges() throws Exception {
- TestRepository<Repo> repo = createProject("repo");
+ repo = createAndOpenProject("repo");
// create 3 closed changes
- Change change1 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
- Change change2 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
- Change change3 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+ Change change1 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
+ Change change2 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
+ Change change3 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
// create 3 new changes
- Change change4 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
- Change change5 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
- Change change6 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+ Change change4 = insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
+ Change change5 = insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
+ Change change6 = insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
// Set queryLimit to 1
projectOperations
diff --git a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index 540416f..12bafd5 100644
--- a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -401,9 +401,8 @@
String query = "uuid:" + uuid;
assertQuery(query, group);
- for (GroupIndex index : groupIndexes.getWriteIndexes()) {
- index.delete(uuid);
- }
+ deleteGroup(uuid);
+
assertQuery(query);
}
@@ -441,6 +440,10 @@
return createGroupWithDescription(name, null, members);
}
+ protected GroupInfo createGroup(GroupInput in) throws Exception {
+ return gApi.groups().create(in).get();
+ }
+
protected GroupInfo createGroupWithDescription(
String name, String description, AccountInfo... members) throws Exception {
GroupInput in = new GroupInput();
@@ -448,21 +451,27 @@
in.description = description;
in.members =
Arrays.asList(members).stream().map(a -> String.valueOf(a._accountId)).collect(toList());
- return gApi.groups().create(in).get();
+ return createGroup(in);
}
protected GroupInfo createGroupWithOwner(String name, GroupInfo ownerGroup) throws Exception {
GroupInput in = new GroupInput();
in.name = name;
in.ownerId = ownerGroup.id;
- return gApi.groups().create(in).get();
+ return createGroup(in);
}
protected GroupInfo createGroupThatIsVisibleToAll(String name) throws Exception {
GroupInput in = new GroupInput();
in.name = name;
in.visibleToAll = true;
- return gApi.groups().create(in).get();
+ return createGroup(in);
+ }
+
+ protected void deleteGroup(AccountGroup.UUID uuid) throws Exception {
+ for (GroupIndex index : groupIndexes.getWriteIndexes()) {
+ index.delete(uuid);
+ }
}
protected GroupInfo getGroup(AccountGroup.UUID uuid) throws Exception {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-file-edit-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-file-edit-dialog.ts
index c3f79c7..0cfbaa4 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-file-edit-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-file-edit-dialog.ts
@@ -14,7 +14,7 @@
import {LitElement, html, nothing} from 'lit';
import {customElement, property, query, state} from 'lit/decorators.js';
import {resolve} from '../../../models/dependency';
-import {createEditUrl} from '../../../models/views/edit';
+import {createEditUrl} from '../../../models/views/change';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {assertIsDefined} from '../../../utils/common-util';
import {when} from 'lit/directives/when.js';
@@ -162,8 +162,8 @@
const url = createEditUrl({
changeNum: change._number,
repo: change.project,
- path: this.path,
patchNum: 1 as PatchSetNumber,
+ editView: {path: this.path},
});
this.getNavigation().setUrl(url);
}
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
index 115bc43..11cfaab 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.ts
@@ -31,7 +31,7 @@
import {LitElement, PropertyValues, css, html} from 'lit';
import {customElement, query, property, state} from 'lit/decorators.js';
import {assertIsDefined} from '../../../utils/common-util';
-import {createEditUrl} from '../../../models/views/edit';
+import {createEditUrl} from '../../../models/views/change';
import {resolve} from '../../../models/dependency';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {GrCreateFileEditDialog} from '../gr-create-change-dialog/gr-create-file-edit-dialog';
@@ -320,8 +320,8 @@
createEditUrl({
changeNum: change._number,
repo: change.project,
- path: CONFIG_PATH,
patchNum: INITIAL_PATCHSET,
+ editView: {path: CONFIG_PATH},
})
);
})
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
index 632ec4c..7eef7a4 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -11,6 +11,7 @@
import '../../shared/gr-list-view/gr-list-view';
import '../gr-create-pointer-dialog/gr-create-pointer-dialog';
import '../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog';
+import {encodeURL} from '../../../utils/url-util';
import {GrCreatePointerDialog} from '../gr-create-pointer-dialog/gr-create-pointer-dialog';
import {
BranchInfo,
@@ -28,16 +29,12 @@
import {formStyles} from '../../../styles/gr-form-styles';
import {tableStyles} from '../../../styles/gr-table-styles';
import {sharedStyles} from '../../../styles/shared-styles';
-import {LitElement, PropertyValues, css, html, nothing} from 'lit';
+import {LitElement, PropertyValues, css, html} from 'lit';
import {customElement, query, property, state} from 'lit/decorators.js';
import {BindValueChangeEvent} from '../../../types/events';
import {assertIsDefined} from '../../../utils/common-util';
import {ifDefined} from 'lit/directives/if-defined.js';
-import {
- createRepoUrl,
- RepoDetailView,
- RepoViewState,
-} from '../../../models/views/repo';
+import {RepoDetailView, RepoViewState} from '../../../models/views/repo';
import {modalStyles} from '../../../styles/gr-modal-styles';
const PGP_START = '-----BEGIN PGP SIGNATURE-----';
@@ -142,7 +139,6 @@
}
override render() {
- if (!this.repo || !this.detailType) return nothing;
return html`
<gr-list-view
.createNew=${this.loggedIn}
@@ -151,7 +147,7 @@
.items=${this.items}
.loading=${this.loading}
.offset=${this.offset}
- .path=${createRepoUrl({repo: this.repo, detail: this.detailType})}
+ .path=${this.getPath(this.repo, this.detailType)}
@create-clicked=${() => {
this.handleCreateClicked();
}}
@@ -445,6 +441,13 @@
return Promise.reject(new Error('unknown detail type'));
}
+ private getPath(repo?: RepoName, detailType?: RepoDetailView) {
+ // TODO: Replace with `createRepoUrl()`, but be aware that `encodeURL()`
+ // gets `false` as a second parameter here. The router pattern in gr-router
+ // does not handle the filter URLs, if the repo is not encoded!
+ return `/admin/repos/${encodeURL(repo ?? '', false)},${detailType}`;
+ }
+
private computeWeblink(repo: ProjectInfo | BranchInfo | TagInfo) {
if (!repo.web_links) return [];
const webLinks = repo.web_links;
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
index 75854cc..e47b450 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -1577,10 +1577,11 @@
base: e.detail.base,
allow_conflicts: e.detail.allowConflicts,
};
+ const rebaseChain = !!e.detail.rebaseChain;
this.fireAction(
- '/rebase',
+ rebaseChain ? '/rebase:chain' : '/rebase',
assertUIActionInfo(this.revisionActions.rebase),
- true,
+ rebaseChain ? false : true,
payload,
{allow_conflicts: payload.allow_conflicts}
);
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
index c6bfd55..4602eac 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -625,7 +625,9 @@
};
assert.isTrue(fetchChangesStub.called);
element.handleRebaseConfirm(
- new CustomEvent('', {detail: {base: '1234', allowConflicts: false}})
+ new CustomEvent('', {
+ detail: {base: '1234', allowConflicts: false, rebaseChain: false},
+ })
);
assert.deepEqual(fireActionStub.lastCall.args, [
'/rebase',
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index c958c7e..c0ed3b3 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -135,10 +135,6 @@
fireTitleChange,
} from '../../../utils/event-util';
import {
- GerritView,
- routerModelToken,
-} from '../../../services/router/router-model';
-import {
debounce,
DelayedTask,
throttleWrap,
@@ -176,12 +172,13 @@
import {getBaseUrl, prependOrigin} from '../../../utils/url-util';
import {CopyLink, GrCopyLinks} from '../gr-copy-links/gr-copy-links';
import {
+ ChangeChildView,
changeViewModelToken,
ChangeViewState,
createChangeUrl,
+ createEditUrl,
} from '../../../models/views/change';
import {rootUrl} from '../../../utils/url-util';
-import {createEditUrl} from '../../../models/views/edit';
import {userModelToken} from '../../../models/user/user-model';
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {modalStyles} from '../../../styles/gr-modal-styles';
@@ -539,8 +536,6 @@
private readonly getChangeModel = resolve(this, changeModelToken);
- private readonly getRouterModel = resolve(this, routerModelToken);
-
private readonly getCommentsModel = resolve(this, commentsModelToken);
private readonly getConfigModel = resolve(this, configModelToken);
@@ -573,7 +568,7 @@
/** Simply reflects the router-model value. */
// visible for testing
- routerPatchNum?: PatchSetNum;
+ viewModelPatchNum?: PatchSetNum;
private readonly shortcutsController = new ShortcutController(this);
@@ -700,16 +695,16 @@
);
subscribe(
this,
- () => this.getRouterModel().routerView$,
- view => {
- this.isViewCurrent = view === GerritView.CHANGE;
+ () => this.getViewModel().childView$,
+ childView => {
+ this.isViewCurrent = childView === ChangeChildView.OVERVIEW;
}
);
subscribe(
this,
- () => this.getRouterModel().routerPatchNum$,
+ () => this.getViewModel().patchNum$,
patchNum => {
- this.routerPatchNum = patchNum;
+ this.viewModelPatchNum = patchNum;
}
);
subscribe(
@@ -1531,7 +1526,6 @@
.allPatchSets=${this.allPatchSets}
.change=${this.change}
.changeNum=${this.changeNum}
- .revisionInfo=${this.getRevisionInfo()}
.commitInfo=${this.commitInfo}
.changeUrl=${this.computeChangeUrl()}
.editMode=${this.getEditMode()}
@@ -2092,13 +2086,6 @@
return;
}
- if (this.viewState.changeNum && this.viewState.repo) {
- this.restApiService.setInProjectLookup(
- this.viewState.changeNum,
- this.viewState.repo
- );
- }
-
if (this.viewState.basePatchNum === undefined)
this.viewState.basePatchNum = PARENT;
@@ -2284,7 +2271,7 @@
private updateTitle(change?: ChangeInfo | ParsedChangeInfo) {
if (!change) return;
- const title = change.subject + ' (' + change.change_id.substr(0, 9) + ')';
+ const title = `${change.subject} (${change._number})`;
fireTitleChange(this, title);
}
@@ -2448,6 +2435,7 @@
// Private but used in tests.
handleDiffBaseAgainstLeft() {
+ if (this.viewState?.childView !== ChangeChildView.OVERVIEW) return;
assertIsDefined(this.change, 'change');
assertIsDefined(this.patchRange, 'patchRange');
@@ -2650,7 +2638,7 @@
// is under change-model control. `patchRange.patchNum` should eventually
// also be model managed, so we can reconcile these two code snippets into
// one location.
- if (!this.routerPatchNum && latestPsNum === editParentRev._number) {
+ if (!this.viewModelPatchNum && latestPsNum === editParentRev._number) {
this.patchRange = {...this.patchRange, patchNum: EDIT};
// The file list is not reactive (yet) with regards to patch range
// changes, so we have to actively trigger it.
@@ -3143,8 +3131,8 @@
createEditUrl({
changeNum: this.change._number,
repo: this.change.project,
- path,
patchNum: this.patchRange.patchNum,
+ editView: {path},
})
);
break;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index 9d89192..9b78e64 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -99,7 +99,7 @@
import {Modifier} from '../../../utils/dom-util';
import {GrButton} from '../../shared/gr-button/gr-button';
import {GrCopyLinks} from '../gr-copy-links/gr-copy-links';
-import {ChangeViewState} from '../../../models/views/change';
+import {ChangeChildView, ChangeViewState} from '../../../models/views/change';
import {rootUrl} from '../../../utils/url-util';
import {testResolver} from '../../../test/common-test-setup';
import {UserModel, userModelToken} from '../../../models/user/user-model';
@@ -369,6 +369,7 @@
);
element.viewState = {
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
changeNum: TEST_NUMERIC_CHANGE_ID,
repo: 'gerrit' as RepoName,
};
@@ -1986,7 +1987,7 @@
// When edit is set, but patchNum as well, then keep patchNum.
element.patchRange.patchNum = 5 as RevisionPatchSetNum;
- element.routerPatchNum = 5 as RevisionPatchSetNum;
+ element.viewModelPatchNum = 5 as RevisionPatchSetNum;
element.processEdit(change);
assert.equal(element.patchRange.patchNum, 5 as RevisionPatchSetNum);
});
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 da61b60..b0dbda5 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
@@ -5,6 +5,7 @@
*/
import {css, html, LitElement, PropertyValues} from 'lit';
import {customElement, property, query, state} from 'lit/decorators.js';
+import {when} from 'lit/directives/when.js';
import {
NumericChangeId,
BranchName,
@@ -21,6 +22,7 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {ValueChangedEvent} from '../../../types/events';
import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {KnownExperimentId} from '../../../services/flags/flags';
export interface RebaseChange {
name: string;
@@ -30,6 +32,7 @@
export interface ConfirmRebaseEventDetail {
base: string | null;
allowConflicts: boolean;
+ rebaseChain: boolean;
}
@customElement('gr-confirm-rebase-dialog')
@@ -85,11 +88,16 @@
@query('#rebaseAllowConflicts')
private rebaseAllowConflicts!: HTMLInputElement;
+ @query('#rebaseChain')
+ private rebaseChain?: HTMLInputElement;
+
@query('#parentInput')
parentInput!: GrAutocomplete;
private readonly restApiService = getAppContext().restApiService;
+ private readonly flagsService = getAppContext().flagsService;
+
constructor() {
super();
this.query = input => this.getChangeSuggestions(input);
@@ -221,6 +229,14 @@
>Allow rebase with conflicts</label
>
</div>
+ ${when(
+ this.flagsService.isEnabled(KnownExperimentId.REBASE_CHAIN),
+ () =>
+ html`<div>
+ <input id="rebaseChain" type="checkbox" />
+ <label for="rebaseChain">Rebase all ancestors</label>
+ </div>`
+ )}
</div>
</gr-dialog>
`;
@@ -326,6 +342,7 @@
const detail: ConfirmRebaseEventDetail = {
base: this.getSelectedBase(),
allowConflicts: this.rebaseAllowConflicts.checked,
+ rebaseChain: !!this.rebaseChain?.checked,
};
this.dispatchEvent(new CustomEvent('confirm', {detail}));
this.text = '';
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index 89a1ff5..c1e866c 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -20,8 +20,6 @@
PatchSetNum,
CommitInfo,
ServerInfo,
- RevisionInfo,
- NumericChangeId,
BasePatchSetNum,
} from '../../../types/common';
import {DiffPreferencesInfo} from '../../../types/diff';
@@ -71,9 +69,6 @@
change: ChangeInfo | undefined;
@property({type: String})
- changeNum?: NumericChangeId;
-
- @property({type: String})
changeUrl?: string;
@property({type: Object})
@@ -97,9 +92,6 @@
@property({type: String})
filesExpanded?: FilesExpandedState;
- @property({type: Object})
- revisionInfo?: RevisionInfo;
-
@state()
diffPrefs?: DiffPreferencesInfo;
@@ -274,12 +266,6 @@
<div class="patchInfoContent">
<gr-patch-range-select
id="rangeSelect"
- .changeNum=${this.changeNum}
- .patchNum=${this.patchNum}
- .basePatchNum=${this.basePatchNum}
- .availablePatches=${this.allPatchSets}
- .revisions=${this.change.revisions}
- .revisionInfo=${this.revisionInfo}
@patch-range-change=${this.handlePatchChange}
>
</gr-patch-range-select>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.ts
index 23534a0..6c2282b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.ts
@@ -18,7 +18,6 @@
import {
BasePatchSetNum,
ChangeId,
- NumericChangeId,
PARENT,
PatchSetNum,
PatchSetNumber,
@@ -174,7 +173,6 @@
});
test('show/hide diffs disabled for large amounts of files', async () => {
- element.changeNum = 42 as NumericChangeId;
element.basePatchNum = PARENT;
element.patchNum = '2' as PatchSetNum;
element.shownFileCount = 1;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 071c489..d4defcb 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -78,9 +78,11 @@
import {incrementalRepeat} from '../../lit/incremental-repeat';
import {ifDefined} from 'lit/directives/if-defined.js';
import {HtmlPatched} from '../../../utils/lit-util';
-import {createDiffUrl} from '../../../models/views/diff';
-import {createEditUrl} from '../../../models/views/edit';
-import {createChangeUrl} from '../../../models/views/change';
+import {
+ createDiffUrl,
+ createEditUrl,
+ createChangeUrl,
+} from '../../../models/views/change';
import {userModelToken} from '../../../models/user/user-model';
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {FileMode, fileModeToString} from '../../../utils/file-util';
@@ -758,7 +760,7 @@
);
subscribe(
this,
- () => this.getFilesModel().filesWithUnmodified$,
+ () => this.getFilesModel().filesIncludingUnmodified$,
files => {
this.files = [...files];
}
@@ -2121,9 +2123,9 @@
this.getNavigation().setUrl(
createDiffUrl({
change: this.change,
- path: diff.path,
patchNum: this.patchRange.patchNum,
basePatchNum: this.patchRange.basePatchNum,
+ diffView: {path: diff.path},
})
);
}
@@ -2142,9 +2144,9 @@
this.getNavigation().setUrl(
createDiffUrl({
change: this.change,
- path: this.files[this.fileCursor.index].__path,
patchNum: this.patchRange.patchNum,
basePatchNum: this.patchRange.basePatchNum,
+ diffView: {path: this.files[this.fileCursor.index].__path},
})
);
}
@@ -2176,16 +2178,16 @@
return createEditUrl({
changeNum: this.change._number,
repo: this.change.project,
- path,
patchNum: this.patchRange.patchNum,
+ editView: {path},
});
}
return createDiffUrl({
changeNum: this.change._number,
repo: this.change.project,
- path,
patchNum: this.patchRange.patchNum,
basePatchNum: this.patchRange.basePatchNum,
+ diffView: {path},
});
}
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 3fdb1c0..5792230 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -75,8 +75,7 @@
import {HtmlPatched} from '../../utils/lit-util';
import {DropdownItem} from '../shared/gr-dropdown-list/gr-dropdown-list';
import './gr-checks-attempt';
-import {createDiffUrl} from '../../models/views/diff';
-import {changeViewModelToken} from '../../models/views/change';
+import {createDiffUrl, changeViewModelToken} from '../../models/views/change';
/**
* Firing this event sets the regular expression of the results filter.
@@ -715,9 +714,8 @@
url: createDiffUrl({
changeNum: change._number,
repo: change.project,
- path,
patchNum: patchset,
- lineNum: line,
+ diffView: {path, lineNum: line},
}),
primary: true,
};
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 b1ff749..833a91a 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
@@ -219,6 +219,7 @@
}
.titleText::after {
content: var(--header-title-content);
+ white-space: nowrap;
}
ul {
list-style: none;
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 1b914bf..bcf6937 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -10,7 +10,11 @@
} from '../../../utils/page-wrapper-utils';
import {NavigationService} from '../gr-navigation/gr-navigation';
import {getAppContext} from '../../../services/app-context';
-import {convertToPatchSetNum} from '../../../utils/patch-set-util';
+import {
+ computeAllPatchSets,
+ computeLatestPatchNum,
+ convertToPatchSetNum,
+} from '../../../utils/patch-set-util';
import {assertIsDefined} from '../../../utils/common-util';
import {
BasePatchSetNum,
@@ -27,7 +31,7 @@
import {AppElement, AppElementParams} from '../../gr-app-types';
import {LocationChangeEventDetail} from '../../../types/events';
import {GerritView, RouterModel} from '../../../services/router/router-model';
-import {firePageError} from '../../../utils/event-util';
+import {fireAlert, firePageError} from '../../../utils/event-util';
import {windowLocationReload} from '../../../utils/dom-util';
import {
getBaseUrl,
@@ -61,13 +65,13 @@
GroupViewModel,
GroupViewState,
} from '../../../models/views/group';
-import {DiffViewModel, DiffViewState} from '../../../models/views/diff';
import {
+ ChangeChildView,
ChangeViewModel,
ChangeViewState,
- createChangeUrl,
+ createChangeViewUrl,
+ createDiffUrl,
} from '../../../models/views/change';
-import {EditViewModel, EditViewState} from '../../../models/views/edit';
import {
DashboardViewModel,
DashboardViewState,
@@ -88,6 +92,13 @@
import {SearchViewModel, SearchViewState} from '../../../models/views/search';
import {DashboardSection} from '../../../utils/dashboard-util';
import {Subscription} from 'rxjs';
+import {
+ addPath,
+ findComment,
+ getPatchRangeForCommentUrl,
+ isInBaseOfPatchRange,
+} from '../../../utils/comment-util';
+import {isFileUnchanged} from '../../../embed/diff/gr-diff/gr-diff-utils';
const RoutePattern = {
ROOT: '/',
@@ -303,9 +314,7 @@
private readonly agreementViewModel: AgreementViewModel,
private readonly changeViewModel: ChangeViewModel,
private readonly dashboardViewModel: DashboardViewModel,
- private readonly diffViewModel: DiffViewModel,
private readonly documentationViewModel: DocumentationViewModel,
- private readonly editViewModel: EditViewModel,
private readonly groupViewModel: GroupViewModel,
private readonly pluginViewModel: PluginViewModel,
private readonly repoViewModel: RepoViewModel,
@@ -322,7 +331,7 @@
// So this check is slightly fragile, but should work.
if (this.view !== GerritView.CHANGE) return;
const browserUrl = new URL(window.location.toString());
- const stateUrl = new URL(createChangeUrl(state), browserUrl);
+ const stateUrl = new URL(createChangeViewUrl(state), browserUrl);
// Keeping the hash and certain parameters are stop-gap solution. We
// should find better ways of maintaining an overall consistent URL
@@ -363,13 +372,14 @@
if ('repo' in state && state.repo !== undefined && 'changeNum' in state)
this.restApiService.setInProjectLookup(state.changeNum, state.repo);
- this.routerModel.setState({
- view: state.view,
- changeNum: 'changeNum' in state ? state.changeNum : undefined,
- patchNum: 'patchNum' in state ? state.patchNum ?? undefined : undefined,
- basePatchNum:
- 'basePatchNum' in state ? state.basePatchNum ?? undefined : undefined,
- });
+ this.routerModel.setState({view: state.view});
+ // We are trying to reset the change (view) model when navigating to other
+ // views, because we don't trust our reset logic at the moment. The models
+ // singletons and might unintentionally keep state from one change to
+ // another. TODO: Let's find some way to avoid that.
+ if (state.view !== GerritView.CHANGE) {
+ this.changeViewModel.setState(undefined);
+ }
this.appElement().params = state;
}
@@ -1441,6 +1451,7 @@
basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
};
const queryMap = new URLSearchParams(ctx.querystring);
@@ -1471,21 +1482,57 @@
this.changeViewModel.setState(state);
}
- handleCommentRoute(ctx: PageContext) {
+ async handleCommentRoute(ctx: PageContext) {
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- const state: DiffViewState = {
- repo: ctx.params[0] as RepoName,
+ const repo = ctx.params[0] as RepoName;
+ const commentId = ctx.params[2] as UrlEncodedCommentId;
+
+ const comments = await this.restApiService.getDiffComments(changeNum);
+ const change = await this.restApiService.getChangeDetail(changeNum);
+
+ const comment = findComment(addPath(comments), commentId);
+ const path = comment?.path;
+ const patchsets = computeAllPatchSets(change);
+ const latestPatchNum = computeLatestPatchNum(patchsets);
+ if (!comment || !path || !latestPatchNum) {
+ this.show404();
+ return;
+ }
+ let {basePatchNum, patchNum} = getPatchRangeForCommentUrl(
+ comment,
+ latestPatchNum
+ );
+
+ if (basePatchNum !== PARENT) {
+ const diff = await this.restApiService.getDiff(
+ changeNum,
+ basePatchNum,
+ patchNum,
+ path
+ );
+ if (diff && isFileUnchanged(diff)) {
+ fireAlert(
+ document,
+ `File is unchanged between Patchset ${basePatchNum} and ${patchNum}.
+ Showing diff of Base vs ${basePatchNum}.`
+ );
+ patchNum = basePatchNum as RevisionPatchSetNum;
+ basePatchNum = PARENT;
+ }
+ }
+
+ const diffUrl = createDiffUrl({
changeNum,
- commentId: ctx.params[2] as UrlEncodedCommentId,
- view: GerritView.DIFF,
- commentLink: true,
- };
- this.reporting.setRepoName(state.repo ?? '');
- this.reporting.setChangeId(changeNum);
- this.normalizePatchRangeParams(state);
- // Note that router model view must be updated before view models.
- this.setState(state);
- this.diffViewModel.setState(state);
+ repo,
+ patchNum,
+ basePatchNum,
+ diffView: {
+ path,
+ lineNum: comment.line,
+ leftSide: isInBaseOfPatchRange(comment, {basePatchNum, patchNum}),
+ },
+ });
+ this.redirect(diffUrl);
}
handleCommentsRoute(ctx: PageContext) {
@@ -1495,6 +1542,7 @@
changeNum,
commentId: ctx.params[2] as UrlEncodedCommentId,
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
};
assertIsDefined(state.repo);
this.reporting.setRepoName(state.repo);
@@ -1508,25 +1556,26 @@
handleDiffRoute(ctx: PageContext) {
const changeNum = Number(ctx.params[1]) as NumericChangeId;
// Parameter order is based on the regex group number matched.
- const state: DiffViewState = {
+ const state: ChangeViewState = {
repo: ctx.params[0] as RepoName,
changeNum,
basePatchNum: convertToPatchSetNum(ctx.params[4]) as BasePatchSetNum,
patchNum: convertToPatchSetNum(ctx.params[6]) as RevisionPatchSetNum,
- path: ctx.params[8],
- view: GerritView.DIFF,
+ view: GerritView.CHANGE,
+ childView: ChangeChildView.DIFF,
+ diffView: {path: ctx.params[8]},
};
const address = this.parseLineAddress(ctx.hash);
if (address) {
- state.leftSide = address.leftSide;
- state.lineNum = address.lineNum;
+ state.diffView!.leftSide = address.leftSide;
+ state.diffView!.lineNum = address.lineNum;
}
this.reporting.setRepoName(state.repo ?? '');
this.reporting.setChangeId(changeNum);
this.normalizePatchRangeParams(state);
// Note that router model view must be updated before view models.
this.setState(state);
- this.diffViewModel.setState(state);
+ this.changeViewModel.setState(state);
}
handleChangeLegacyRoute(ctx: PageContext) {
@@ -1554,19 +1603,19 @@
// Parameter order is based on the regex group number matched.
const project = ctx.params[0] as RepoName;
const changeNum = Number(ctx.params[1]) as NumericChangeId;
- const state: EditViewState = {
+ const state: ChangeViewState = {
repo: project,
changeNum,
// for edit view params, patchNum cannot be undefined
patchNum: convertToPatchSetNum(ctx.params[2]) as RevisionPatchSetNum,
- path: ctx.params[3],
- lineNum: Number(ctx.hash),
- view: GerritView.EDIT,
+ view: GerritView.CHANGE,
+ childView: ChangeChildView.EDIT,
+ editView: {path: ctx.params[3], lineNum: Number(ctx.hash)},
};
this.normalizePatchRangeParams(state);
// Note that router model view must be updated before view models.
this.setState(state);
- this.editViewModel.setState(state);
+ this.changeViewModel.setState(state);
this.reporting.setRepoName(project);
this.reporting.setChangeId(changeNum);
}
@@ -1581,6 +1630,7 @@
changeNum,
patchNum: convertToPatchSetNum(ctx.params[3]) as RevisionPatchSetNum,
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
edit: true,
};
const tab = queryMap.get('tab');
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
index b8f68e6..d8761bf 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
@@ -28,10 +28,16 @@
import {AdminChildView} from '../../../models/views/admin';
import {RepoDetailView} from '../../../models/views/repo';
import {GroupDetailView} from '../../../models/views/group';
-import {EditViewState} from '../../../models/views/edit';
-import {ChangeViewState} from '../../../models/views/change';
+import {ChangeChildView, ChangeViewState} from '../../../models/views/change';
import {PatchRangeParams} from '../../../utils/url-util';
import {testResolver} from '../../../test/common-test-setup';
+import {
+ createComment,
+ createDiff,
+ createParsedChange,
+ createRevision,
+} from '../../../test/test-data-generators';
+import {ParsedChangeInfo} from '../../../types/types';
suite('gr-router tests', () => {
let router: GrRouter;
@@ -1134,6 +1140,7 @@
const ctx = makeParams('', '');
assertctxToParams(ctx, 'handleChangeRoute', {
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
repo: 'foo/bar' as RepoName,
changeNum: 1234 as NumericChangeId,
basePatchNum: 4 as BasePatchSetNum,
@@ -1154,6 +1161,7 @@
ctx.querystring = queryMap.toString();
assertctxToParams(ctx, 'handleChangeRoute', {
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
repo: 'foo/bar' as RepoName,
changeNum: 1234 as NumericChangeId,
basePatchNum: 4 as BasePatchSetNum,
@@ -1193,36 +1201,89 @@
test('diff view', () => {
const ctx = makeParams('foo/bar/baz', 'b44');
assertctxToParams(ctx, 'handleDiffRoute', {
- view: GerritView.DIFF,
+ view: GerritView.CHANGE,
+ childView: ChangeChildView.DIFF,
repo: 'foo/bar' as RepoName,
changeNum: 1234 as NumericChangeId,
basePatchNum: 4 as BasePatchSetNum,
patchNum: 7 as RevisionPatchSetNum,
- path: 'foo/bar/baz',
- leftSide: true,
- lineNum: 44,
+ diffView: {
+ path: 'foo/bar/baz',
+ lineNum: 44,
+ leftSide: true,
+ },
});
assert.isFalse(redirectStub.called);
});
- test('comment route', () => {
- const url = '/c/gerrit/+/264833/comment/00049681_f34fd6a9/';
+ test('comment route base..1', async () => {
+ const change: ParsedChangeInfo = createParsedChange();
+ const repo = change.project;
+ const changeNum = change._number;
+ const ps = 1 as RevisionPatchSetNum;
+ const line = 23;
+ const id = '00049681_f34fd6a9' as UrlEncodedCommentId;
+ stubRestApi('getChangeDetail').resolves(change);
+ stubRestApi('getDiffComments').resolves({
+ filepath: [{...createComment(), id, patch_set: ps, line}],
+ });
+
+ const url = `/c/${repo}/+/${changeNum}/comment/${id}/`;
const groups = url.match(_testOnly_RoutePattern.COMMENT);
- assert.deepEqual(groups!.slice(1), [
- 'gerrit', // project
- '264833', // changeNum
- '00049681_f34fd6a9', // commentId
- ]);
- assertctxToParams(
- {params: groups!.slice(1)} as any,
- 'handleCommentRoute',
- {
- repo: 'gerrit' as RepoName,
- changeNum: 264833 as NumericChangeId,
- commentId: '00049681_f34fd6a9' as UrlEncodedCommentId,
- commentLink: true,
- view: GerritView.DIFF,
- }
+ assert.deepEqual(groups!.slice(1), [repo, `${changeNum}`, id]);
+
+ await router.handleCommentRoute({params: groups!.slice(1)} as any);
+ assert.isTrue(redirectStub.calledOnce);
+ assert.equal(
+ redirectStub.lastCall.args[0],
+ `/c/${repo}/+/${changeNum}/${ps}/filepath#${line}`
+ );
+ });
+
+ test('comment route 1..2', async () => {
+ const change: ParsedChangeInfo = {
+ ...createParsedChange(),
+ revisions: {
+ abc: createRevision(1),
+ def: createRevision(2),
+ },
+ };
+ const repo = change.project;
+ const changeNum = change._number;
+ const ps = 1 as RevisionPatchSetNum;
+ const line = 23;
+ const id = '00049681_f34fd6a9' as UrlEncodedCommentId;
+
+ stubRestApi('getChangeDetail').resolves(change);
+ stubRestApi('getDiffComments').resolves({
+ filepath: [{...createComment(), id, patch_set: ps, line}],
+ });
+ const diffStub = stubRestApi('getDiff');
+
+ const url = `/c/${repo}/+/${changeNum}/comment/${id}/`;
+ const groups = url.match(_testOnly_RoutePattern.COMMENT);
+
+ // If getDiff() returns a diff with changes, then we will compare
+ // the patchset of the comment (1) against latest (2).
+ diffStub.onFirstCall().resolves(createDiff());
+ await router.handleCommentRoute({params: groups!.slice(1)} as any);
+ assert.isTrue(redirectStub.calledOnce);
+ assert.equal(
+ redirectStub.lastCall.args[0],
+ `/c/${repo}/+/${changeNum}/${ps}..2/filepath#b${line}`
+ );
+
+ // If getDiff() returns an unchanged diff, then we will compare
+ // the patchset of the comment (1) against base.
+ diffStub.onSecondCall().resolves({
+ ...createDiff(),
+ content: [],
+ });
+ await router.handleCommentRoute({params: groups!.slice(1)} as any);
+ assert.isTrue(redirectStub.calledTwice);
+ assert.equal(
+ redirectStub.lastCall.args[0],
+ `/c/${repo}/+/${changeNum}/${ps}/filepath#${line}`
);
});
@@ -1242,6 +1303,7 @@
changeNum: 264833 as NumericChangeId,
commentId: '00049681_f34fd6a9' as UrlEncodedCommentId,
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
}
);
});
@@ -1259,13 +1321,13 @@
3: 'foo/bar/baz', // 3 File path
},
};
- const appParams: EditViewState = {
+ const appParams: ChangeViewState = {
repo: 'foo/bar' as RepoName,
changeNum: 1234 as NumericChangeId,
- view: GerritView.EDIT,
- path: 'foo/bar/baz',
+ view: GerritView.CHANGE,
+ childView: ChangeChildView.EDIT,
patchNum: 3 as RevisionPatchSetNum,
- lineNum: 0,
+ editView: {path: 'foo/bar/baz', lineNum: 0},
};
router.handleDiffEditRoute(ctx);
@@ -1285,13 +1347,13 @@
3: 'foo/bar/baz', // 3 File path
},
};
- const appParams: EditViewState = {
+ const appParams: ChangeViewState = {
repo: 'foo/bar' as RepoName,
changeNum: 1234 as NumericChangeId,
- view: GerritView.EDIT,
- path: 'foo/bar/baz',
+ view: GerritView.CHANGE,
+ childView: ChangeChildView.EDIT,
patchNum: 3 as RevisionPatchSetNum,
- lineNum: 4,
+ editView: {path: 'foo/bar/baz', lineNum: 4},
};
router.handleDiffEditRoute(ctx);
@@ -1314,6 +1376,7 @@
repo: 'foo/bar' as RepoName,
changeNum: 1234 as NumericChangeId,
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
patchNum: 3 as RevisionPatchSetNum,
edit: true,
};
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
index 26043b32..6eb5243 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.ts
@@ -7,7 +7,6 @@
PatchRange,
PatchSetNum,
RobotCommentInfo,
- UrlEncodedCommentId,
PathToCommentsInfoMap,
FileInfo,
PARENT,
@@ -64,26 +63,6 @@
return this._drafts;
}
- findCommentById(
- commentId?: UrlEncodedCommentId
- ): CommentInfo | DraftInfo | undefined {
- if (!commentId) return undefined;
- const findComment = (comments: {
- [path: string]: (CommentInfo | DraftInfo)[];
- }) => {
- let comment;
- for (const path of Object.keys(comments)) {
- comment = comment || comments[path].find(c => c.id === commentId);
- }
- return comment;
- };
- return (
- findComment(this._comments) ||
- findComment(this._robotComments) ||
- findComment(this._drafts)
- );
- }
-
/**
* Get an object mapping file paths to a boolean representing whether that
* path contains diff comments in the given patch set (including drafts and
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index eb2b494..de92b16 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -26,12 +26,7 @@
isInBaseOfPatchRange,
isInRevisionOfPatchRange,
} from '../../../utils/comment-util';
-import {
- CommitRange,
- CoverageRange,
- DiffLayer,
- PatchSetFile,
-} from '../../../types/types';
+import {CoverageRange, DiffLayer, PatchSetFile} from '../../../types/types';
import {
Base64ImageFile,
BlameInfo,
@@ -192,9 +187,6 @@
fire(this, 'is-image-diff-changed', {value: isImageDiff});
}
- @property({type: Object})
- commitRange?: CommitRange;
-
@state()
private _editWeblinks?: GeneratedWebLink[];
@@ -607,7 +599,11 @@
this.hasReloadBeenCalledOnce = true;
this.reporting.time(Timing.DIFF_TOTAL);
this.reporting.time(Timing.DIFF_LOAD);
+ // TODO: Find better names for these 3 clear/cancel methods. Ideally the
+ // <gr-diff-host> should not re-used at all for another diff rendering pass.
this.clear();
+ this.cancel();
+ this.clearDiffContent();
assertIsDefined(this.path, 'path');
assertIsDefined(this.changeNum, 'changeNum');
this.diff = undefined;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index d17c858..a9cbcbd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -20,21 +20,12 @@
import '../gr-diff-preferences-dialog/gr-diff-preferences-dialog';
import '../gr-patch-range-select/gr-patch-range-select';
import '../../change/gr-download-dialog/gr-download-dialog';
-import {navigationToken} from '../../core/gr-navigation/gr-navigation';
import {getAppContext} from '../../../services/app-context';
+import {isMergeParent, getParentIndex} from '../../../utils/patch-set-util';
import {
- computeAllPatchSets,
- computeLatestPatchNum,
- PatchSet,
- isMergeParent,
- getParentIndex,
-} from '../../../utils/patch-set-util';
-import {
- addUnmodifiedFiles,
computeDisplayPath,
computeTruncatedPath,
isMagicPath,
- specialFilePathCompare,
} from '../../../utils/path-list-util';
import {changeBaseURL, changeIsOpen} from '../../../utils/change-util';
import {GrDiffHost} from '../../diff/gr-diff-host/gr-diff-host';
@@ -45,52 +36,33 @@
import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
import {
BasePatchSetNum,
- ChangeInfo,
- CommitId,
EDIT,
- FileInfo,
NumericChangeId,
PARENT,
PatchRange,
- PatchSetNum,
PatchSetNumber,
PreferencesInfo,
RepoName,
- RevisionInfo,
RevisionPatchSetNum,
ServerInfo,
} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {
- CommitRange,
- EditRevisionInfo,
- FileRange,
- ParsedChangeInfo,
-} from '../../../types/types';
+import {FileRange, ParsedChangeInfo} from '../../../types/types';
import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import {CommentSide, DiffViewMode, Side} from '../../../constants/constants';
import {GrApplyFixDialog} from '../gr-apply-fix-dialog/gr-apply-fix-dialog';
-import {RevisionInfo as RevisionInfoObj} from '../../shared/revision-info/revision-info';
-import {
- CommentMap,
- getPatchRangeForCommentUrl,
- isInBaseOfPatchRange,
-} from '../../../utils/comment-util';
+import {CommentMap} from '../../../utils/comment-util';
import {
EventType,
OpenFixPreviewEvent,
ValueChangedEvent,
} from '../../../types/events';
import {fireAlert, fireEvent, fireTitleChange} from '../../../utils/event-util';
-import {
- GerritView,
- routerModelToken,
-} from '../../../services/router/router-model';
import {assertIsDefined, queryAndAssert} from '../../../utils/common-util';
import {Key, toggleClass, whenVisible} from '../../../utils/dom-util';
import {CursorMoveResult} from '../../../api/core';
-import {isFalse, throttleWrap, until} from '../../../utils/async-util';
+import {throttleWrap} from '../../../utils/async-util';
import {filter, take, switchMap} from 'rxjs/operators';
import {combineLatest} from 'rxjs';
import {
@@ -98,14 +70,12 @@
ShortcutSection,
shortcutsServiceToken,
} from '../../../services/shortcuts/shortcuts-service';
-import {LoadingStatus} from '../../../models/change/change-model';
import {DisplayLine} from '../../../api/diff';
import {GrDownloadDialog} from '../../change/gr-download-dialog/gr-download-dialog';
import {commentsModelToken} from '../../../models/comments/comments-model';
import {changeModelToken} from '../../../models/change/change-model';
import {resolve} from '../../../models/dependency';
-import {BehaviorSubject} from 'rxjs';
-import {css, html, LitElement, PropertyValues} from 'lit';
+import {css, html, LitElement, nothing, PropertyValues} from 'lit';
import {ShortcutController} from '../../lit/shortcut-controller';
import {subscribe} from '../../lit/subscription-controller';
import {customElement, property, query, state} from 'lit/decorators.js';
@@ -116,16 +86,18 @@
import {when} from 'lit/directives/when.js';
import {
createDiffUrl,
- diffViewModelToken,
- DiffViewState,
-} from '../../../models/views/diff';
-import {createChangeUrl} from '../../../models/views/change';
-import {createEditUrl} from '../../../models/views/edit';
+ ChangeChildView,
+ changeViewModelToken,
+} from '../../../models/views/change';
import {GeneratedWebLink} from '../../../utils/weblink-util';
import {userModelToken} from '../../../models/user/user-model';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
import {GrDiffPreferencesDialog} from '../gr-diff-preferences-dialog/gr-diff-preferences-dialog';
+import {
+ FileNameToNormalizedFileInfoMap,
+ filesModelToken,
+} from '../../../models/change/files-model';
const LOADING_BLAME = 'Loading blame...';
const LOADED_BLAME = 'Blame loaded';
@@ -135,14 +107,11 @@
// visible for testing
export interface Files {
- sortedFileList: string[];
- changeFilesByPath: {[path: string]: FileInfo};
+ /** All file paths sorted by `specialFilePathCompare`. */
+ sortedPaths: string[];
+ changeFilesByPath: FileNameToNormalizedFileInfoMap;
}
-interface CommentSkips {
- previous: string | null;
- next: string | null;
-}
@customElement('gr-diff-view')
export class GrDiffView extends LitElement {
/**
@@ -159,8 +128,8 @@
@query('#diffHost')
diffHost?: GrDiffHost;
- @query('#reviewed')
- reviewed?: HTMLInputElement;
+ @state()
+ reviewed = false;
@query('#downloadModal')
downloadModal?: HTMLDialogElement;
@@ -177,33 +146,31 @@
@query('#diffPreferencesDialog')
diffPreferencesDialog?: GrDiffPreferencesDialog;
- private _viewState: DiffViewState | undefined;
-
+ // Private but used in tests.
@state()
- get viewState(): DiffViewState | undefined {
- return this._viewState;
- }
-
- set viewState(viewState: DiffViewState | undefined) {
- if (this._viewState === viewState) return;
- const oldViewState = this._viewState;
- this._viewState = viewState;
- this.viewStateChanged();
- this.requestUpdate('viewState', oldViewState);
+ get patchRange(): PatchRange | undefined {
+ if (!this.patchNum) return undefined;
+ return {
+ patchNum: this.patchNum,
+ basePatchNum: this.basePatchNum,
+ };
}
// Private but used in tests.
@state()
- patchRange?: PatchRange;
+ patchNum?: RevisionPatchSetNum;
// Private but used in tests.
@state()
- commitRange?: CommitRange;
+ basePatchNum: BasePatchSetNum = PARENT;
// Private but used in tests.
@state()
change?: ParsedChangeInfo;
+ @state()
+ latestPatchNum?: PatchSetNumber;
+
// Private but used in tests.
@state()
changeComments?: ChangeComments;
@@ -216,10 +183,9 @@
@state()
diff?: DiffInfo;
- // TODO: Move to using files-model.
// Private but used in tests.
@state()
- files: Files = {sortedFileList: [], changeFilesByPath: {}};
+ files: Files = {sortedPaths: [], changeFilesByPath: {}};
// Private but used in tests
// Use path getter/setter.
@@ -237,13 +203,13 @@
this.requestUpdate('path', oldPath);
}
+ /** Allows us to react when the user switches to the DIFF view. */
// Private but used in tests.
- @state()
- loggedIn = false;
+ @state() isActiveChildView = false;
// Private but used in tests.
@state()
- loading = true;
+ loggedIn = false;
@property({type: Object})
prefs?: DiffPreferencesInfo;
@@ -266,65 +232,61 @@
// Private but used in tests.
@state()
- commentMap?: CommentMap;
-
- @state()
- private commentSkips?: CommentSkips;
-
- // Private but used in tests.
- @state()
isBlameLoaded?: boolean;
@state()
private isBlameLoading = false;
- @state()
- private allPatchSets?: PatchSet[] = [];
-
+ /** Directly reflects the view model property `diffView.lineNum`. */
// Private but used in tests.
@state()
focusLineNum?: number;
+ /** Directly reflects the view model property `diffView.leftSide`. */
+ @state()
+ leftSide = false;
+
// visible for testing
reviewedFiles = new Set<string>();
private readonly reporting = getAppContext().reportingService;
- private readonly restApiService = getAppContext().restApiService;
-
- private readonly getRouterModel = resolve(this, routerModelToken);
-
private readonly getUserModel = resolve(this, userModelToken);
private readonly getChangeModel = resolve(this, changeModelToken);
private readonly getCommentsModel = resolve(this, commentsModelToken);
+ private readonly getFilesModel = resolve(this, filesModelToken);
+
private readonly getShortcutsService = resolve(this, shortcutsServiceToken);
private readonly getConfigModel = resolve(this, configModelToken);
- private readonly getViewModel = resolve(this, diffViewModelToken);
+ private readonly getViewModel = resolve(this, changeViewModelToken);
private throttledToggleFileReviewed?: (e: KeyboardEvent) => void;
@state()
cursor?: GrDiffCursor;
- private connected$ = new BehaviorSubject(false);
-
private readonly shortcutsController = new ShortcutController(this);
- private readonly getNavigation = resolve(this, navigationToken);
-
constructor() {
super();
this.setupKeyboardShortcuts();
this.setupSubscriptions();
subscribe(
this,
- () => this.getViewModel().state$,
- x => (this.viewState = x)
+ () => this.getFilesModel().filesIncludingUnmodified$,
+ files => {
+ const filesByPath: FileNameToNormalizedFileInfoMap = {};
+ for (const f of files) filesByPath[f.__path] = f;
+ this.files = {
+ sortedPaths: files.map(f => f.__path),
+ changeFilesByPath: filesByPath,
+ };
+ }
);
}
@@ -338,10 +300,10 @@
listen(Shortcut.PREV_LINE, _ => this.handlePrevLine());
listen(Shortcut.VISIBLE_LINE, _ => this.cursor?.moveToVisibleArea());
listen(Shortcut.NEXT_FILE_WITH_COMMENTS, _ =>
- this.moveToNextFileWithComment()
+ this.moveToFileWithComment(1)
);
listen(Shortcut.PREV_FILE_WITH_COMMENTS, _ =>
- this.moveToPreviousFileWithComment()
+ this.moveToFileWithComment(-1)
);
listen(Shortcut.NEW_COMMENT, _ => this.handleNewComment());
listen(Shortcut.SAVE_COMMENT, _ => {});
@@ -354,7 +316,9 @@
listen(Shortcut.OPEN_REPLY_DIALOG, _ => this.handleOpenReplyDialog());
listen(Shortcut.TOGGLE_LEFT_PANE, _ => this.handleToggleLeftPane());
listen(Shortcut.OPEN_DOWNLOAD_DIALOG, _ => this.handleOpenDownloadDialog());
- listen(Shortcut.UP_TO_CHANGE, _ => this.handleUpToChange());
+ listen(Shortcut.UP_TO_CHANGE, _ =>
+ this.getChangeModel().navigateToChange()
+ );
listen(Shortcut.OPEN_DIFF_PREFS, _ => this.handleCommaKey());
listen(Shortcut.TOGGLE_DIFF_MODE, _ => this.handleToggleDiffMode());
listen(Shortcut.TOGGLE_FILE_REVIEWED, e => {
@@ -437,6 +401,11 @@
);
subscribe(
this,
+ () => this.getChangeModel().latestPatchNum$,
+ latestPatchNum => (this.latestPatchNum = latestPatchNum)
+ );
+ subscribe(
+ this,
() => this.getChangeModel().reviewedFiles$,
reviewedFiles => {
this.reviewedFiles = new Set(reviewedFiles) ?? new Set();
@@ -444,45 +413,82 @@
);
subscribe(
this,
- () => this.getChangeModel().diffPath$,
+ () => this.getViewModel().changeNum$,
+ changeNum => {
+ if (!changeNum || this.changeNum === changeNum) return;
+
+ // We are only setting the changeNum of the diff view once!
+ // Everything in the diff view is tied to the change. It seems better to
+ // force the re-creation of the diff view when the change number changes.
+ if (!this.changeNum) {
+ this.changeNum = changeNum;
+ } else {
+ fireEvent(this, EventType.RECREATE_DIFF_VIEW);
+ }
+ }
+ );
+ subscribe(
+ this,
+ () => this.getViewModel().childView$,
+ childView => (this.isActiveChildView = childView === ChangeChildView.DIFF)
+ );
+ subscribe(
+ this,
+ () => this.getViewModel().diffPath$,
path => (this.path = path)
);
-
+ subscribe(
+ this,
+ () => this.getViewModel().diffLine$,
+ line => (this.focusLineNum = line)
+ );
+ subscribe(
+ this,
+ () => this.getViewModel().diffLeftSide$,
+ leftSide => (this.leftSide = leftSide)
+ );
+ subscribe(
+ this,
+ () => this.getViewModel().patchNum$,
+ patchNum => (this.patchNum = patchNum)
+ );
+ subscribe(
+ this,
+ () => this.getViewModel().basePatchNum$,
+ basePatchNum => (this.basePatchNum = basePatchNum ?? PARENT)
+ );
subscribe(
this,
() =>
combineLatest([
- this.getChangeModel().diffPath$,
+ this.getViewModel().diffPath$,
this.getChangeModel().reviewedFiles$,
]),
([path, files]) => {
- this.updateComplete.then(() => {
- assertIsDefined(this.reviewed, 'reviewed');
- this.reviewed.checked = !!path && !!files && files.includes(path);
- });
+ this.reviewed = !!path && !!files && files.includes(path);
}
);
- // When user initially loads the diff view, we want to autmatically mark
+ // When user initially loads the diff view, we want to automatically mark
// the file as reviewed if they have it enabled. We can't observe these
// properties since the method will be called anytime a property updates
// but we only want to call this on the initial load.
subscribe(
this,
() =>
- this.getChangeModel().diffPath$.pipe(
+ this.getViewModel().diffPath$.pipe(
filter(diffPath => !!diffPath),
switchMap(() =>
combineLatest([
this.getChangeModel().patchNum$,
- this.getRouterModel().routerView$,
+ this.getViewModel().childView$,
this.getUserModel().diffPreferences$,
this.getChangeModel().reviewedFiles$,
]).pipe(
filter(
- ([patchNum, routerView, diffPrefs, reviewedFiles]) =>
+ ([patchNum, childView, diffPrefs, reviewedFiles]) =>
!!patchNum &&
- routerView === GerritView.DIFF &&
+ childView === ChangeChildView.DIFF &&
!!diffPrefs &&
!!reviewedFiles
),
@@ -491,14 +497,11 @@
)
),
([patchNum, _routerView, diffPrefs]) => {
- this.setReviewedStatus(patchNum!, diffPrefs);
+ // `patchNum` must be defined, because of the `!!patchNum` filter above.
+ assertIsDefined(patchNum, 'patchNum');
+ this.setReviewedStatus(patchNum, diffPrefs);
}
);
- subscribe(
- this,
- () => this.getChangeModel().diffPath$,
- path => (this.path = path)
- );
}
static override get styles() {
@@ -691,7 +694,6 @@
override connectedCallback() {
super.connectedCallback();
- this.connected$.next(true);
this.throttledToggleFileReviewed = throttleWrap(_ =>
this.handleToggleFileReviewed()
);
@@ -702,38 +704,11 @@
override disconnectedCallback() {
this.cursor?.dispose();
- this.connected$.next(false);
super.disconnectedCallback();
}
- protected override willUpdate(changedProperties: PropertyValues) {
- super.willUpdate(changedProperties);
- if (changedProperties.has('change')) {
- this.allPatchSets = computeAllPatchSets(this.change);
- }
- if (
- changedProperties.has('commentMap') ||
- changedProperties.has('files') ||
- changedProperties.has('path')
- ) {
- this.commentSkips = this.computeCommentSkips(
- this.commentMap,
- this.files?.sortedFileList,
- this.path
- );
- }
-
- if (
- changedProperties.has('changeNum') ||
- changedProperties.has('changeComments') ||
- changedProperties.has('patchRange')
- ) {
- this.fetchFiles();
- }
- }
-
private reInitCursor() {
- assertIsDefined(this.diffHost, 'diffHost');
+ if (!this.diffHost) return;
this.cursor?.replaceDiffs([this.diffHost]);
this.cursor?.reInitCursor();
}
@@ -741,16 +716,35 @@
protected override updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (
+ changedProperties.has('change') ||
+ changedProperties.has('path') ||
+ changedProperties.has('patchNum') ||
+ changedProperties.has('basePatchNum')
+ ) {
+ this.reloadDiff();
+ } else if (
+ changedProperties.has('isActiveChildView') &&
+ this.isActiveChildView
+ ) {
+ this.initializePositions();
+ }
+ if (
+ changedProperties.has('focusLineNum') ||
+ changedProperties.has('leftSide')
+ ) {
+ this.initLineOfInterestAndCursor();
+ }
+ if (
+ changedProperties.has('change') ||
changedProperties.has('changeComments') ||
changedProperties.has('path') ||
- changedProperties.has('patchRange') ||
+ changedProperties.has('patchNum') ||
+ changedProperties.has('basePatchNum') ||
changedProperties.has('files')
) {
- if (this.changeComments && this.path && this.patchRange) {
+ if (this.change && this.changeComments && this.path && this.patchRange) {
assertIsDefined(this.diffHost, 'diffHost');
- const file = this.files?.changeFilesByPath
- ? this.files.changeFilesByPath[this.path]
- : undefined;
+ const file = this.files?.changeFilesByPath?.[this.path];
this.diffHost.updateComplete.then(() => {
assertIsDefined(this.path);
assertIsDefined(this.patchRange);
@@ -766,17 +760,18 @@
}
override render() {
+ if (!this.isActiveChildView) return nothing;
+ if (!this.patchNum || !this.changeNum || !this.change || !this.path) {
+ return html`<div class="loading">Loading...</div>`;
+ }
const file = this.getFileRange();
return html`
${this.renderStickyHeader()}
- <div class="loading" ?hidden=${!this.loading}>Loading...</div>
<h2 class="assistive-tech-only">Diff view</h2>
<gr-diff-host
id="diffHost"
- ?hidden=${this.loading}
.changeNum=${this.changeNum}
.change=${this.change}
- .commitRange=${this.commitRange}
.patchRange=${this.patchRange}
.file=${file}
.path=${this.path}
@@ -797,7 +792,7 @@
private renderStickyHeader() {
return html` <div
- class="stickyHeader ${this.computeEditMode() ? 'editMode' : ''}"
+ class="stickyHeader ${this.patchNum === EDIT ? 'editMode' : ''}"
>
<h1 class="assistive-tech-only">
Diff of ${this.path ? computeTruncatedPath(this.path) : ''}
@@ -823,7 +818,8 @@
const fileNum = this.computeFileNum(formattedFiles);
const fileNumClass = this.computeFileNumClass(fileNum, formattedFiles);
return html` <div>
- <a href=${this.getChangePath()}>${this.changeNum}</a
+ <a href=${ifDefined(this.getChangeModel().changeUrl())}
+ >${this.changeNum}</a
><span class="changeNumberColon">:</span>
<span class="headerSubject">${this.change?.subject}</span>
<input
@@ -833,6 +829,7 @@
?hidden=${!this.loggedIn}
title="Toggle reviewed status of file"
aria-label="file reviewed"
+ .checked=${this.reviewed}
@change=${this.handleReviewedChange}
/>
<div class="jumpToFileContainer">
@@ -866,7 +863,7 @@
Shortcut.UP_TO_CHANGE,
ShortcutSection.NAVIGATION
)}
- href=${this.getChangePath()}
+ href=${ifDefined(this.getChangeModel().changeUrl())}
>Up</a
>
<span class="separator"></span>
@@ -883,19 +880,10 @@
}
private renderPatchRangeLeft() {
- const revisionInfo = this.change
- ? new RevisionInfoObj(this.change)
- : undefined;
return html` <div class="patchRangeLeft">
<gr-patch-range-select
id="rangeSelect"
- .changeNum=${this.changeNum}
- .patchNum=${this.patchRange?.patchNum}
- .basePatchNum=${this.patchRange?.basePatchNum}
.filesWeblinks=${this.filesWeblinks}
- .availablePatches=${this.allPatchSets}
- .revisions=${this.change?.revisions}
- .revisionInfo=${revisionInfo}
@patch-range-change=${this.handlePatchChange}
>
</gr-patch-range-select>
@@ -1022,7 +1010,7 @@
<gr-download-dialog
id="downloadDialog"
.change=${this.change}
- .patchNum=${this.patchRange?.patchNum}
+ .patchNum=${this.patchNum}
.config=${this.serverConfig?.download}
@close=${this.handleDownloadDialogClose}
></gr-download-dialog>
@@ -1047,36 +1035,12 @@
if (!this.files || !this.path) return;
const fileInfo = this.files.changeFilesByPath[this.path];
const fileRange: FileRange = {path: this.path};
- if (fileInfo && fileInfo.old_path) {
+ if (fileInfo?.old_path) {
fileRange.basePath = fileInfo.old_path;
}
return fileRange;
}
- // Private but used in tests.
- fetchFiles() {
- if (!this.changeNum || !this.patchRange || !this.changeComments) {
- return Promise.resolve();
- }
-
- if (!this.patchRange.patchNum) {
- return Promise.resolve();
- }
-
- return this.restApiService
- .getChangeFiles(this.changeNum, this.patchRange)
- .then(changeFiles => {
- if (!changeFiles) return;
- const commentedPaths = this.changeComments!.getPaths(this.patchRange);
- const files = {...changeFiles};
- addUnmodifiedFiles(files, commentedPaths);
- this.files = {
- sortedFileList: Object.keys(files).sort(specialFilePathCompare),
- changeFilesByPath: files,
- };
- });
- }
-
private handleReviewedChange(e: Event) {
const input = e.target as HTMLInputElement;
this.setReviewed(input.checked ?? false);
@@ -1085,12 +1049,14 @@
// Private but used in tests.
setReviewed(
reviewed: boolean,
- patchNum: RevisionPatchSetNum | undefined = this.patchRange?.patchNum
+ patchNum: RevisionPatchSetNum | undefined = this.patchNum
) {
- if (this.computeEditMode()) return;
+ if (this.patchNum === EDIT) return;
if (!patchNum || !this.path || !this.changeNum) return;
// if file is already reviewed then do not make a saveReview request
if (this.reviewedFiles.has(this.path) && reviewed) return;
+ // optimistic update
+ this.reviewed = reviewed;
this.getChangeModel().setReviewedFilesStatus(
this.changeNum,
patchNum,
@@ -1101,8 +1067,7 @@
// Private but used in tests.
handleToggleFileReviewed() {
- assertIsDefined(this.reviewed);
- this.setReviewed(!this.reviewed.checked);
+ this.setReviewed(!this.reviewed);
}
private handlePrevLine() {
@@ -1147,48 +1112,13 @@
}
// Private but used in tests.
- moveToPreviousFileWithComment() {
- if (!this.commentSkips) return;
- if (!this.change) return;
- if (!this.patchRange?.patchNum) return;
-
- // If there is no previous diff with comments, then return to the change
- // view.
- if (!this.commentSkips.previous) {
- this.navToChangeView();
- return;
+ moveToFileWithComment(direction: -1 | 1) {
+ const path = this.findFileWithComment(direction);
+ if (!path) {
+ this.getChangeModel().navigateToChange();
+ } else {
+ this.getChangeModel().navigateToDiff({path});
}
-
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.commentSkips.previous,
- patchNum: this.patchRange.patchNum,
- basePatchNum: this.patchRange.basePatchNum,
- })
- );
- }
-
- // Private but used in tests.
- moveToNextFileWithComment() {
- if (!this.commentSkips) return;
- if (!this.change) return;
- if (!this.patchRange?.patchNum) return;
-
- // If there is no next diff with comments, then return to the change view.
- if (!this.commentSkips.next) {
- this.navToChangeView();
- return;
- }
-
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.commentSkips.next,
- patchNum: this.patchRange.patchNum,
- basePatchNum: this.patchRange.basePatchNum,
- })
- );
}
private handleNewComment() {
@@ -1198,14 +1128,14 @@
private handlePrevFile() {
if (!this.path) return;
- if (!this.files?.sortedFileList) return;
- this.navToFile(this.files.sortedFileList, -1);
+ if (!this.files?.sortedPaths) return;
+ this.navToFile(this.files.sortedPaths, -1);
}
private handleNextFile() {
if (!this.path) return;
- if (!this.files?.sortedFileList) return;
- this.navToFile(this.files.sortedFileList, 1);
+ if (!this.files?.sortedPaths) return;
+ this.navToFile(this.files.sortedPaths, 1);
}
private handleNextChunk() {
@@ -1249,11 +1179,11 @@
private navigateToUnreviewedFile(direction: string) {
if (!this.path) return;
- if (!this.files?.sortedFileList) return;
+ if (!this.files?.sortedPaths) return;
if (!this.reviewedFiles) return;
// Ensure that the currently viewed file always appears in unreviewedFiles
// so we resolve the right "next" file.
- const unreviewedFiles = this.files.sortedFileList.filter(
+ const unreviewedFiles = this.files.sortedPaths.filter(
file => file === this.path || !this.reviewedFiles.has(file)
);
@@ -1277,7 +1207,7 @@
fireEvent(this, 'show-auth-required');
return;
}
- this.navToChangeView(true);
+ this.getChangeModel().navigateToChange(true);
}
private handleToggleLeftPane() {
@@ -1313,10 +1243,6 @@
this.downloadModal.close();
}
- private handleUpToChange() {
- this.navToChangeView();
- }
-
private handleCommaKey() {
if (!this.loggedIn) return;
assertIsDefined(this.diffPreferencesDialog, 'diffPreferencesDialog');
@@ -1336,19 +1262,6 @@
}
// Private but used in tests.
- navToChangeView(openReplyDialog = false) {
- if (!this.changeNum || !this.patchRange?.patchNum) {
- return;
- }
- this.navigateToChange(
- this.change,
- this.patchRange,
- this.change && this.change.revisions,
- openReplyDialog
- );
- }
-
- // Private but used in tests.
navToFile(
fileList: string[],
direction: -1 | 1,
@@ -1356,15 +1269,10 @@
) {
const newPath = this.getNavLinkPath(fileList, direction);
if (!newPath) return;
- if (!this.change) return;
if (!this.patchRange) return;
if (newPath.up) {
- this.navigateToChange(
- this.change,
- this.patchRange,
- this.change && this.change.revisions
- );
+ this.getChangeModel().navigateToChange();
return;
}
@@ -1375,15 +1283,7 @@
newPath.path,
this.patchRange
)?.[0].line;
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: newPath.path,
- patchNum: this.patchRange.patchNum,
- basePatchNum: this.patchRange.basePatchNum,
- lineNum,
- })
- );
+ this.getChangeModel().navigateToDiff({path: newPath.path, lineNum});
}
/**
@@ -1394,35 +1294,25 @@
private computeNavLinkURL(direction?: -1 | 1) {
if (!this.change) return;
if (!this.path) return;
- if (!this.files?.sortedFileList) return;
+ if (!this.files?.sortedPaths) return;
if (!direction) return;
- const newPath = this.getNavLinkPath(this.files.sortedFileList, direction);
- if (!newPath) {
- return;
- }
-
- if (newPath.up) {
- return this.getChangePath();
- }
- return this.getDiffUrl(this.change, this.patchRange, newPath.path);
+ const newPath = this.getNavLinkPath(this.files.sortedPaths, direction);
+ if (!newPath) return;
+ if (newPath.up) return this.getChangeModel().changeUrl();
+ if (!newPath.path) return;
+ return this.getChangeModel().diffUrl({path: newPath.path});
}
private goToEditFile() {
- if (!this.change) return;
- if (!this.path) return;
- if (!this.patchRange) return;
+ assertIsDefined(this.path, 'path');
// TODO(taoalpha): add a shortcut for editing
const cursorAddress = this.cursor?.getAddress();
- const editUrl = createEditUrl({
- changeNum: this.change._number,
- repo: this.change.project,
+ this.getChangeModel().navigateToEdit({
path: this.path,
- patchNum: this.patchRange.patchNum,
lineNum: cursorAddress?.number,
});
- this.getNavigation().setUrl(editUrl);
}
/**
@@ -1444,7 +1334,6 @@
if (!this.path || !fileList || fileList.length === 0) {
return null;
}
-
let idx = fileList.indexOf(this.path);
if (idx === -1) {
const file = direction > 0 ? fileList[0] : fileList[fileList.length - 1];
@@ -1462,325 +1351,74 @@
}
// Private but used in tests.
- initLineOfInterestAndCursor(leftSide: boolean) {
- assertIsDefined(this.diffHost, 'diffHost');
- this.diffHost.lineOfInterest = this.getLineOfInterest(leftSide);
- this.initCursor(leftSide);
- }
-
- // Private but used in tests.
- displayDiffBaseAgainstLeftToast() {
- if (!this.patchRange) return;
- fireAlert(
- this,
- `Patchset ${this.patchRange.basePatchNum} vs ` +
- `${this.patchRange.patchNum} selected. Press v + \u2190 to view ` +
- `Base vs ${this.patchRange.basePatchNum}`
- );
- }
-
- private displayDiffAgainstLatestToast(latestPatchNum?: PatchSetNum) {
- if (!this.patchRange) return;
- const leftPatchset =
- this.patchRange.basePatchNum === PARENT
- ? 'Base'
- : `Patchset ${this.patchRange.basePatchNum}`;
- fireAlert(
- this,
- `${leftPatchset} vs
- ${this.patchRange.patchNum} selected\n. Press v + \u2191 to view
- ${leftPatchset} vs Patchset ${latestPatchNum}`
- );
- }
-
- private displayToasts() {
- if (!this.patchRange) return;
- if (this.patchRange.basePatchNum !== PARENT) {
- this.displayDiffBaseAgainstLeftToast();
- return;
- }
- const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
- if (this.patchRange.patchNum !== latestPatchNum) {
- this.displayDiffAgainstLatestToast(latestPatchNum);
- return;
- }
- }
-
- private initCommitRange() {
- let commit: CommitId | undefined;
- let baseCommit: CommitId | undefined;
- if (!this.change) return;
- if (!this.patchRange || !this.patchRange.patchNum) return;
- const revisions = this.change.revisions ?? {};
- for (const [commitSha, revision] of Object.entries(revisions)) {
- const patchNum = revision._number;
- if (patchNum === this.patchRange.patchNum) {
- commit = commitSha as CommitId;
- const commitObj = revision.commit;
- const parents = commitObj?.parents || [];
- if (this.patchRange.basePatchNum === PARENT && parents.length) {
- baseCommit = parents[parents.length - 1].commit;
- }
- } else if (patchNum === this.patchRange.basePatchNum) {
- baseCommit = commitSha as CommitId;
- }
- }
- this.commitRange = commit && baseCommit ? {commit, baseCommit} : undefined;
+ initLineOfInterestAndCursor() {
+ if (!this.diffHost) return;
+ this.diffHost.lineOfInterest = this.getLineOfInterest();
+ this.initCursor();
}
private updateUrlToDiffUrl(lineNum?: number, leftSide?: boolean) {
if (!this.change) return;
- if (!this.patchRange) return;
+ if (!this.patchNum) return;
if (!this.changeNum) return;
if (!this.path) return;
const url = createDiffUrl({
changeNum: this.changeNum,
repo: this.change.project,
- path: this.path,
- patchNum: this.patchRange.patchNum,
- basePatchNum: this.patchRange.basePatchNum,
- lineNum,
- leftSide,
+ patchNum: this.patchNum,
+ basePatchNum: this.basePatchNum,
+ diffView: {
+ path: this.path,
+ lineNum,
+ leftSide,
+ },
});
history.replaceState(null, '', url);
}
- // Private but used in tests.
- initPatchRange() {
- let leftSide = false;
- if (!this.change) return;
- if (this.viewState?.view !== GerritView.DIFF) return;
- if (this.viewState?.commentId) {
- const comment = this.changeComments?.findCommentById(
- this.viewState.commentId
- );
- if (!comment) {
- fireAlert(this, 'comment not found');
- this.getNavigation().setUrl(createChangeUrl({change: this.change}));
- return;
- }
- this.getChangeModel().updatePath(comment.path);
-
- const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
- if (!latestPatchNum) throw new Error('Missing allPatchSets');
- this.patchRange = getPatchRangeForCommentUrl(comment, latestPatchNum);
- leftSide = isInBaseOfPatchRange(comment, this.patchRange);
-
- this.focusLineNum = comment.line;
- } else {
- if (this.viewState.path) {
- this.getChangeModel().updatePath(this.viewState.path);
- }
- if (this.viewState.patchNum) {
- this.patchRange = {
- patchNum: this.viewState.patchNum,
- basePatchNum: this.viewState.basePatchNum || PARENT,
- };
- }
- if (this.viewState.lineNum) {
- this.focusLineNum = this.viewState.lineNum;
- leftSide = !!this.viewState.leftSide;
- }
- }
- assertIsDefined(this.patchRange, 'patchRange');
- this.initLineOfInterestAndCursor(leftSide);
-
- if (this.viewState?.commentId) {
- // url is of type /comment/{commentId} which isn't meaningful
- this.updateUrlToDiffUrl(this.focusLineNum, leftSide);
- }
-
- this.commentMap = this.getPaths();
+ async reloadDiff() {
+ if (!this.diffHost) return;
+ await this.diffHost.reload(true);
+ this.reporting.diffViewDisplayed();
+ if (this.isBlameLoaded) this.loadBlame();
}
- // Private but used in tests.
- isFileUnchanged(diff?: DiffInfo) {
- if (!diff || !diff.content) return false;
- return !diff.content.some(
- content =>
- (content.a && !content.common) || (content.b && !content.common)
- );
- }
-
- private isSameDiffLoaded(value: DiffViewState) {
- return (
- this.patchRange?.basePatchNum === value.basePatchNum &&
- this.patchRange?.patchNum === value.patchNum &&
- this.path === value.path
- );
- }
-
- private async untilModelLoaded() {
- // NOTE: Wait until this page is connected before determining whether the
- // model is loaded. This can happen when params are changed when setting up
- // this view. It's unclear whether this issue is related to Polymer
- // specifically.
- if (!this.isConnected) {
- await until(this.connected$, connected => connected);
- }
- await until(
- this.getChangeModel().changeLoadingStatus$,
- status => status === LoadingStatus.LOADED
- );
- }
-
- // Private but used in tests.
- viewStateChanged() {
- if (this.viewState === undefined) return;
- const viewState = this.viewState;
-
+ /**
+ * (Re-initialize) the diff view without actually reloading the diff. The
+ * typical user journey is that the user comes back from the change page.
+ */
+ initializePositions() {
// The diff view is kept in the background once created. If the user
// scrolls in the change page, the scrolling is reflected in the diff view
// as well, which means the diff is scrolled to a random position based
// on how much the change view was scrolled.
// Hence, reset the scroll position here.
document.documentElement.scrollTop = 0;
-
- // Everything in the diff view is tied to the change. It seems better to
- // force the re-creation of the diff view when the change number changes.
- const changeChanged = this.changeNum !== viewState.changeNum;
- if (this.changeNum !== undefined && changeChanged) {
- fireEvent(this, EventType.RECREATE_DIFF_VIEW);
- return;
- } else if (
- this.changeNum !== undefined &&
- this.isSameDiffLoaded(viewState)
- ) {
- // changeNum has not changed, so check if there are changes in patchRange
- // path. If no changes then we can simply render the view as is.
- this.reporting.reportInteraction('diff-view-re-rendered');
- // Make sure to re-initialize the cursor because this is typically
- // done on the 'render' event which doesn't fire in this path as
- // rerendering is avoided.
- this.reInitCursor();
- this.diffHost?.initLayers();
- return;
- }
-
- this.files = {sortedFileList: [], changeFilesByPath: {}};
- if (this.isConnected) {
- this.getChangeModel().updatePath(undefined);
- }
- this.patchRange = undefined;
- this.commitRange = undefined;
- this.focusLineNum = undefined;
-
- if (viewState.changeNum && viewState.repo) {
- this.restApiService.setInProjectLookup(
- viewState.changeNum,
- viewState.repo
- );
- }
-
- this.changeNum = viewState.changeNum;
+ this.reInitCursor();
+ this.diffHost?.initLayers();
this.classList.remove('hideComments');
-
- // When navigating away from the page, there is a possibility that the
- // patch number is no longer a part of the URL (say when navigating to
- // the top-level change info view) and therefore undefined in `params`.
- // If route is of type /comment/<commentId>/ then no patchNum is present
- if (!viewState.patchNum && !viewState.commentLink) {
- this.reporting.error(
- 'GrDiffView',
- new Error(`Invalid diff view URL, no patchNum found: ${this.viewState}`)
- );
- return;
- }
-
- const promises: Promise<unknown>[] = [];
- if (!this.change) {
- promises.push(this.untilModelLoaded());
- }
- promises.push(this.waitUntilCommentsLoaded());
-
- if (this.diffHost) {
- this.diffHost.cancel();
- this.diffHost.clearDiffContent();
- }
- this.loading = true;
- return Promise.all(promises)
- .then(() => {
- this.loading = false;
- this.initPatchRange();
- this.initCommitRange();
- return this.updateComplete.then(() => this.diffHost!.reload(true));
- })
- .then(() => {
- this.reporting.diffViewDisplayed();
- })
- .then(() => {
- const fileUnchanged = this.isFileUnchanged(this.diff);
- if (fileUnchanged && viewState.commentLink) {
- assertIsDefined(this.change, 'change');
- assertIsDefined(this.path, 'path');
- assertIsDefined(this.patchRange, 'patchRange');
-
- if (this.patchRange.basePatchNum === PARENT) {
- // file is unchanged between Base vs X
- // hence should not show diff between Base vs Base
- return;
- }
-
- fireAlert(
- this,
- `File is unchanged between Patchset
- ${this.patchRange.basePatchNum} and
- ${this.patchRange.patchNum}. Showing diff of Base vs
- ${this.patchRange.basePatchNum}`
- );
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.path,
- patchNum: this.patchRange.basePatchNum as RevisionPatchSetNum,
- basePatchNum: PARENT,
- lineNum: this.focusLineNum,
- })
- );
- return;
- }
- if (viewState.commentLink) {
- this.displayToasts();
- }
- // If the blame was loaded for a previous file and user navigates to
- // another file, then we load the blame for this file too
- if (this.isBlameLoaded) this.loadBlame();
- });
- }
-
- private async waitUntilCommentsLoaded() {
- await until(this.connected$, c => c);
- await until(this.getCommentsModel().commentsLoading$, isFalse);
}
/**
* If the params specify a diff address then configure the diff cursor.
* Private but used in tests.
*/
- initCursor(leftSide: boolean) {
- if (this.focusLineNum === undefined) {
- return;
- }
+ initCursor() {
+ if (!this.focusLineNum) return;
if (!this.cursor) return;
- if (leftSide) {
- this.cursor.side = Side.LEFT;
- } else {
- this.cursor.side = Side.RIGHT;
- }
+ this.cursor.side = this.leftSide ? Side.LEFT : Side.RIGHT;
this.cursor.initialLineNumber = this.focusLineNum;
}
// Private but used in tests.
- getLineOfInterest(leftSide: boolean): DisplayLine | undefined {
+ getLineOfInterest(): DisplayLine | undefined {
// If there is a line number specified, pass it along to the diff so that
// it will not get collapsed.
- if (!this.focusLineNum) {
- return undefined;
- }
+ if (!this.focusLineNum) return undefined;
return {
lineNum: this.focusLineNum,
- side: leftSide ? Side.LEFT : Side.RIGHT,
+ side: this.leftSide ? Side.LEFT : Side.RIGHT,
};
}
@@ -1790,83 +1428,6 @@
}
}
- private getDiffUrl(
- change?: ChangeInfo | ParsedChangeInfo,
- patchRange?: PatchRange,
- path?: string
- ) {
- if (!change || !patchRange || !path) return '';
- return createDiffUrl({
- changeNum: change._number,
- repo: change.project,
- path,
- patchNum: patchRange.patchNum,
- basePatchNum: patchRange.basePatchNum,
- });
- }
-
- /**
- * When the latest patch of the change is selected (and there is no base
- * patch) then the patch range need not appear in the URL. Return a patch
- * range object with undefined values when a range is not needed.
- */
- private getChangeUrlRange(
- patchRange?: PatchRange,
- revisions?: {[revisionId: string]: RevisionInfo | EditRevisionInfo}
- ) {
- let patchNum = undefined;
- let basePatchNum = undefined;
- let latestPatchNum = -1;
- for (const rev of Object.values(revisions || {})) {
- if (typeof rev._number === 'number') {
- latestPatchNum = Math.max(latestPatchNum, rev._number);
- }
- }
- if (!patchRange) return {patchNum, basePatchNum};
- if (
- patchRange.basePatchNum !== PARENT ||
- patchRange.patchNum !== latestPatchNum
- ) {
- patchNum = patchRange.patchNum;
- basePatchNum = patchRange.basePatchNum;
- }
- return {patchNum, basePatchNum};
- }
-
- private getChangePath() {
- if (!this.change) return '';
- if (!this.patchRange) return '';
-
- const range = this.getChangeUrlRange(
- this.patchRange,
- this.change.revisions
- );
- return createChangeUrl({
- change: this.change,
- patchNum: range.patchNum,
- basePatchNum: range.basePatchNum,
- });
- }
-
- // Private but used in tests.
- navigateToChange(
- change?: ChangeInfo | ParsedChangeInfo,
- patchRange?: PatchRange,
- revisions?: {[revisionId: string]: RevisionInfo | EditRevisionInfo},
- openReplyDialog?: boolean
- ) {
- if (!change) return;
- const range = this.getChangeUrlRange(patchRange, revisions);
- this.getNavigation().setUrl(
- createChangeUrl({
- change,
- patchNum: range.patchNum,
- basePatchNum: range.basePatchNum,
- openReplyDialog: !!openReplyDialog,
- })
- );
- }
-
// Private but used in tests
formatFilesForDropdown(): DropdownItem[] {
if (!this.files) return [];
@@ -1874,7 +1435,8 @@
if (!this.changeComments) return [];
const dropdownContent: DropdownItem[] = [];
- for (const path of this.files.sortedFileList) {
+ for (const path of this.files.sortedPaths) {
+ const file = this.files.changeFilesByPath[path];
dropdownContent.push({
text: computeDisplayPath(path),
mobileText: computeTruncatedPath(path),
@@ -1882,10 +1444,10 @@
bottomText: this.changeComments.computeCommentsString(
this.patchRange,
path,
- this.files.changeFilesByPath[path],
+ file,
/* includeUnmodified= */ true
),
- file: {...this.files.changeFilesByPath[path], __path: path},
+ file,
});
}
return dropdownContent;
@@ -1893,45 +1455,24 @@
// Private but used in tests.
handleFileChange(e: CustomEvent) {
- if (!this.change) return;
- if (!this.patchRange) return;
-
- // This is when it gets set initially.
const path = e.detail.value;
- if (path === this.path) {
- return;
- }
-
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path,
- patchNum: this.patchRange.patchNum,
- basePatchNum: this.patchRange.basePatchNum,
- })
- );
+ if (path === this.path) return;
+ this.getChangeModel().navigateToDiff(path);
}
// Private but used in tests.
handlePatchChange(e: CustomEvent) {
- if (!this.change) return;
if (!this.path) return;
- if (!this.patchRange) return;
+ if (!this.patchNum) return;
const {basePatchNum, patchNum} = e.detail;
- if (
- basePatchNum === this.patchRange.basePatchNum &&
- patchNum === this.patchRange.patchNum
- ) {
+ if (basePatchNum === this.basePatchNum && patchNum === this.patchNum) {
return;
}
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.path,
- patchNum,
- basePatchNum,
- })
+ this.getChangeModel().navigateToDiff(
+ {path: this.path},
+ patchNum,
+ basePatchNum
);
}
@@ -1956,7 +1497,7 @@
computeDownloadDropdownLinks() {
if (!this.change?.project) return [];
if (!this.changeNum) return [];
- if (!this.patchRange?.patchNum) return [];
+ if (!this.patchRange) return [];
if (!this.path) return [];
const links = [
@@ -2004,6 +1545,7 @@
return links;
}
+ // TODO: Move to view-model or router.
// Private but used in tests.
computeDownloadFileLink(
repo: RepoName,
@@ -2032,6 +1574,7 @@
return url;
}
+ // TODO: Move to view-model or router.
// Private but used in tests.
computeDownloadPatchLink(
repo: RepoName,
@@ -2045,49 +1588,19 @@
}
// Private but used in tests.
- getPaths(): CommentMap {
- if (!this.changeComments) return {};
- return this.changeComments.getPaths(this.patchRange);
- }
+ findFileWithComment(direction: -1 | 1): string | undefined {
+ const fileList = this.files?.sortedPaths;
+ const commentMap: CommentMap =
+ this.changeComments?.getPaths(this.patchRange) ?? {};
+ if (!fileList || fileList.length === 0) return undefined;
+ if (!this.path) return undefined;
- // Private but used in tests.
- computeCommentSkips(
- commentMap?: CommentMap,
- fileList?: string[],
- path?: string
- ): CommentSkips | undefined {
- if (!commentMap) return undefined;
- if (!fileList) return undefined;
- if (!path) return undefined;
-
- const skips: CommentSkips = {previous: null, next: null};
- if (!fileList.length) {
- return skips;
+ const pathIndex = fileList.indexOf(this.path);
+ const stopIndex = direction === 1 ? fileList.length : -1;
+ for (let i = pathIndex + direction; i !== stopIndex; i += direction) {
+ if (commentMap[fileList[i]]) return fileList[i];
}
- const pathIndex = fileList.indexOf(path);
-
- // Scan backward for the previous file.
- for (let i = pathIndex - 1; i >= 0; i--) {
- if (commentMap[fileList[i]]) {
- skips.previous = fileList[i];
- break;
- }
- }
-
- // Scan forward for the next file.
- for (let i = pathIndex + 1; i < fileList.length; i++) {
- if (commentMap[fileList[i]]) {
- skips.next = fileList[i];
- break;
- }
- }
-
- return skips;
- }
-
- // Private but used in tests.
- computeEditMode() {
- return this.patchRange?.patchNum === EDIT;
+ return undefined;
}
// Private but used in tests.
@@ -2130,111 +1643,89 @@
// Private but used in tests.
handleDiffAgainstBase() {
- if (!this.change) return;
- if (!this.path) return;
- if (!this.patchRange) return;
+ if (!this.isActiveChildView) return;
+ assertIsDefined(this.path, 'path');
+ assertIsDefined(this.patchNum, 'patchNum');
- if (this.patchRange.basePatchNum === PARENT) {
+ if (this.basePatchNum === PARENT) {
fireAlert(this, 'Base is already selected.');
return;
}
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.path,
- patchNum: this.patchRange.patchNum,
- })
+ this.getChangeModel().navigateToDiff(
+ {path: this.path},
+ this.patchNum,
+ PARENT
);
}
// Private but used in tests.
handleDiffBaseAgainstLeft() {
- if (!this.change) return;
- if (!this.path) return;
- if (!this.patchRange) return;
+ if (!this.isActiveChildView) return;
+ assertIsDefined(this.path, 'path');
+ assertIsDefined(this.patchNum, 'patchNum');
- if (this.patchRange.basePatchNum === PARENT) {
+ if (this.basePatchNum === PARENT) {
fireAlert(this, 'Left is already base.');
return;
}
- const lineNum =
- this.viewState?.view === GerritView.DIFF && this.viewState?.commentLink
- ? this.focusLineNum
- : undefined;
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.path,
- patchNum: this.patchRange.basePatchNum as RevisionPatchSetNum,
- basePatchNum: PARENT,
- lineNum,
- })
+ this.getChangeModel().navigateToDiff(
+ {path: this.path},
+ this.basePatchNum as RevisionPatchSetNum,
+ PARENT
);
}
// Private but used in tests.
handleDiffAgainstLatest() {
- if (!this.change) return;
- if (!this.path) return;
- if (!this.patchRange) return;
+ if (!this.isActiveChildView) return;
+ assertIsDefined(this.path, 'path');
+ assertIsDefined(this.patchNum, 'patchNum');
- const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
- if (this.patchRange.patchNum === latestPatchNum) {
+ if (this.patchNum === this.latestPatchNum) {
fireAlert(this, 'Latest is already selected.');
return;
}
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.path,
- patchNum: latestPatchNum,
- basePatchNum: this.patchRange.basePatchNum,
- })
+ this.getChangeModel().navigateToDiff(
+ {path: this.path},
+ this.latestPatchNum,
+ this.basePatchNum
);
}
// Private but used in tests.
handleDiffRightAgainstLatest() {
- if (!this.change) return;
- if (!this.path) return;
- if (!this.patchRange) return;
+ if (!this.isActiveChildView) return;
+ assertIsDefined(this.path, 'path');
+ assertIsDefined(this.patchNum, 'patchNum');
- const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
- if (this.patchRange.patchNum === latestPatchNum) {
+ if (this.patchNum === this.latestPatchNum) {
fireAlert(this, 'Right is already latest.');
return;
}
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.path,
- patchNum: latestPatchNum,
- basePatchNum: this.patchRange.patchNum as BasePatchSetNum,
- })
+
+ this.getChangeModel().navigateToDiff(
+ {path: this.path},
+ this.latestPatchNum,
+ this.patchNum as BasePatchSetNum
);
}
// Private but used in tests.
handleDiffBaseAgainstLatest() {
- if (!this.change) return;
- if (!this.path) return;
- if (!this.patchRange) return;
+ if (!this.isActiveChildView) return;
+ assertIsDefined(this.path, 'path');
+ assertIsDefined(this.patchNum, 'patchNum');
- const latestPatchNum = computeLatestPatchNum(this.allPatchSets);
- if (
- this.patchRange.patchNum === latestPatchNum &&
- this.patchRange.basePatchNum === PARENT
- ) {
+ if (this.patchNum === this.latestPatchNum && this.basePatchNum === PARENT) {
fireAlert(this, 'Already diffing base against latest.');
return;
}
- this.getNavigation().setUrl(
- createDiffUrl({
- change: this.change,
- path: this.path,
- patchNum: latestPatchNum,
- })
+
+ this.getChangeModel().navigateToDiff(
+ {path: this.path},
+ this.latestPatchNum,
+ PARENT
);
}
@@ -2265,13 +1756,13 @@
private navigateToNextFileWithCommentThread() {
if (!this.path) return;
- if (!this.files?.sortedFileList) return;
- if (!this.patchRange) return;
+ if (!this.files?.sortedPaths) return;
+ const range = this.patchRange;
+ if (!range) return;
if (!this.change) return;
const hasComment = (path: string) =>
- this.changeComments?.getCommentsForPath(path, this.patchRange!)?.length ??
- 0 > 0;
- const filesWithComments = this.files.sortedFileList.filter(
+ this.changeComments?.getCommentsForPath(path, range)?.length ?? 0 > 0;
+ const filesWithComments = this.files.sortedPaths.filter(
file => file === this.path || hasComment(file)
);
this.navToFile(filesWithComments, 1, true);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
index 6a565eb..1999a82 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
@@ -5,7 +5,6 @@
*/
import '../../../test/common-test-setup';
import './gr-diff-view';
-import {navigationToken} from '../../core/gr-navigation/gr-navigation';
import {
ChangeStatus,
DiffViewMode,
@@ -18,38 +17,29 @@
query,
queryAll,
queryAndAssert,
- stubReporting,
stubRestApi,
waitEventLoop,
waitUntil,
} from '../../../test/test-utils';
import {ChangeComments} from '../gr-comment-api/gr-comment-api';
import {
- GerritView,
- routerModelToken,
-} from '../../../services/router/router-model';
-import {
createRevisions,
createComment as createCommentGeneric,
- TEST_NUMERIC_CHANGE_ID,
createDiff,
- createPatchRange,
createServerInfo,
createConfig,
createParsedChange,
createRevision,
- createCommit,
createFileInfo,
+ createDiffViewState,
+ TEST_NUMERIC_CHANGE_ID,
} from '../../../test/test-data-generators';
import {
BasePatchSetNum,
CommentInfo,
- CommitId,
EDIT,
- FileInfo,
NumericChangeId,
PARENT,
- PatchRange,
PatchSetNum,
PatchSetNumber,
PathToCommentsInfoMap,
@@ -58,17 +48,15 @@
UrlEncodedCommentId,
} from '../../../types/common';
import {CursorMoveResult} from '../../../api/core';
-import {DiffInfo, Side} from '../../../api/diff';
+import {Side} from '../../../api/diff';
import {Files, GrDiffView} from './gr-diff-view';
import {DropdownItem} from '../../shared/gr-dropdown-list/gr-dropdown-list';
-import {SinonFakeTimers, SinonStub, SinonSpy} from 'sinon';
+import {SinonFakeTimers, SinonStub} from 'sinon';
import {
changeModelToken,
ChangeModel,
LoadingStatus,
} from '../../../models/change/change-model';
-import {CommentMap} from '../../../utils/comment-util';
-import {ParsedChangeInfo} from '../../../types/types';
import {assertIsDefined} from '../../../utils/common-util';
import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import {fixture, html, assert} from '@open-wc/testing';
@@ -85,6 +73,11 @@
BrowserModel,
browserModelToken,
} from '../../../models/browser/browser-model';
+import {
+ ChangeViewModel,
+ changeViewModelToken,
+} from '../../../models/views/change';
+import {FileNameToNormalizedFileInfoMap} from '../../../models/change/files-model';
function createComment(
id: string,
@@ -107,25 +100,27 @@
let clock: SinonFakeTimers;
let diffCommentsStub;
let getDiffRestApiStub: SinonStub;
- let setUrlStub: SinonStub;
+ let navToChangeStub: SinonStub;
+ let navToDiffStub: SinonStub;
+ let navToEditStub: SinonStub;
let changeModel: ChangeModel;
+ let viewModel: ChangeViewModel;
let commentsModel: CommentsModel;
let browserModel: BrowserModel;
let userModel: UserModel;
function getFilesFromFileList(fileList: string[]): Files {
const changeFilesByPath = fileList.reduce((files, path) => {
- files[path] = createFileInfo();
+ files[path] = createFileInfo(path);
return files;
- }, {} as {[path: string]: FileInfo});
+ }, {} as FileNameToNormalizedFileInfoMap);
return {
- sortedFileList: fileList,
+ sortedPaths: fileList,
changeFilesByPath,
};
}
setup(async () => {
- setUrlStub = sinon.stub(testResolver(navigationToken), 'setUrl');
stubRestApi('getConfig').returns(Promise.resolve(createServerInfo()));
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getProjectConfig').returns(Promise.resolve(createConfig()));
@@ -144,14 +139,17 @@
stubRestApi('getPortedComments').returns(Promise.resolve({}));
element = await fixture(html`<gr-diff-view></gr-diff-view>`);
- element.changeNum = 42 as NumericChangeId;
+ viewModel = testResolver(changeViewModelToken);
+ viewModel.setState(createDiffViewState());
+ await waitUntil(() => element.changeNum === TEST_NUMERIC_CHANGE_ID);
element.path = 'some/path.txt';
element.change = createParsedChange();
element.diff = {...createDiff(), content: []};
getDiffRestApiStub = stubRestApi('getDiff');
// Delayed in case a test updates element.diff.
getDiffRestApiStub.callsFake(() => Promise.resolve(element.diff));
- element.patchRange = createPatchRange();
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.changeComments = new ChangeComments({
'/COMMIT_MSG': [
createComment('c1', 10, 2, '/COMMIT_MSG'),
@@ -163,6 +161,9 @@
changeModel = testResolver(changeModelToken);
browserModel = testResolver(browserModelToken);
userModel = testResolver(userModelToken);
+ navToChangeStub = sinon.stub(changeModel, 'navigateToChange');
+ navToDiffStub = sinon.stub(changeModel, 'navigateToDiff');
+ navToEditStub = sinon.stub(changeModel, 'navigateToEdit');
commentsModel.setState({
comments: {},
@@ -179,279 +180,6 @@
sinon.restore();
});
- test('viewState change triggers diffViewDisplayed()', () => {
- const diffViewDisplayedStub = stubReporting('diffViewDisplayed');
- assertIsDefined(element.diffHost);
- sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- sinon.stub(element, 'initPatchRange');
- sinon.stub(element, 'fetchFiles');
- const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- path: '/COMMIT_MSG',
- };
- element.path = '/COMMIT_MSG';
- element.patchRange = createPatchRange();
- return viewStateChangedSpy.returnValues[0]?.then(() => {
- assert.isTrue(diffViewDisplayedStub.calledOnce);
- });
- });
-
- suite('comment route', () => {
- let initLineOfInterestAndCursorStub: SinonStub;
- let replaceStateStub: SinonStub;
- let viewStateChangedSpy: SinonSpy;
- setup(() => {
- initLineOfInterestAndCursorStub = sinon.stub(
- element,
- 'initLineOfInterestAndCursor'
- );
- replaceStateStub = sinon.stub(history, 'replaceState');
- sinon.stub(element, 'fetchFiles');
- stubReporting('diffViewDisplayed');
- assertIsDefined(element.diffHost);
- sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
- changeModel.setState({
- change: {
- ...createParsedChange(),
- revisions: createRevisions(11),
- },
- loadingStatus: LoadingStatus.LOADED,
- });
- });
-
- test('comment url resolves to comment.patch_set vs latest', () => {
- commentsModel.setState({
- comments: {
- '/COMMIT_MSG': [
- createComment('c1', 10, 2, '/COMMIT_MSG'),
- createComment('c3', 10, PARENT, '/COMMIT_MSG'),
- ],
- },
- robotComments: {},
- drafts: {},
- portedComments: {},
- portedDrafts: {},
- discardedDrafts: [],
- });
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- commentLink: true,
- commentId: 'c1' as UrlEncodedCommentId,
- path: 'abcd',
- patchNum: 1 as RevisionPatchSetNum,
- };
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(11),
- };
- return viewStateChangedSpy.returnValues[0].then(() => {
- assert.isTrue(
- initLineOfInterestAndCursorStub.calledWithExactly(true)
- );
- assert.equal(element.focusLineNum, 10);
- assert.equal(element.patchRange?.patchNum, 11 as RevisionPatchSetNum);
- assert.equal(element.patchRange?.basePatchNum, 2 as BasePatchSetNum);
- assert.isTrue(replaceStateStub.called);
- });
- });
- });
-
- test('viewState change causes blame to load if it was set to true', () => {
- // Blame loads for subsequent files if it was loaded for one file
- element.isBlameLoaded = true;
- stubReporting('diffViewDisplayed');
- const loadBlameStub = sinon.stub(element, 'loadBlame');
- assertIsDefined(element.diffHost);
- sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
- sinon.stub(element, 'initPatchRange');
- sinon.stub(element, 'fetchFiles');
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- path: '/COMMIT_MSG',
- };
- element.path = '/COMMIT_MSG';
- element.patchRange = createPatchRange();
- return viewStateChangedSpy.returnValues[0]!.then(() => {
- assert.isTrue(element.isBlameLoaded);
- assert.isTrue(loadBlameStub.calledOnce);
- });
- });
-
- test('unchanged diff X vs latest from comment links navigates to base vs X', async () => {
- commentsModel.setState({
- comments: {
- '/COMMIT_MSG': [
- createComment('c1', 10, 2, '/COMMIT_MSG'),
- createComment('c3', 10, PARENT, '/COMMIT_MSG'),
- ],
- },
- robotComments: {},
- drafts: {},
- portedComments: {},
- portedDrafts: {},
- discardedDrafts: [],
- });
- stubReporting('diffViewDisplayed');
- sinon.stub(element, 'loadBlame');
- assertIsDefined(element.diffHost);
- sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- sinon.stub(element, 'isFileUnchanged').returns(true);
- const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
- changeModel.setState({
- change: {
- ...createParsedChange(),
- revisions: createRevisions(11),
- },
- loadingStatus: LoadingStatus.LOADED,
- });
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- path: '/COMMIT_MSG',
- commentLink: true,
- commentId: 'c1' as UrlEncodedCommentId,
- };
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(11),
- };
- await viewStateChangedSpy.returnValues[0];
- assert.isTrue(setUrlStub.calledOnce);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/2//COMMIT_MSG#10'
- );
- });
-
- test('unchanged diff Base vs latest from comment does not navigate', async () => {
- commentsModel.setState({
- comments: {
- '/COMMIT_MSG': [
- createComment('c1', 10, 2, '/COMMIT_MSG'),
- createComment('c3', 10, PARENT, '/COMMIT_MSG'),
- ],
- },
- robotComments: {},
- drafts: {},
- portedComments: {},
- portedDrafts: {},
- discardedDrafts: [],
- });
- stubReporting('diffViewDisplayed');
- sinon.stub(element, 'loadBlame');
- assertIsDefined(element.diffHost);
- sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- sinon.stub(element, 'isFileUnchanged').returns(true);
- const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
- changeModel.setState({
- change: {
- ...createParsedChange(),
- revisions: createRevisions(11),
- },
- loadingStatus: LoadingStatus.LOADED,
- });
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- path: '/COMMIT_MSG',
- commentLink: true,
- commentId: 'c3' as UrlEncodedCommentId,
- };
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(11),
- };
- await viewStateChangedSpy.returnValues[0];
- assert.isFalse(setUrlStub.calledOnce);
- });
-
- test('isFileUnchanged', () => {
- let diff: DiffInfo = {
- ...createDiff(),
- content: [
- {a: ['abcd'], ab: ['ef']},
- {b: ['ancd'], a: ['xx']},
- ],
- };
- assert.equal(element.isFileUnchanged(diff), false);
- diff = {
- ...createDiff(),
- content: [{ab: ['abcd']}, {ab: ['ancd']}],
- };
- assert.equal(element.isFileUnchanged(diff), true);
- diff = {
- ...createDiff(),
- content: [
- {a: ['abcd'], ab: ['ef'], common: true},
- {b: ['ancd'], ab: ['xx']},
- ],
- };
- assert.equal(element.isFileUnchanged(diff), false);
- diff = {
- ...createDiff(),
- content: [
- {a: ['abcd'], ab: ['ef'], common: true},
- {b: ['ancd'], ab: ['xx'], common: true},
- ],
- };
- assert.equal(element.isFileUnchanged(diff), true);
- });
-
- test('diff toast to go to latest is shown and not base', async () => {
- commentsModel.setState({
- comments: {
- '/COMMIT_MSG': [
- createComment('c1', 10, 2, '/COMMIT_MSG'),
- createComment('c3', 10, PARENT, '/COMMIT_MSG'),
- ],
- },
- robotComments: {},
- drafts: {},
- portedComments: {},
- portedDrafts: {},
- discardedDrafts: [],
- });
-
- stubReporting('diffViewDisplayed');
- sinon.stub(element, 'loadBlame');
- assertIsDefined(element.diffHost);
- sinon.stub(element.diffHost, 'reload').returns(Promise.resolve());
- const viewStateChangedSpy = sinon.spy(element, 'viewStateChanged');
- element.change = undefined;
- changeModel.setState({
- change: {
- ...createParsedChange(),
- revisions: createRevisions(11),
- },
- loadingStatus: LoadingStatus.LOADED,
- });
- element.patchRange = {
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- };
- sinon.stub(element, 'isFileUnchanged').returns(false);
- const toastStub = sinon.stub(element, 'displayDiffBaseAgainstLeftToast');
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- repo: 'p' as RepoName,
- commentId: 'c1' as UrlEncodedCommentId,
- commentLink: true,
- };
- await viewStateChangedSpy.returnValues[0];
- assert.isTrue(toastStub.called);
- });
-
test('toggle left diff with a hotkey', () => {
assertIsDefined(element.diffHost);
const toggleLeftDiffStub = sinon.stub(element.diffHost, 'toggleLeftDiff');
@@ -460,20 +188,17 @@
});
test('renders', async () => {
- clock = sinon.useFakeTimers();
- element.changeNum = 42 as NumericChangeId;
browserModel.setScreenWidth(0);
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 10 as RevisionPatchSetNum,
- };
- element.change = {
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
+ const change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
a: createRevision(10),
},
};
+ changeModel.updateStateChange(change);
element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
@@ -625,9 +350,8 @@
</a>
</div>
</div>
- <div class="loading">Loading...</div>
<h2 class="assistive-tech-only">Diff view</h2>
- <gr-diff-host hidden="" id="diffHost"> </gr-diff-host>
+ <gr-diff-host id="diffHost"> </gr-diff-host>
<gr-apply-fix-dialog id="applyFixDialog"> </gr-apply-fix-dialog>
<gr-diff-preferences-dialog id="diffPreferencesDialog">
</gr-diff-preferences-dialog>
@@ -643,10 +367,8 @@
clock = sinon.useFakeTimers();
element.changeNum = 42 as NumericChangeId;
browserModel.setScreenWidth(0);
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 10 as RevisionPatchSetNum,
- };
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -662,51 +384,42 @@
element.path = 'glados.txt';
element.loggedIn = true;
await element.updateComplete;
- setUrlStub.reset();
+ navToChangeStub.reset();
pressKey(element, 'u');
- assert.equal(setUrlStub.callCount, 1);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42');
+ assert.isTrue(navToChangeStub.calledOnce);
await element.updateComplete;
pressKey(element, ']');
- assert.equal(setUrlStub.callCount, 2);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/10/wheatley.md'
- );
+ assert.equal(navToDiffStub.callCount, 1);
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'wheatley.md', lineNum: undefined},
+ ]);
+
element.path = 'wheatley.md';
await element.updateComplete;
- assert.isTrue(element.loading);
-
pressKey(element, '[');
- assert.equal(setUrlStub.callCount, 3);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/10/glados.txt'
- );
+ assert.equal(navToDiffStub.callCount, 2);
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'glados.txt', lineNum: undefined},
+ ]);
+
element.path = 'glados.txt';
await element.updateComplete;
- assert.isTrue(element.loading);
-
pressKey(element, '[');
- assert.equal(setUrlStub.callCount, 4);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/10/chell.go'
- );
+ assert.equal(navToDiffStub.callCount, 3);
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'chell.go', lineNum: undefined},
+ ]);
+
element.path = 'chell.go';
await element.updateComplete;
- assert.isTrue(element.loading);
-
pressKey(element, '[');
- assert.equal(setUrlStub.callCount, 5);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42');
+ assert.equal(navToChangeStub.callCount, 2);
await element.updateComplete;
- assert.isTrue(element.loading);
assertIsDefined(element.diffPreferencesDialog);
const showPrefsStub = sinon
@@ -756,12 +469,8 @@
);
assert.isFalse(element.diffHost.diffElement.displayLine);
- // Note that stubbing setReviewed means that the value of the
- // `element.reviewed` checkbox is not flipped.
const setReviewedStub = sinon.stub(element, 'setReviewed');
const handleToggleSpy = sinon.spy(element, 'handleToggleFileReviewed');
- assertIsDefined(element.reviewed);
- element.reviewed.checked = false;
assert.isFalse(handleToggleSpy.called);
assert.isFalse(setReviewedStub.called);
@@ -791,10 +500,8 @@
'wheatley.md': [createComment('c2', 21, 10, 'wheatley.md')],
};
element.changeComments = new ChangeComments(comment);
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 10 as RevisionPatchSetNum,
- };
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -810,23 +517,21 @@
element.path = 'glados.txt';
element.loggedIn = true;
await element.updateComplete;
- setUrlStub.reset();
+ navToDiffStub.reset();
pressKey(element, 'N');
await element.updateComplete;
- assert.equal(setUrlStub.callCount, 1);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/10/wheatley.md#21'
- );
+ assert.equal(navToDiffStub.callCount, 1);
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'wheatley.md', lineNum: 21},
+ ]);
element.path = 'wheatley.md'; // navigated to next file
pressKey(element, 'N');
await element.updateComplete;
- assert.equal(setUrlStub.callCount, 2);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42');
+ assert.equal(navToChangeStub.callCount, 1);
});
test('shift+x shortcut toggles all diff context', async () => {
@@ -838,114 +543,61 @@
});
test('diff against base', async () => {
- element.patchRange = {
- basePatchNum: 5 as BasePatchSetNum,
- patchNum: 10 as RevisionPatchSetNum,
- };
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = 5 as BasePatchSetNum;
await element.updateComplete;
element.handleDiffAgainstBase();
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/10/some/path.txt'
- );
+ const expected = [{path: 'some/path.txt'}, 10, PARENT];
+ assert.deepEqual(navToDiffStub.lastCall.args, expected);
});
test('diff against latest', async () => {
element.path = 'foo';
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(12),
- };
- element.patchRange = {
- basePatchNum: 5 as BasePatchSetNum,
- patchNum: 10 as RevisionPatchSetNum,
- };
+ element.latestPatchNum = 12 as PatchSetNumber;
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = 5 as BasePatchSetNum;
await element.updateComplete;
element.handleDiffAgainstLatest();
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/5..12/foo'
- );
+ const expected = [{path: 'foo'}, 12, 5];
+ assert.deepEqual(navToDiffStub.lastCall.args, expected);
});
test('handleDiffBaseAgainstLeft', async () => {
element.path = 'foo';
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(10),
- };
- element.patchRange = {
+ element.latestPatchNum = 10 as PatchSetNumber;
+ element.patchNum = 3 as RevisionPatchSetNum;
+ element.basePatchNum = 1 as BasePatchSetNum;
+ viewModel.setState({
+ ...createDiffViewState(),
patchNum: 3 as RevisionPatchSetNum,
basePatchNum: 1 as BasePatchSetNum,
- };
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- patchNum: 3 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- path: 'foo',
- };
+ diffView: {path: 'foo'},
+ });
await element.updateComplete;
element.handleDiffBaseAgainstLeft();
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/1/foo');
- });
-
- test('handleDiffBaseAgainstLeft when initially navigating to a comment', () => {
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(10),
- };
- element.patchRange = {
- patchNum: 3 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- };
- sinon.stub(element, 'viewStateChanged');
- element.viewState = {
- commentLink: true,
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- };
- element.focusLineNum = 10;
- element.handleDiffBaseAgainstLeft();
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/1/some/path.txt#10'
- );
+ const expected = [{path: 'foo'}, 1, PARENT];
+ assert.deepEqual(navToDiffStub.lastCall.args, expected);
});
test('handleDiffRightAgainstLatest', async () => {
element.path = 'foo';
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(10),
- };
- element.patchRange = {
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: 3 as RevisionPatchSetNum,
- };
+ element.latestPatchNum = 10 as PatchSetNumber;
+ element.patchNum = 3 as RevisionPatchSetNum;
+ element.basePatchNum = 1 as BasePatchSetNum;
await element.updateComplete;
element.handleDiffRightAgainstLatest();
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/3..10/foo'
- );
+ const expected = [{path: 'foo'}, 10, 3];
+ assert.deepEqual(navToDiffStub.lastCall.args, expected);
});
test('handleDiffBaseAgainstLatest', async () => {
- element.change = {
- ...createParsedChange(),
- revisions: createRevisions(10),
- };
- element.patchRange = {
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: 3 as RevisionPatchSetNum,
- };
+ element.latestPatchNum = 10 as PatchSetNumber;
+ element.patchNum = 3 as RevisionPatchSetNum;
+ element.basePatchNum = 1 as BasePatchSetNum;
await element.updateComplete;
element.handleDiffBaseAgainstLatest();
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/10/some/path.txt'
- );
+ const expected = [{path: 'some/path.txt'}, 10, PARENT];
+ assert.deepEqual(navToDiffStub.lastCall.args, expected);
});
test('A fires an error event when not logged in', async () => {
@@ -954,16 +606,14 @@
element.addEventListener('show-auth-required', loggedInErrorSpy);
pressKey(element, 'a');
await element.updateComplete;
- assert.isFalse(setUrlStub.calledOnce);
+ assert.isFalse(navToDiffStub.calledOnce);
assert.isTrue(loggedInErrorSpy.called);
});
test('A navigates to change with logged in', async () => {
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: 5 as BasePatchSetNum,
- patchNum: 10 as RevisionPatchSetNum,
- };
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = 5 as BasePatchSetNum;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -976,25 +626,20 @@
await element.updateComplete;
const loggedInErrorSpy = sinon.spy();
element.addEventListener('show-auth-required', loggedInErrorSpy);
- setUrlStub.reset();
+ navToDiffStub.reset();
pressKey(element, 'a');
await element.updateComplete;
- assert.equal(setUrlStub.callCount, 1);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/5..10?openReplyDialog=true'
- );
+ assert.isTrue(navToChangeStub.calledOnce);
+ assert.deepEqual(navToChangeStub.lastCall.args, [true]);
assert.isFalse(loggedInErrorSpy.called);
});
test('A navigates to change with old patch number with logged in', async () => {
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -1008,20 +653,15 @@
element.addEventListener('show-auth-required', loggedInErrorSpy);
pressKey(element, 'a');
await element.updateComplete;
- assert.isTrue(setUrlStub.calledOnce);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/1?openReplyDialog=true'
- );
+ assert.isTrue(navToChangeStub.calledOnce);
+ assert.deepEqual(navToChangeStub.lastCall.args, [true]);
assert.isFalse(loggedInErrorSpy.called);
});
test('keyboard shortcuts with patch range', () => {
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: 5 as BasePatchSetNum,
- patchNum: 10 as RevisionPatchSetNum,
- };
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = 5 as BasePatchSetNum;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -1038,40 +678,31 @@
element.path = 'glados.txt';
pressKey(element, 'u');
- assert.equal(setUrlStub.callCount, 1);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/5..10');
+ assert.equal(navToChangeStub.callCount, 1);
pressKey(element, ']');
- assert.isTrue(element.loading);
- assert.equal(setUrlStub.callCount, 2);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/5..10/wheatley.md'
- );
+ assert.equal(navToDiffStub.callCount, 1);
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'wheatley.md', lineNum: undefined},
+ ]);
element.path = 'wheatley.md';
pressKey(element, '[');
- assert.isTrue(element.loading);
- assert.equal(setUrlStub.callCount, 3);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/5..10/glados.txt'
- );
+ assert.equal(navToDiffStub.callCount, 2);
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'glados.txt', lineNum: undefined},
+ ]);
element.path = 'glados.txt';
pressKey(element, '[');
- assert.isTrue(element.loading);
- assert.equal(setUrlStub.callCount, 4);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/5..10/chell.go'
- );
+ assert.equal(navToDiffStub.callCount, 3);
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'chell.go', lineNum: undefined},
+ ]);
element.path = 'chell.go';
pressKey(element, '[');
- assert.isTrue(element.loading);
- assert.equal(setUrlStub.callCount, 5);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/5..10');
+ assert.equal(navToChangeStub.callCount, 2);
assertIsDefined(element.downloadModal);
const downloadModalStub = sinon.stub(element.downloadModal, 'showModal');
@@ -1079,12 +710,10 @@
assert.isTrue(downloadModalStub.called);
});
- test('keyboard shortcuts with old patch number', () => {
+ test('keyboard shortcuts with old patch number', async () => {
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -1101,53 +730,57 @@
element.path = 'glados.txt';
pressKey(element, 'u');
- assert.isTrue(setUrlStub.calledOnce);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/1');
+ assert.isTrue(navToChangeStub.calledOnce);
pressKey(element, ']');
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/1/wheatley.md'
- );
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'wheatley.md', lineNum: undefined},
+ ]);
element.path = 'wheatley.md';
pressKey(element, '[');
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/1/glados.txt'
- );
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'glados.txt', lineNum: undefined},
+ ]);
element.path = 'glados.txt';
pressKey(element, '[');
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/test-project/+/42/1/chell.go'
- );
- element.path = 'chell.go';
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: 'chell.go', lineNum: undefined},
+ ]);
- setUrlStub.reset();
+ element.path = 'chell.go';
+ await element.updateComplete;
+ navToDiffStub.reset();
pressKey(element, '[');
- assert.isTrue(setUrlStub.calledOnce);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/test-project/+/42/1');
+ assert.equal(navToChangeStub.callCount, 2);
+ });
+
+ test('reloadDiff is called when patchNum changes', async () => {
+ const reloadStub = sinon.stub(element, 'reloadDiff');
+ element.patchNum = 5 as RevisionPatchSetNum;
+ await element.updateComplete;
+ assert.isTrue(reloadStub.called);
+ });
+
+ test('initializePositions is called when view becomes active', async () => {
+ const reloadStub = sinon.stub(element, 'reloadDiff');
+ const initializeStub = sinon.stub(element, 'initializePositions');
+
+ element.isActiveChildView = false;
+ await element.updateComplete;
+ element.isActiveChildView = true;
+ await element.updateComplete;
+
+ assert.isTrue(initializeStub.calledOnce);
+ assert.isFalse(reloadStub.called);
});
test('edit should redirect to edit page', async () => {
element.loggedIn = true;
element.path = 't.txt';
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
- element.change = {
- ...createParsedChange(),
- _number: 42 as NumericChangeId,
- project: 'gerrit' as RepoName,
- status: ChangeStatus.NEW,
- revisions: {
- a: createRevision(1),
- b: createRevision(2),
- },
- };
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
await element.updateComplete;
const editBtn = queryAndAssert<GrButton>(
element,
@@ -1155,28 +788,18 @@
);
assert.isTrue(!!editBtn);
editBtn.click();
- assert.equal(setUrlStub.callCount, 1);
- assert.equal(setUrlStub.lastCall.firstArg, '/c/gerrit/+/42/1/t.txt,edit');
+ assert.equal(navToEditStub.callCount, 1);
+ assert.deepEqual(navToEditStub.lastCall.args, [
+ {path: 't.txt', lineNum: undefined},
+ ]);
});
test('edit should redirect to edit page with line number', async () => {
const lineNumber = 42;
element.loggedIn = true;
element.path = 't.txt';
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
- element.change = {
- ...createParsedChange(),
- _number: 42 as NumericChangeId,
- project: 'gerrit' as RepoName,
- status: ChangeStatus.NEW,
- revisions: {
- a: createRevision(1),
- b: createRevision(2),
- },
- };
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
assertIsDefined(element.cursor);
sinon
.stub(element.cursor, 'getAddress')
@@ -1188,11 +811,10 @@
);
assert.isTrue(!!editBtn);
editBtn.click();
- assert.equal(setUrlStub.callCount, 1);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/gerrit/+/42/1/t.txt,edit#42'
- );
+ assert.equal(navToEditStub.callCount, 1);
+ assert.deepEqual(navToEditStub.lastCall.args, [
+ {path: 't.txt', lineNum: 42},
+ ]);
});
async function isEditVisibile({
@@ -1204,10 +826,8 @@
}): Promise<boolean> {
element.loggedIn = loggedIn;
element.path = 't.txt';
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- };
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -1304,16 +924,10 @@
});
suite('url parameters', () => {
- setup(() => {
- sinon.stub(element, 'fetchFiles');
- });
-
test('_formattedFiles', () => {
element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 10 as RevisionPatchSetNum,
- };
+ element.patchNum = 10 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
@@ -1386,18 +1000,19 @@
});
test('prev/up/next links', async () => {
- element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 10 as RevisionPatchSetNum,
- };
- element.change = {
+ viewModel.setState({
+ ...createDiffViewState(),
+ });
+ const change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
a: createRevision(10),
},
};
+ changeModel.updateStateChange(change);
+ await element.updateComplete;
+
element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
@@ -1417,24 +1032,30 @@
linkEls[2].getAttribute('href'),
'/c/test-project/+/42/10/wheatley.md'
);
+
element.path = 'wheatley.md';
await element.updateComplete;
+
assert.equal(
linkEls[0].getAttribute('href'),
'/c/test-project/+/42/10/glados.txt'
);
assert.equal(linkEls[1].getAttribute('href'), '/c/test-project/+/42');
assert.equal(linkEls[2].getAttribute('href'), '/c/test-project/+/42');
+
element.path = 'chell.go';
await element.updateComplete;
+
assert.equal(linkEls[0].getAttribute('href'), '/c/test-project/+/42');
assert.equal(linkEls[1].getAttribute('href'), '/c/test-project/+/42');
assert.equal(
linkEls[2].getAttribute('href'),
'/c/test-project/+/42/10/glados.txt'
);
+
element.path = 'not_a_real_file';
await element.updateComplete;
+
assert.equal(
linkEls[0].getAttribute('href'),
'/c/test-project/+/42/10/wheatley.md'
@@ -1447,26 +1068,30 @@
});
test('prev/up/next links with patch range', async () => {
- element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
+ viewModel.setState({
+ ...createDiffViewState(),
basePatchNum: 5 as BasePatchSetNum,
patchNum: 10 as RevisionPatchSetNum,
- };
- element.change = {
+ diffView: {path: 'glados.txt'},
+ });
+ const change = {
...createParsedChange(),
_number: 42 as NumericChangeId,
revisions: {
a: createRevision(5),
b: createRevision(10),
+ c: createRevision(12),
},
};
+ changeModel.updateStateChange(change);
element.files = getFilesFromFileList([
'chell.go',
'glados.txt',
'wheatley.md',
]);
- element.path = 'glados.txt';
- await element.updateComplete;
+ await waitUntil(() => element.path === 'glados.txt');
+ await waitUntil(() => element.patchRange?.patchNum === 10);
+
const linkEls = queryAll(element, '.navLink');
assert.equal(linkEls.length, 3);
assert.equal(
@@ -1481,8 +1106,10 @@
linkEls[2].getAttribute('href'),
'/c/test-project/+/42/5..10/wheatley.md'
);
- element.path = 'wheatley.md';
- await element.updateComplete;
+
+ viewModel.updateState({diffView: {path: 'wheatley.md'}});
+ await waitUntil(() => element.path === 'wheatley.md');
+
assert.equal(
linkEls[0].getAttribute('href'),
'/c/test-project/+/42/5..10/glados.txt'
@@ -1495,8 +1122,10 @@
linkEls[2].getAttribute('href'),
'/c/test-project/+/42/5..10'
);
- element.path = 'chell.go';
- await element.updateComplete;
+
+ viewModel.updateState({diffView: {path: 'chell.go'}});
+ await waitUntil(() => element.path === 'chell.go');
+
assert.equal(
linkEls[0].getAttribute('href'),
'/c/test-project/+/42/5..10'
@@ -1513,32 +1142,24 @@
});
test('handlePatchChange calls setUrl correctly', async () => {
- element.change = {
- ...createParsedChange(),
- _number: 321 as NumericChangeId,
- project: 'foo/bar' as RepoName,
- };
element.path = 'path/to/file.txt';
-
- element.patchRange = {
- basePatchNum: PARENT,
- patchNum: 3 as RevisionPatchSetNum,
- };
+ element.patchNum = 3 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
await element.updateComplete;
const detail = {
basePatchNum: PARENT,
patchNum: 1 as RevisionPatchSetNum,
};
-
queryAndAssert(element, '#rangeSelect').dispatchEvent(
new CustomEvent('patch-range-change', {detail, bubbles: false})
);
- assert.equal(
- setUrlStub.lastCall.firstArg,
- '/c/foo/bar/+/321/1/path/to/file.txt'
- );
+ assert.deepEqual(navToDiffStub.lastCall.args, [
+ {path: element.path},
+ detail.patchNum,
+ detail.basePatchNum,
+ ]);
});
test(
@@ -1559,23 +1180,13 @@
manual_review: true,
};
userModel.setDiffPreferences(diffPreferences);
+ viewModel.updateState({diffView: {path: 'wheatley.md'}});
changeModel.setState({
change: createParsedChange(),
- diffPath: '/COMMIT_MSG',
reviewedFiles: [],
loadingStatus: LoadingStatus.LOADED,
});
- testResolver(routerModelToken).setState({
- changeNum: TEST_NUMERIC_CHANGE_ID,
- view: GerritView.DIFF,
- patchNum: 2 as RevisionPatchSetNum,
- });
- element.patchRange = {
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- };
-
await waitUntil(() => setReviewedStatusStub.called);
assert.isFalse(setReviewedFileStatusStub.called);
@@ -1601,55 +1212,39 @@
manual_review: false,
};
userModel.setDiffPreferences(diffPreferences);
+ viewModel.updateState({diffView: {path: 'wheatley.md'}});
changeModel.setState({
change: createParsedChange(),
- diffPath: '/COMMIT_MSG',
reviewedFiles: [],
loadingStatus: LoadingStatus.LOADED,
});
- testResolver(routerModelToken).setState({
- changeNum: TEST_NUMERIC_CHANGE_ID,
- view: GerritView.DIFF,
- patchNum: 22 as RevisionPatchSetNum,
- });
- element.patchRange = {
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- };
-
await waitUntil(() => setReviewedFileStatusStub.called);
assert.isTrue(setReviewedFileStatusStub.called);
});
test('file review status', async () => {
+ const saveReviewedStub = sinon
+ .stub(changeModel, 'setReviewedFilesStatus')
+ .callsFake(() => Promise.resolve());
+ userModel.setDiffPreferences(createDefaultDiffPrefs());
+ viewModel.updateState({
+ patchNum: 1 as RevisionPatchSetNum,
+ basePatchNum: PARENT,
+ diffView: {path: '/COMMIT_MSG'},
+ });
changeModel.setState({
change: createParsedChange(),
- diffPath: '/COMMIT_MSG',
reviewedFiles: [],
loadingStatus: LoadingStatus.LOADED,
});
element.loggedIn = true;
- const saveReviewedStub = sinon
- .stub(changeModel, 'setReviewedFilesStatus')
- .callsFake(() => Promise.resolve());
+ await waitUntil(() => element.patchRange?.patchNum === 1);
+ await element.updateComplete;
assertIsDefined(element.diffHost);
sinon.stub(element.diffHost, 'reload');
- userModel.setDiffPreferences(createDefaultDiffPrefs());
-
- testResolver(routerModelToken).setState({
- changeNum: TEST_NUMERIC_CHANGE_ID,
- view: GerritView.DIFF,
- patchNum: 2 as RevisionPatchSetNum,
- });
-
- element.patchRange = {
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- };
-
await waitUntil(() => saveReviewedStub.called);
changeModel.updateStateFileReviewed('/COMMIT_MSG', true);
@@ -1657,13 +1252,13 @@
const reviewedStatusCheckBox = queryAndAssert<HTMLInputElement>(
element,
- 'input[type="checkbox"]'
+ 'input#reviewed'
);
assert.isTrue(reviewedStatusCheckBox.checked);
assert.deepEqual(saveReviewedStub.lastCall.args, [
42,
- 2,
+ 1,
'/COMMIT_MSG',
true,
]);
@@ -1672,7 +1267,7 @@
assert.isFalse(reviewedStatusCheckBox.checked);
assert.deepEqual(saveReviewedStub.lastCall.args, [
42,
- 2,
+ 1,
'/COMMIT_MSG',
false,
]);
@@ -1684,18 +1279,17 @@
assert.isTrue(reviewedStatusCheckBox.checked);
assert.deepEqual(saveReviewedStub.lastCall.args, [
42,
- 2,
+ 1,
'/COMMIT_MSG',
true,
]);
const callCount = saveReviewedStub.callCount;
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
+ viewModel.setState({
+ ...createDiffViewState(),
repo: 'test' as RepoName,
- };
+ });
await element.updateComplete;
// saveReviewedState observer observes viewState, but should not fire when
@@ -1703,20 +1297,18 @@
assert.equal(saveReviewedStub.callCount, callCount);
});
- test('file review status with edit loaded', async () => {
+ test('do not set file review status for EDIT patchset', async () => {
const saveReviewedStub = sinon.stub(
changeModel,
'setReviewedFilesStatus'
);
- element.patchRange = {
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: EDIT,
- };
+ element.patchNum = EDIT;
+ element.basePatchNum = 1 as BasePatchSetNum;
await waitEventLoop();
- assert.isTrue(element.computeEditMode());
element.setReviewed(true);
+
assert.isFalse(saveReviewedStub.called);
});
@@ -1725,14 +1317,7 @@
sinon.stub(element.diffHost, 'reload');
const initLineStub = sinon.stub(element, 'initLineOfInterestAndCursor');
- element.loggedIn = true;
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- path: '/COMMIT_MSG',
- };
+ element.focusLineNum = 123;
await element.updateComplete;
await waitEventLoop();
@@ -1780,115 +1365,56 @@
assert.isTrue(diffModeSelector.classList.contains('hide'));
});
- suite('commitRange', () => {
- const change: ParsedChangeInfo = {
- ...createParsedChange(),
- _number: 42 as NumericChangeId,
- revisions: {
- 'commit-sha-1': {
- ...createRevision(1),
- commit: {
- ...createCommit(),
- parents: [{subject: 's1', commit: 'sha-1-parent' as CommitId}],
- },
- },
- 'commit-sha-2': createRevision(2),
- 'commit-sha-3': createRevision(3),
- 'commit-sha-4': createRevision(4),
- 'commit-sha-5': {
- ...createRevision(5),
- commit: {
- ...createCommit(),
- parents: [{subject: 's5', commit: 'sha-5-parent' as CommitId}],
- },
- },
- },
- };
- setup(async () => {
- assertIsDefined(element.diffHost);
- sinon.stub(element.diffHost, 'reload');
- sinon.stub(element, 'initCursor');
- element.change = change;
- await element.updateComplete;
- await element.diffHost.updateComplete;
- });
-
- test('uses the patchNum and basePatchNum ', async () => {
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- patchNum: 4 as RevisionPatchSetNum,
- basePatchNum: 2 as BasePatchSetNum,
- path: '/COMMIT_MSG',
- };
- element.change = change;
- await element.updateComplete;
- await waitEventLoop();
- assert.deepEqual(element.commitRange, {
- baseCommit: 'commit-sha-2' as CommitId,
- commit: 'commit-sha-4' as CommitId,
- });
- });
-
- test('uses the parent when there is no base patch num ', async () => {
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- patchNum: 5 as RevisionPatchSetNum,
- path: '/COMMIT_MSG',
- };
- element.change = change;
- await element.updateComplete;
- await waitEventLoop();
- assert.deepEqual(element.commitRange, {
- commit: 'commit-sha-5' as CommitId,
- baseCommit: 'sha-5-parent' as CommitId,
- });
- });
- });
-
test('initCursor', () => {
assertIsDefined(element.cursor);
assert.isNotOk(element.cursor.initialLineNumber);
// Does nothing when viewState specify no cursor address:
- element.initCursor(false);
+ element.leftSide = false;
+ element.initCursor();
assert.isNotOk(element.cursor.initialLineNumber);
// Does nothing when viewState specify side but no number:
- element.initCursor(true);
+ element.leftSide = true;
+ element.initCursor();
assert.isNotOk(element.cursor.initialLineNumber);
// Revision hash: specifies lineNum but not side.
element.focusLineNum = 234;
- element.initCursor(false);
+ element.leftSide = false;
+ element.initCursor();
assert.equal(element.cursor.initialLineNumber, 234);
assert.equal(element.cursor.side, Side.RIGHT);
// Base hash: specifies lineNum and side.
element.focusLineNum = 345;
- element.initCursor(true);
+ element.leftSide = true;
+ element.initCursor();
assert.equal(element.cursor.initialLineNumber, 345);
assert.equal(element.cursor.side, Side.LEFT);
// Specifies right side:
element.focusLineNum = 123;
- element.initCursor(false);
+ element.leftSide = false;
+ element.initCursor();
assert.equal(element.cursor.initialLineNumber, 123);
assert.equal(element.cursor.side, Side.RIGHT);
});
test('getLineOfInterest', () => {
- assert.isUndefined(element.getLineOfInterest(false));
+ element.leftSide = false;
+ assert.isUndefined(element.getLineOfInterest());
element.focusLineNum = 12;
- let result = element.getLineOfInterest(false);
+ element.leftSide = false;
+ let result = element.getLineOfInterest();
assert.isOk(result);
assert.equal(result!.lineNum, 12);
assert.equal(result!.side, Side.RIGHT);
- result = element.getLineOfInterest(true);
+ element.leftSide = true;
+ result = element.getLineOfInterest();
assert.isOk(result);
assert.equal(result!.lineNum, 12);
assert.equal(result!.side, Side.LEFT);
@@ -1907,10 +1433,8 @@
_number: 321 as NumericChangeId,
project: 'foo/bar' as RepoName,
};
- element.patchRange = {
- basePatchNum: 3 as BasePatchSetNum,
- patchNum: 5 as RevisionPatchSetNum,
- };
+ element.patchNum = 5 as RevisionPatchSetNum;
+ element.basePatchNum = 3 as BasePatchSetNum;
const e = {detail: {number: 123, side: Side.RIGHT}} as CustomEvent;
element.onLineSelected(e);
@@ -1931,10 +1455,8 @@
_number: 321 as NumericChangeId,
project: 'foo/bar' as RepoName,
};
- element.patchRange = {
- basePatchNum: 3 as BasePatchSetNum,
- patchNum: 5 as RevisionPatchSetNum,
- };
+ element.patchNum = 5 as RevisionPatchSetNum;
+ element.basePatchNum = 3 as BasePatchSetNum;
const e = {detail: {number: 123, side: Side.LEFT}} as CustomEvent;
element.onLineSelected(e);
@@ -1965,199 +1487,116 @@
});
});
- suite('initPatchRange', () => {
- setup(async () => {
- getDiffRestApiStub.returns(Promise.resolve(createDiff()));
- element.viewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- patchNum: 3 as RevisionPatchSetNum,
- path: 'abcd',
- };
- await element.updateComplete;
- });
- test('empty', () => {
- sinon.stub(element, 'getPaths').returns({});
- element.initPatchRange();
- assert.equal(Object.keys(element.commentMap ?? {}).length, 0);
- });
-
- test('has paths', () => {
- sinon.stub(element, 'fetchFiles');
- sinon.stub(element, 'getPaths').returns({
- 'path/to/file/one.cpp': true,
- 'path-to/file/two.py': true,
- });
- element.changeNum = 42 as NumericChangeId;
- element.patchRange = {
- basePatchNum: 3 as BasePatchSetNum,
- patchNum: 5 as RevisionPatchSetNum,
- };
- element.initPatchRange();
- assert.deepEqual(Object.keys(element.commentMap ?? {}), [
- 'path/to/file/one.cpp',
- 'path-to/file/two.py',
- ]);
- });
- });
-
- suite('computeCommentSkips', () => {
+ suite('findFileWithComment', () => {
test('empty file list', () => {
- const commentMap = {
- 'path/one.jpg': true,
- 'path/three.wav': true,
- };
- const path = 'path/two.m4v';
- const result = element.computeCommentSkips(commentMap, [], path);
- assert.isOk(result);
- assert.isNotOk(result!.previous);
- assert.isNotOk(result!.next);
+ element.changeComments = new ChangeComments({
+ 'path/one.jpg': [createComment('c1', 1, 1, 'path/one.jpg')],
+ 'path/three.wav': [createComment('c1', 1, 1, 'path/three.wav')],
+ });
+ element.path = 'path/two.m4v';
+ assert.isUndefined(element.findFileWithComment(-1));
+ assert.isUndefined(element.findFileWithComment(1));
});
test('finds skips', () => {
const fileList = ['path/one.jpg', 'path/two.m4v', 'path/three.wav'];
- let path = fileList[1];
- const commentMap: CommentMap = {};
- commentMap[fileList[0]] = true;
- commentMap[fileList[1]] = false;
- commentMap[fileList[2]] = true;
+ element.files = {sortedPaths: fileList, changeFilesByPath: {}};
+ element.path = fileList[1];
+ element.changeComments = new ChangeComments({
+ 'path/one.jpg': [createComment('c1', 1, 1, 'path/one.jpg')],
+ 'path/three.wav': [createComment('c1', 1, 1, 'path/three.wav')],
+ });
- let result = element.computeCommentSkips(commentMap, fileList, path);
- assert.isOk(result);
- assert.equal(result!.previous, fileList[0]);
- assert.equal(result!.next, fileList[2]);
+ assert.equal(element.findFileWithComment(-1), fileList[0]);
+ assert.equal(element.findFileWithComment(1), fileList[2]);
- commentMap[fileList[1]] = true;
+ element.changeComments = new ChangeComments({
+ 'path/one.jpg': [createComment('c1', 1, 1, 'path/one.jpg')],
+ 'path/two.m4v': [createComment('c1', 1, 1, 'path/two.m4v')],
+ 'path/three.wav': [createComment('c1', 1, 1, 'path/three.wav')],
+ });
- result = element.computeCommentSkips(commentMap, fileList, path);
- assert.isOk(result);
- assert.equal(result!.previous, fileList[0]);
- assert.equal(result!.next, fileList[2]);
+ assert.equal(element.findFileWithComment(-1), fileList[0]);
+ assert.equal(element.findFileWithComment(1), fileList[2]);
- path = fileList[0];
+ element.path = fileList[0];
- result = element.computeCommentSkips(commentMap, fileList, path);
- assert.isOk(result);
- assert.isNull(result!.previous);
- assert.equal(result!.next, fileList[1]);
+ assert.isUndefined(element.findFileWithComment(-1));
+ assert.equal(element.findFileWithComment(1), fileList[1]);
- path = fileList[2];
+ element.path = fileList[2];
- result = element.computeCommentSkips(commentMap, fileList, path);
- assert.isOk(result);
- assert.equal(result!.previous, fileList[1]);
- assert.isNull(result!.next);
+ assert.equal(element.findFileWithComment(-1), fileList[1]);
+ assert.isUndefined(element.findFileWithComment(1));
});
suite('skip next/previous', () => {
- let navToChangeStub: SinonStub;
-
setup(() => {
- navToChangeStub = sinon.stub(element, 'navToChangeView');
element.files = getFilesFromFileList([
'path/one.jpg',
'path/two.m4v',
'path/three.wav',
]);
- element.patchRange = {
- patchNum: 2 as RevisionPatchSetNum,
- basePatchNum: 1 as BasePatchSetNum,
- };
+ element.patchNum = 2 as RevisionPatchSetNum;
+ element.basePatchNum = 1 as BasePatchSetNum;
});
- suite('moveToPreviousFileWithComment', () => {
- test('no skips', () => {
- element.moveToPreviousFileWithComment();
- assert.isFalse(navToChangeStub.called);
- assert.isFalse(setUrlStub.called);
- });
-
+ suite('moveToFileWithComment previous', () => {
test('no previous', async () => {
- const commentMap: CommentMap = {};
- commentMap[element.files.sortedFileList[0]!] = false;
- commentMap[element.files.sortedFileList[1]!] = false;
- commentMap[element.files.sortedFileList[2]!] = true;
- element.commentMap = commentMap;
- element.path = element.files.sortedFileList[1];
+ element.changeComments = new ChangeComments({
+ 'path/three.wav': [createComment('c1', 1, 1, 'path/three.wav')],
+ });
+ element.path = element.files.sortedPaths[1];
await element.updateComplete;
- element.moveToPreviousFileWithComment();
+ element.moveToFileWithComment(-1);
assert.isTrue(navToChangeStub.calledOnce);
- assert.isFalse(setUrlStub.called);
+ assert.isFalse(navToDiffStub.called);
});
test('w/ previous', async () => {
- const commentMap: CommentMap = {};
- commentMap[element.files.sortedFileList[0]!] = true;
- commentMap[element.files.sortedFileList[1]!] = false;
- commentMap[element.files.sortedFileList[2]!] = true;
- element.commentMap = commentMap;
- element.path = element.files.sortedFileList[1];
+ element.changeComments = new ChangeComments({
+ 'path/one.jpg': [createComment('c1', 1, 1, 'path/one.jpg')],
+ 'path/three.wav': [createComment('c1', 1, 1, 'path/three.wav')],
+ });
+ element.path = element.files.sortedPaths[1];
await element.updateComplete;
- element.moveToPreviousFileWithComment();
+ element.moveToFileWithComment(-1);
assert.isFalse(navToChangeStub.called);
- assert.isTrue(setUrlStub.calledOnce);
+ assert.isTrue(navToDiffStub.calledOnce);
});
});
- suite('moveToNextFileWithComment', () => {
- test('no skips', () => {
- element.moveToNextFileWithComment();
- assert.isFalse(navToChangeStub.called);
- assert.isFalse(setUrlStub.called);
- });
-
+ suite('moveToFileWithComment next', () => {
test('no previous', async () => {
- const commentMap: CommentMap = {};
- commentMap[element.files.sortedFileList[0]!] = true;
- commentMap[element.files.sortedFileList[1]!] = false;
- commentMap[element.files.sortedFileList[2]!] = false;
- element.commentMap = commentMap;
- element.path = element.files.sortedFileList[1];
+ element.changeComments = new ChangeComments({
+ 'path/one.jpg': [createComment('c1', 1, 1, 'path/one.jpg')],
+ });
+ element.path = element.files.sortedPaths[1];
await element.updateComplete;
- element.moveToNextFileWithComment();
+ element.moveToFileWithComment(1);
assert.isTrue(navToChangeStub.calledOnce);
- assert.isFalse(setUrlStub.called);
+ assert.isFalse(navToDiffStub.called);
});
test('w/ previous', async () => {
- const commentMap: CommentMap = {};
- commentMap[element.files.sortedFileList[0]!] = true;
- commentMap[element.files.sortedFileList[1]!] = false;
- commentMap[element.files.sortedFileList[2]!] = true;
- element.commentMap = commentMap;
- element.path = element.files.sortedFileList[1];
+ element.changeComments = new ChangeComments({
+ 'path/one.jpg': [createComment('c1', 1, 1, 'path/one.jpg')],
+ 'path/three.wav': [createComment('c1', 1, 1, 'path/three.wav')],
+ });
+ element.path = element.files.sortedPaths[1];
await element.updateComplete;
- element.moveToNextFileWithComment();
+ element.moveToFileWithComment(1);
assert.isFalse(navToChangeStub.called);
- assert.isTrue(setUrlStub.calledOnce);
+ assert.isTrue(navToDiffStub.calledOnce);
});
});
});
});
- test('_computeEditMode', () => {
- const callCompute = (range: PatchRange) => {
- element.patchRange = range;
- return element.computeEditMode();
- };
- assert.isFalse(
- callCompute({
- basePatchNum: PARENT,
- patchNum: 1 as RevisionPatchSetNum,
- })
- );
- assert.isTrue(
- callCompute({
- basePatchNum: 1 as BasePatchSetNum,
- patchNum: EDIT,
- })
- );
- });
-
test('computeFileNum', () => {
element.path = '/foo';
assert.equal(
@@ -2224,15 +1663,18 @@
test('reviewed checkbox', async () => {
sinon.stub(element, 'handlePatchChange');
- element.patchRange = createPatchRange();
- await element.updateComplete;
- assertIsDefined(element.reviewed);
- // Reviewed checkbox should be shown.
- assert.isTrue(isVisible(element.reviewed));
- element.patchRange = {...element.patchRange, patchNum: EDIT};
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
await element.updateComplete;
- assert.isFalse(isVisible(element.reviewed));
+ let checkbox = queryAndAssert(element, '#reviewed');
+ assert.isTrue(isVisible(checkbox));
+
+ element.patchNum = EDIT;
+ await element.updateComplete;
+
+ checkbox = queryAndAssert(element, '#reviewed');
+ assert.isFalse(isVisible(checkbox));
});
});
@@ -2380,45 +1822,39 @@
sinon.stub(element, 'initLineOfInterestAndCursor');
// Load file1
- element.viewState = {
- view: GerritView.DIFF,
+ viewModel.setState({
+ ...createDiffViewState(),
patchNum: 1 as RevisionPatchSetNum,
- changeNum: 101 as NumericChangeId,
repo: 'test-project' as RepoName,
- path: 'file1',
- };
- element.patchRange = {
- patchNum: 1 as RevisionPatchSetNum,
- basePatchNum: PARENT,
- };
+ diffView: {path: 'file1'},
+ });
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.change = {
...createParsedChange(),
revisions: createRevisions(1),
};
await element.updateComplete;
- assert.isFalse(setUrlStub.called);
+ assert.isFalse(navToDiffStub.called);
// Switch to file2
element.handleFileChange(
new CustomEvent('value-change', {detail: {value: 'file2'}})
);
- assert.isTrue(setUrlStub.calledOnce);
+ assert.isTrue(navToDiffStub.calledOnce);
// This is to mock the param change triggered by above navigate
- element.viewState = {
- view: GerritView.DIFF,
+ viewModel.setState({
+ ...createDiffViewState(),
patchNum: 1 as RevisionPatchSetNum,
- changeNum: 101 as NumericChangeId,
repo: 'test-project' as RepoName,
- path: 'file2',
- };
- element.patchRange = {
- patchNum: 1 as RevisionPatchSetNum,
- basePatchNum: PARENT,
- };
+ diffView: {path: 'file2'},
+ });
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
// No extra call
- assert.isTrue(setUrlStub.calledOnce);
+ assert.isTrue(navToDiffStub.calledOnce);
});
test('_computeDownloadDropdownLinks', () => {
@@ -2440,10 +1876,8 @@
element.change = createParsedChange();
element.change.project = 'test' as RepoName;
element.changeNum = 12 as NumericChangeId;
- element.patchRange = {
- patchNum: 1 as RevisionPatchSetNum,
- basePatchNum: PARENT,
- };
+ element.patchNum = 1 as RevisionPatchSetNum;
+ element.basePatchNum = PARENT;
element.path = 'index.php';
element.diff = createDiff();
assert.deepEqual(element.computeDownloadDropdownLinks(), downloadLinks);
@@ -2472,10 +1906,8 @@
element.change = createParsedChange();
element.change.project = 'test' as RepoName;
element.changeNum = 12 as NumericChangeId;
- element.patchRange = {
- patchNum: 3 as RevisionPatchSetNum,
- basePatchNum: 2 as BasePatchSetNum,
- };
+ element.patchNum = 3 as RevisionPatchSetNum;
+ element.basePatchNum = 2 as BasePatchSetNum;
element.path = 'index.php';
element.diff = diff;
assert.deepEqual(element.computeDownloadDropdownLinks(), downloadLinks);
@@ -2539,49 +1971,4 @@
);
});
});
-
- suite('unmodified files with comments', () => {
- let element: GrDiffView;
-
- setup(async () => {
- const changedFiles = {
- 'file1.txt': createFileInfo(),
- 'a/b/test.c': createFileInfo(),
- };
- stubRestApi('getConfig').returns(Promise.resolve(createServerInfo()));
- stubRestApi('getProjectConfig').returns(Promise.resolve(createConfig()));
- stubRestApi('getChangeFiles').returns(Promise.resolve(changedFiles));
- stubRestApi('saveFileReviewed').returns(Promise.resolve(new Response()));
- stubRestApi('getDiffComments').returns(Promise.resolve({}));
- stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
- stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
- stubRestApi('getReviewedFiles').returns(Promise.resolve([]));
- element = await fixture(html`<gr-diff-view></gr-diff-view>`);
- element.changeNum = 42 as NumericChangeId;
- });
-
- test('fetchFiles add files with comments without changes', () => {
- element.patchRange = {
- basePatchNum: 5 as BasePatchSetNum,
- patchNum: 10 as RevisionPatchSetNum,
- };
- element.changeComments = {
- getPaths: sinon.stub().returns({
- 'file2.txt': {},
- 'file1.txt': {},
- }),
- } as unknown as ChangeComments;
- element.changeNum = 23 as NumericChangeId;
- return element.fetchFiles().then(() => {
- assert.deepEqual(element.files, {
- sortedFileList: ['a/b/test.c', 'file1.txt', 'file2.txt'],
- changeFilesByPath: {
- 'file1.txt': createFileInfo(),
- 'file2.txt': {status: 'U'} as FileInfo,
- 'a/b/test.c': createFileInfo(),
- },
- });
- });
- });
- });
});
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index d9f88f7..325798c 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -21,6 +21,7 @@
import {
BasePatchSetNum,
EDIT,
+ NumericChangeId,
PARENT,
PatchSetNum,
RevisionInfo,
@@ -36,7 +37,7 @@
import {EditRevisionInfo} from '../../../types/types';
import {a11yStyles} from '../../../styles/gr-a11y-styles';
import {sharedStyles} from '../../../styles/shared-styles';
-import {LitElement, PropertyValues, css, html} from 'lit';
+import {LitElement, css, html, nothing} from 'lit';
import {customElement, property, query, state} from 'lit/decorators.js';
import {subscribe} from '../../lit/subscription-controller';
import {commentsModelToken} from '../../../models/comments/comments-model';
@@ -44,6 +45,8 @@
import {ifDefined} from 'lit/directives/if-defined.js';
import {ValueChangedEvent} from '../../../types/events';
import {GeneratedWebLink} from '../../../utils/weblink-util';
+import {changeModelToken} from '../../../models/change/change-model';
+import {changeViewModelToken} from '../../../models/views/change';
// Maximum length for patch set descriptions.
const PATCH_DESC_MAX_LENGTH = 500;
@@ -83,33 +86,27 @@
@query('#patchNumDropdown')
patchNumDropdown?: GrDropdownList;
- @property({type: Array})
- availablePatches?: PatchSet[];
+ @state()
+ availablePatches: PatchSet[] = [];
- @property({type: String})
- changeNum?: string;
+ @state()
+ changeNum?: NumericChangeId;
@property({type: Object})
filesWeblinks?: FilesWebLinks;
- @property({type: String})
+ @state()
patchNum?: RevisionPatchSetNum;
- @property({type: String})
+ @state()
basePatchNum?: BasePatchSetNum;
- /** Not used directly. Translated into `sortedRevisions` in willUpdate(). */
- @property({type: Object})
- revisions: (RevisionInfo | EditRevisionInfo)[] = [];
-
- @property({type: Object})
+ @state()
revisionInfo?: RevisionInfoClass;
- /** Private internal state, derived from `revisions` in willUpdate(). */
@state()
- private sortedRevisions: (RevisionInfo | EditRevisionInfo)[] = [];
+ sortedRevisions: (RevisionInfo | EditRevisionInfo)[] = [];
- /** Private internal state, visible for testing. */
@state()
changeComments?: ChangeComments;
@@ -118,10 +115,44 @@
private readonly getCommentsModel = resolve(this, commentsModelToken);
+ private readonly getChangeModel = resolve(this, changeModelToken);
+
+ private readonly getViewModel = resolve(this, changeViewModelToken);
+
constructor() {
super();
subscribe(
this,
+ () => this.getViewModel().changeNum$,
+ x => (this.changeNum = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().change$,
+ x => (this.revisionInfo = x ? new RevisionInfoClass(x) : undefined)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().patchNum$,
+ x => (this.patchNum = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().basePatchNum$,
+ x => (this.basePatchNum = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().patchsets$,
+ x => (this.availablePatches = x)
+ );
+ subscribe(
+ this,
+ () => this.getChangeModel().revisions$,
+ x => (this.sortedRevisions = sortRevisions(Object.values(x || {})))
+ );
+ subscribe(
+ this,
() => this.getCommentsModel().changeComments$,
x => (this.changeComments = x)
);
@@ -164,6 +195,9 @@
}
override render() {
+ if (!this.changeNum || !this.patchNum || !this.basePatchNum) {
+ return nothing;
+ }
return html`
<h3 class="assistive-tech-only">Patchset Range Selection</h3>
<span class="patchRange" aria-label="patch range starts with">
@@ -203,16 +237,9 @@
> `;
}
- override willUpdate(changedProperties: PropertyValues) {
- if (changedProperties.has('revisions')) {
- this.sortedRevisions = sortRevisions(Object.values(this.revisions || {}));
- }
- }
-
// Private method, but visible for testing.
computeBaseDropdownContent(): DropdownItem[] {
if (
- this.availablePatches === undefined ||
this.patchNum === undefined ||
this.changeComments === undefined ||
this.revisionInfo === undefined
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
index c90992f..584fcd7 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
@@ -9,7 +9,7 @@
import {GrPatchRangeSelect} from './gr-patch-range-select';
import {RevisionInfo as RevisionInfoClass} from '../../shared/revision-info/revision-info';
import {ChangeComments} from '../gr-comment-api/gr-comment-api';
-import {stubReporting, stubRestApi} from '../../../test/test-utils';
+import {stubReporting} from '../../../test/test-utils';
import {
BasePatchSetNum,
EDIT,
@@ -25,8 +25,11 @@
import {EditRevisionInfo, ParsedChangeInfo} from '../../../types/types';
import {SpecialFilePath} from '../../../constants/constants';
import {
+ createChangeViewState,
createEditRevision,
+ createParsedChange,
createRevision,
+ createRevisions,
} from '../../../test/test-data-generators';
import {PatchSet} from '../../../utils/patch-set-util';
import {
@@ -36,6 +39,9 @@
import {queryAndAssert} from '../../../test/test-utils';
import {fire} from '../../../utils/event-util';
import {fixture, html, assert} from '@open-wc/testing';
+import {testResolver} from '../../../test/common-test-setup';
+import {changeViewModelToken} from '../../../models/views/change';
+import {changeModelToken} from '../../../models/change/change-model';
type RevIdToRevisionInfo = {
[revisionId: string]: RevisionInfo | EditRevisionInfo;
@@ -53,16 +59,23 @@
}
setup(async () => {
- stubRestApi('getDiffComments').returns(Promise.resolve({}));
- stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
- stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
-
// Element must be wrapped in an element with direct access to the
// comment API.
element = await fixture(
html`<gr-patch-range-select></gr-patch-range-select>`
);
+ const viewModel = testResolver(changeViewModelToken);
+ viewModel.setState({
+ ...createChangeViewState(),
+ patchNum: 1 as RevisionPatchSetNum,
+ basePatchNum: PARENT,
+ });
+ const changeModel = testResolver(changeModelToken);
+ changeModel.updateStateChange({
+ ...createParsedChange(),
+ revisions: createRevisions(5),
+ });
// Stub methods on the changeComments object after changeComments has
// been initialized.
element.changeComments = new ChangeComments();
@@ -86,7 +99,7 @@
});
test('enabled/disabled options', async () => {
- element.revisions = [
+ element.sortedRevisions = [
createRevision(3),
createEditRevision(2),
createRevision(2),
@@ -119,13 +132,13 @@
{num: 2, sha: '3'} as PatchSet,
{num: 1, sha: '4'} as PatchSet,
];
- element.revisions = [
- createRevision(2),
- createRevision(3),
- createRevision(1),
+ element.sortedRevisions = [
createRevision(4),
+ createRevision(3),
+ createRevision(2),
+ createRevision(1),
];
- element.revisionInfo = getInfo(element.revisions);
+ element.revisionInfo = getInfo(element.sortedRevisions);
const expectedResult: DropdownItem[] = [
{
disabled: true,
@@ -175,13 +188,13 @@
});
test('computeBaseDropdownContent called when patchNum updates', async () => {
- element.revisions = [
- createRevision(2),
- createRevision(3),
- createRevision(1),
+ element.sortedRevisions = [
createRevision(4),
+ createRevision(3),
+ createRevision(2),
+ createRevision(1),
];
- element.revisionInfo = getInfo(element.revisions);
+ element.revisionInfo = getInfo(element.sortedRevisions);
element.availablePatches = [
{num: 1, sha: '1'} as PatchSet,
{num: 2, sha: '2'} as PatchSet,
@@ -201,13 +214,13 @@
});
test('computeBaseDropdownContent called when changeComments update', async () => {
- element.revisions = [
- createRevision(2),
- createRevision(3),
- createRevision(1),
+ element.sortedRevisions = [
createRevision(4),
+ createRevision(3),
+ createRevision(2),
+ createRevision(1),
];
- element.revisionInfo = getInfo(element.revisions);
+ element.revisionInfo = getInfo(element.sortedRevisions);
element.availablePatches = [
{num: 3, sha: '2'} as PatchSet,
{num: 2, sha: '3'} as PatchSet,
@@ -226,13 +239,13 @@
});
test('computePatchDropdownContent called when basePatchNum updates', async () => {
- element.revisions = [
+ element.sortedRevisions = [
createRevision(2),
createRevision(3),
createRevision(1),
createRevision(4),
];
- element.revisionInfo = getInfo(element.revisions);
+ element.revisionInfo = getInfo(element.sortedRevisions);
element.availablePatches = [
{num: 1, sha: '1'} as PatchSet,
{num: 2, sha: '2'} as PatchSet,
@@ -258,7 +271,7 @@
{num: 1, sha: '4'} as PatchSet,
];
element.basePatchNum = 1 as BasePatchSetNum;
- element.revisions = [
+ element.sortedRevisions = [
createRevision(3),
createEditRevision(2),
createRevision(2, 'description'),
@@ -402,13 +415,13 @@
{num: 2, sha: '3'} as PatchSet,
{num: 1, sha: '4'} as PatchSet,
];
- element.revisions = [
+ element.sortedRevisions = [
createRevision(2),
createRevision(3),
createRevision(1),
createRevision(4),
];
- element.revisionInfo = getInfo(element.revisions);
+ element.revisionInfo = getInfo(element.sortedRevisions);
await element.updateComplete;
element.addEventListener('patch-range-change', handler);
@@ -444,13 +457,13 @@
{num: 2, sha: '3'} as PatchSet,
{num: 1, sha: '4'} as PatchSet,
];
- element.revisions = [
+ element.sortedRevisions = [
createRevision(2),
createRevision(3),
createRevision(1),
createRevision(4),
];
- element.revisionInfo = getInfo(element.revisions);
+ element.revisionInfo = getInfo(element.sortedRevisions);
element.patchNum = 1 as PatchSetNumber;
element.basePatchNum = PARENT;
await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
index d1d05b7..ec1e48e 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.ts
@@ -29,7 +29,7 @@
import {customElement, property, query, state} from 'lit/decorators.js';
import {BindValueChangeEvent} from '../../../types/events';
import {IronInputElement} from '@polymer/iron-input/iron-input';
-import {createEditUrl} from '../../../models/views/edit';
+import {createEditUrl} from '../../../models/views/change';
import {resolve} from '../../../models/dependency';
import {modalStyles} from '../../../styles/gr-modal-styles';
import {whenVisible} from '../../../utils/dom-util';
@@ -429,8 +429,8 @@
const url = createEditUrl({
changeNum: this.change._number,
repo: this.change.project,
- path: this.path,
patchNum: this.patchNum,
+ editView: {path: this.path},
});
this.getNavigation().setUrl(url);
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
index 8e346c3..acc4c9e 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -31,8 +31,12 @@
import {resolve} from '../../../models/dependency';
import {changeModelToken} from '../../../models/change/change-model';
import {ShortcutController} from '../../lit/shortcut-controller';
-import {editViewModelToken, EditViewState} from '../../../models/views/edit';
-import {createChangeUrl} from '../../../models/views/change';
+import {
+ ChangeChildView,
+ changeViewModelToken,
+ ChangeViewState,
+ createChangeUrl,
+} from '../../../models/views/change';
import {userModelToken} from '../../../models/user/user-model';
import {storageServiceToken} from '../../../services/storage/gr-storage_impl';
@@ -60,7 +64,7 @@
*/
@property({type: Object})
- viewState?: EditViewState;
+ viewState?: ChangeViewState;
// private but used in test
@state() change?: ParsedChangeInfo;
@@ -95,7 +99,7 @@
private readonly getChangeModel = resolve(this, changeModelToken);
- private readonly getEditViewModel = resolve(this, editViewModelToken);
+ private readonly getViewModel = resolve(this, changeViewModelToken);
private readonly getNavigation = resolve(this, navigationToken);
@@ -116,8 +120,10 @@
);
subscribe(
this,
- () => this.getEditViewModel().state$,
+ () => this.getViewModel().state$,
state => {
+ // TODO: Add a setter for `viewState` instead of relying on the
+ // `viewStateChanged()` call here.
this.viewState = state;
this.viewStateChanged();
}
@@ -206,7 +212,7 @@
}
override render() {
- if (!this.viewState) return;
+ if (this.viewState?.childView !== ChangeChildView.EDIT) return nothing;
return html` ${this.renderHeader()} ${this.renderEndpoint()} `;
}
@@ -220,7 +226,7 @@
<span class="separator"></span>
<gr-editable-label
labelText="File path"
- .value=${this.viewState?.path}
+ .value=${this.viewState?.editView?.path}
placeholder="File path..."
@changed=${this.handlePathChanged}
></gr-editable-label>
@@ -277,7 +283,7 @@
></gr-endpoint-param>
<gr-endpoint-param
name="lineNum"
- .value=${this.viewState?.lineNum}
+ .value=${this.viewState?.editView?.lineNum}
></gr-endpoint-param>
<gr-default-editor
id="file"
@@ -298,19 +304,21 @@
}
get storageKey() {
- return `c${this.viewState?.changeNum}_ps${this.viewState?.patchNum}_${this.viewState?.path}`;
+ return `c${this.viewState?.changeNum}_ps${this.viewState?.patchNum}_${this.viewState?.editView?.path}`;
}
// private but used in test
viewStateChanged() {
- if (!this.viewState) return;
+ if (this.viewState?.childView !== ChangeChildView.EDIT) return;
// NOTE: This may be called before attachment (e.g. while parentElement is
// null). Fire title-change in an async so that, if attachment to the DOM
// has been queued, the event can bubble up to the handler in gr-app.
setTimeout(() => {
if (!this.viewState) return;
- const title = `Editing ${computeTruncatedPath(this.viewState.path)}`;
+ const title = `Editing ${computeTruncatedPath(
+ this.viewState.editView?.path
+ )}`;
fireTitleChange(this, title);
});
@@ -347,7 +355,7 @@
// private but used in test
async handlePathChanged(e: CustomEvent<string>): Promise<void> {
const changeNum = this.viewState?.changeNum;
- const currentPath = this.viewState?.path;
+ const currentPath = this.viewState?.editView?.path;
assertIsDefined(changeNum, 'change number');
assertIsDefined(currentPath, 'path');
@@ -376,7 +384,7 @@
getFileData() {
const changeNum = this.viewState?.changeNum;
const patchNum = this.viewState?.patchNum;
- const path = this.viewState?.path;
+ const path = this.viewState?.editView?.path;
assertIsDefined(changeNum, 'change number');
assertIsDefined(patchNum, 'patchset number');
assertIsDefined(path, 'path');
@@ -416,7 +424,7 @@
// private but used in test
saveEdit() {
const changeNum = this.viewState?.changeNum;
- const path = this.viewState?.path;
+ const path = this.viewState?.editView?.path;
assertIsDefined(changeNum, 'change number');
assertIsDefined(path, 'path');
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
index d428e18..1a4879d 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
@@ -72,7 +72,7 @@
labeltext="File path"
placeholder="File path..."
tabindex="0"
- title="${element.viewState?.path}"
+ title="${element.viewState?.editView?.path}"
>
</gr-editable-label>
</span>
@@ -373,7 +373,7 @@
...createEditViewState(),
changeNum: 1 as NumericChangeId,
patchNum: EDIT,
- path: 'test/path',
+ editView: {path: 'test/path'},
};
// Ensure no data is set with a bad response.
@@ -392,7 +392,7 @@
...createEditViewState(),
changeNum: 1 as NumericChangeId,
patchNum: EDIT,
- path: 'test/path',
+ editView: {path: 'test/path'},
};
// Ensure no data is set with a bad response.
@@ -415,7 +415,7 @@
...createEditViewState(),
changeNum: 1 as NumericChangeId,
patchNum: EDIT,
- path: 'test/path',
+ editView: {path: 'test/path'},
};
return element.getFileData().then(() => {
@@ -433,7 +433,7 @@
...createEditViewState(),
changeNum: 1 as NumericChangeId,
patchNum: EDIT,
- path: 'test/path',
+ editView: {path: 'test/path'},
};
return element.getFileData().then(() => {
@@ -530,7 +530,7 @@
...createEditViewState(),
changeNum: 1 as NumericChangeId,
patchNum: 1 as RevisionPatchSetNum,
- path: 'test',
+ editView: {path: 'test'},
};
const alertStub = sinon.stub();
@@ -562,7 +562,7 @@
...createEditViewState(),
changeNum: 1 as NumericChangeId,
patchNum: 1 as RevisionPatchSetNum,
- path: 'test',
+ editView: {path: 'test'},
};
const alertStub = sinon.stub();
@@ -583,7 +583,7 @@
...createEditViewState(),
changeNum: 1 as NumericChangeId,
patchNum: 1 as RevisionPatchSetNum,
- path: 'test',
+ editView: {path: 'test'},
};
assert.equal(element.storageKey, 'c1_ps1_test');
});
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index e959e05..eceb05e 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -74,6 +74,7 @@
import {userModelToken} from '../models/user/user-model';
import {modalStyles} from '../styles/gr-modal-styles';
import {AdminChildView, createAdminUrl} from '../models/views/admin';
+import {ChangeChildView, changeViewModelToken} from '../models/views/change';
interface ErrorInfo {
text: string;
@@ -119,6 +120,9 @@
@state() private view?: GerritView;
+ // TODO: Introduce a wrapper element for CHANGE, DIFF, EDIT view.
+ @state() private childView?: ChangeChildView;
+
@state() private lastError?: ErrorInfo;
// private but used in test
@@ -168,6 +172,8 @@
private readonly getRouterModel = resolve(this, routerModelToken);
+ private readonly getChangeViewModel = resolve(this, changeViewModelToken);
+
constructor() {
super();
@@ -237,6 +243,13 @@
if (view) this.errorView?.classList.remove('show');
}
);
+ subscribe(
+ this,
+ () => this.getChangeViewModel().childView$,
+ childView => {
+ this.childView = childView;
+ }
+ );
prefersDarkColorScheme().addEventListener('change', () => {
if (this.theme === AppTheme.AUTO) {
@@ -464,9 +477,7 @@
this.updateComplete.then(() => (this.invalidateChangeViewCache = false));
return nothing;
}
- return cache(
- this.view === GerritView.CHANGE ? this.changeViewTemplate() : nothing
- );
+ return cache(this.isChangeView() ? this.changeViewTemplate() : nothing);
}
// Template as not to create duplicates, for renderChangeView() only.
@@ -476,8 +487,27 @@
`;
}
+ private isChangeView() {
+ return (
+ this.view === GerritView.CHANGE &&
+ this.childView === ChangeChildView.OVERVIEW
+ );
+ }
+
+ private isDiffView() {
+ return (
+ this.view === GerritView.CHANGE && this.childView === ChangeChildView.DIFF
+ );
+ }
+
+ private isEditorView() {
+ return (
+ this.view === GerritView.CHANGE && this.childView === ChangeChildView.EDIT
+ );
+ }
+
private renderEditorView() {
- if (this.view !== GerritView.EDIT) return nothing;
+ if (!this.isEditorView()) return nothing;
return html`<gr-editor-view></gr-editor-view>`;
}
@@ -486,9 +516,7 @@
this.updateComplete.then(() => (this.invalidateDiffViewCache = false));
return nothing;
}
- return cache(
- this.view === GerritView.DIFF ? this.diffViewTemplate() : nothing
- );
+ return cache(this.isDiffView() ? this.diffViewTemplate() : nothing);
}
private diffViewTemplate() {
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index 3008236..0261992 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -13,8 +13,6 @@
import {SearchViewState} from '../models/views/search';
import {DashboardViewState} from '../models/views/dashboard';
import {ChangeViewState} from '../models/views/change';
-import {DiffViewState} from '../models/views/diff';
-import {EditViewState} from '../models/views/edit';
export interface AppElement extends HTMLElement {
params: AppElementParams;
@@ -41,8 +39,6 @@
| SearchViewState
| SettingsViewState
| AgreementViewState
- | DiffViewState
- | EditViewState
| AppElementJustRegisteredParams;
export function isAppElementJustRegisteredParams(
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 1549da5..0e75abb 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
@@ -95,12 +95,26 @@
.lengthCounter {
font-weight: var(--font-weight-normal);
}
+ p {
+ max-width: 65ch;
+ margin-bottom: var(--spacing-m);
+ }
`,
];
override render() {
if (!this.account || this.loading) return nothing;
return html`<div class="gr-form-styles">
+ <p>
+ 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"
+ >Learn more</a
+ >
+ </p>
+ <gr-endpoint-decorator name="profile"></gr-endpoint-decorator>
<section>
<span class="title"></span>
<span class="value">
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
index e968b12..f954960 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
@@ -69,6 +69,16 @@
element,
/* HTML */ `
<div class="gr-form-styles">
+ <p>
+ 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"
+ >Learn more</a
+ >
+ </p>
+ <gr-endpoint-decorator name="profile"></gr-endpoint-decorator>
<section>
<span class="title"></span>
<span class="value">
@@ -134,7 +144,8 @@
</span>
</section>
</div>
- `
+ `,
+ {ignoreChildren: ['p']}
);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index df41b1f4..dd0fbca 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -72,8 +72,7 @@
import {whenRendered} from '../../../utils/dom-util';
import {Interaction} from '../../../constants/reporting';
import {HtmlPatched} from '../../../utils/lit-util';
-import {createDiffUrl} from '../../../models/views/diff';
-import {createChangeUrl} from '../../../models/views/change';
+import {createChangeUrl, createDiffUrl} from '../../../models/views/change';
import {userModelToken} from '../../../models/user/user-model';
import {highlightServiceToken} from '../../../services/highlight/highlight-service';
@@ -746,8 +745,8 @@
return createDiffUrl({
changeNum: this.changeNum,
repo: this.repoName,
- path: this.thread.path,
patchNum: this.thread.patchNum,
+ diffView: {path: this.thread.path},
});
}
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 090dfef..66beaf1 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -60,7 +60,7 @@
import {Interaction} from '../../../constants/reporting';
import {KnownExperimentId} from '../../../services/flags/flags';
import {isBase64FileContent} from '../../../api/rest-api';
-import {createDiffUrl} from '../../../models/views/diff';
+import {createDiffUrl} from '../../../models/views/change';
import {userModelToken} from '../../../models/user/user-model';
import {modalStyles} from '../../../styles/gr-modal-styles';
@@ -547,8 +547,9 @@
${this.renderDraftLabel()}
</div>
<div class="headerMiddle">${this.renderCollapsedContent()}</div>
- ${this.renderRunDetails()} ${this.renderDeleteButton()}
- ${this.renderPatchset()} ${this.renderDate()} ${this.renderToggle()}
+ ${this.renderSuggestEditButton()} ${this.renderRunDetails()}
+ ${this.renderDeleteButton()} ${this.renderPatchset()}
+ ${this.renderDate()} ${this.renderToggle()}
</div>
`;
}
@@ -777,10 +778,9 @@
return html`
<div class="rightActions">
${this.autoSaving ? html`. ` : ''}
- ${this.renderDiscardButton()} ${this.renderSuggestEditButton()}
- ${this.renderPreviewSuggestEditButton()} ${this.renderEditButton()}
- ${this.renderCancelButton()} ${this.renderSaveButton()}
- ${this.renderCopyLinkIcon()}
+ ${this.renderDiscardButton()} ${this.renderPreviewSuggestEditButton()}
+ ${this.renderEditButton()} ${this.renderCancelButton()}
+ ${this.renderSaveButton()} ${this.renderCopyLinkIcon()}
</div>
`;
}
@@ -809,6 +809,7 @@
return nothing;
}
if (
+ !this.editing ||
this.permanentEditingMode ||
this.comment?.path === SpecialFilePath.PATCHSET_LEVEL_COMMENTS
) {
@@ -1139,7 +1140,8 @@
fire(this, 'open-fix-preview', await this.createFixPreview());
}
- async createSuggestEdit() {
+ async createSuggestEdit(e: MouseEvent) {
+ e.stopPropagation();
const line = await this.getCommentedCode();
this.messageText += `${USER_SUGGESTION_START_PATTERN}${line}${'\n```'}`;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index ec9c875..3390369 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -854,12 +854,12 @@
.initiallyCollapsed=${false}
></gr-comment>`
);
+ element.editing = true;
});
test('renders suggest fix button', () => {
assert.dom.equal(
queryAndAssert(element, 'gr-button.suggestEdit'),
/* HTML */ `<gr-button
- aria-disabled="false"
class="action suggestEdit"
link=""
role="button"
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index c8663e6..627ea27 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -198,7 +198,16 @@
text = htmlEscape(text).toString();
// Unescape block quotes '>'. This is slightly dangerous as '>' can be used
// in HTML fragments, but it is insufficient on it's own.
- text = text.replace(/(^|\n)>/g, '$1>');
+ for (;;) {
+ const newText = text.replace(
+ /(^|\n)((?:\s{0,3}>)*\s{0,3})>/g,
+ '$1$2>'
+ );
+ if (newText === text) {
+ break;
+ }
+ text = newText;
+ }
return text;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index 9acca0f..3881c62 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -593,5 +593,27 @@
`
);
});
+
+ test('renders nested block quotes', async () => {
+ element.content = '> > > block quote';
+ await element.updateComplete;
+
+ assert.shadowDom.equal(
+ element,
+ /* HTML */ `
+ <marked-element>
+ <div slot="markdown-html" class="markdown-html">
+ <blockquote>
+ <blockquote>
+ <blockquote>
+ <p>block quote</p>
+ </blockquote>
+ </blockquote>
+ </blockquote>
+ </div>
+ </marked-element>
+ `
+ );
+ });
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
index 1460246..f8ae38c 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.ts
@@ -553,7 +553,7 @@
assert.deepEqual(indentCommand.args[0], ['insertText', false, '\n ']);
});
- test('emoji dropdown is closed when iron-overlay-closed is fired', async () => {
+ test('emoji dropdown is closed when dropdown-closed is fired', async () => {
const resetSpy = sinon.spy(element, 'closeDropdown');
element.emojiSuggestions!.dispatchEvent(
new CustomEvent('dropdown-closed', {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
index 1c09372..28e83ae 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
@@ -16,7 +16,11 @@
DiffPreferencesInfo,
} from '../../../api/diff';
import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {countLines, diffClasses} from '../gr-diff/gr-diff-utils';
+import {
+ countLines,
+ diffClasses,
+ getResponsiveMode,
+} from '../gr-diff/gr-diff-utils';
import {GrDiffRow} from './gr-diff-row';
import '../gr-context-controls/gr-context-controls-section';
import '../gr-context-controls/gr-context-controls';
@@ -71,6 +75,7 @@
if (this.group.ignoredWhitespaceOnly) extras.push('ignoredWhitespaceOnly');
const pairs = this.getLinePairs();
+ const responsiveMode = getResponsiveMode(this.diffPrefs, this.renderPrefs);
const body = html`
<tbody class=${diffClasses(...extras)}>
${this.renderContextControls()} ${this.renderMoveControls()}
@@ -86,6 +91,7 @@
.lineLength=${this.diffPrefs?.line_length ?? 80}
.tabSize=${this.diffPrefs?.tab_size ?? 2}
.unifiedDiff=${this.isUnifiedDiff()}
+ .responsiveMode=${responsiveMode}
>
</gr-diff-row>
`;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
index f7cc7e5..c1b13ac 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
@@ -115,7 +115,7 @@
}
const piece = html`<span
class=${diffClasses('tab')}
- style=${styleMap({'tab-size': `${this.tabSize}`})}
+ style=${styleMap({'tab-size': `${tabSize}`})}
>${TAB}</span
>`;
this.pieces.push(piece);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
index 8f0503a..a0e7840 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
@@ -75,18 +75,25 @@
test('tab wrapper style', async () => {
element.lineLimit = 100;
- element.text = '\t';
- await element.updateComplete;
- for (const size of [1, 3, 8, 55]) {
- element.tabSize = size;
- await element.updateComplete;
- await check(
- '\t',
- /* HTML */ `
- <span class="gr-diff tab" style="tab-size: ${size};"> </span>
- `
- );
- }
+ element.tabSize = 4;
+ await check(
+ '\t',
+ /* HTML */ '<span class="gr-diff tab" style="tab-size:4;"></span>'
+ );
+ await check(
+ 'abc\t',
+ /* HTML */ 'abc<span class="gr-diff tab" style="tab-size:1;"></span>'
+ );
+
+ element.tabSize = 8;
+ await check(
+ '\t',
+ /* HTML */ '<span class="gr-diff tab" style="tab-size:8;"></span>'
+ );
+ await check(
+ 'abc\t',
+ /* HTML */ 'abc<span class="gr-diff tab" style="tab-size:5;"></span>'
+ );
});
test('tab wrapper insertion', async () => {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
index f583c2e..87fd5ca 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -48,15 +48,21 @@
}, 0);
}
+export function isFileUnchanged(diff: DiffInfo) {
+ return !diff.content.some(
+ content => (content.a && !content.common) || (content.b && !content.common)
+ );
+}
+
export function getResponsiveMode(
- prefs: DiffPreferencesInfo,
+ prefs?: DiffPreferencesInfo,
renderPrefs?: RenderPreferences
): DiffResponsiveMode {
if (renderPrefs?.responsive_mode) {
return renderPrefs.responsive_mode;
}
// Backwards compatibility to the line_wrapping param.
- if (prefs.line_wrapping) {
+ if (prefs?.line_wrapping) {
return 'FULL_RESPONSIVE';
}
return 'NONE';
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
index 98b4586..25dc768 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
@@ -4,8 +4,15 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {assert} from '@open-wc/testing';
+import {DiffInfo} from '../../../api/diff';
import '../../../test/common-test-setup';
-import {createElementDiff, formatText, createTabWrapper} from './gr-diff-utils';
+import {createDiff} from '../../../test/test-data-generators';
+import {
+ createElementDiff,
+ formatText,
+ createTabWrapper,
+ isFileUnchanged,
+} from './gr-diff-utils';
const LINE_BREAK_HTML = '<span class="gr-diff br"></span>';
@@ -156,4 +163,36 @@
expectTextLength('abc\tde\t', 10, 20);
expectTextLength('\t\t\t\t\t', 20, 100);
});
+
+ test('isFileUnchanged', () => {
+ let diff: DiffInfo = {
+ ...createDiff(),
+ content: [
+ {a: ['abcd'], ab: ['ef']},
+ {b: ['ancd'], a: ['xx']},
+ ],
+ };
+ assert.equal(isFileUnchanged(diff), false);
+ diff = {
+ ...createDiff(),
+ content: [{ab: ['abcd']}, {ab: ['ancd']}],
+ };
+ assert.equal(isFileUnchanged(diff), true);
+ diff = {
+ ...createDiff(),
+ content: [
+ {a: ['abcd'], ab: ['ef'], common: true},
+ {b: ['ancd'], ab: ['xx']},
+ ],
+ };
+ assert.equal(isFileUnchanged(diff), false);
+ diff = {
+ ...createDiff(),
+ content: [
+ {a: ['abcd'], ab: ['ef'], common: true},
+ {b: ['ancd'], ab: ['xx'], common: true},
+ ],
+ };
+ assert.equal(isFileUnchanged(diff), true);
+ });
});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index 2097170..a0526ed 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -755,9 +755,9 @@
rule wins in case of same specificity.
*/
.trailing-whitespace,
- .content .trailing-whitespace,
+ .content .contentText .trailing-whitespace,
.trailing-whitespace .intraline,
- .content .trailing-whitespace .intraline {
+ .content .contentText .trailing-whitespace .intraline {
border-radius: var(--border-radius, 4px);
background-color: var(--diff-trailing-whitespace-indicator);
}
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index 8282a3f..446822f 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -12,6 +12,7 @@
PatchSetNum,
PreferencesInfo,
RevisionPatchSetNum,
+ PatchSetNumber,
} from '../../types/common';
import {DefaultBase} from '../../constants/constants';
import {combineLatest, from, fromEvent, Observable, forkJoin, of} from 'rxjs';
@@ -22,7 +23,6 @@
startWith,
switchMap,
} from 'rxjs/operators';
-import {RouterModel} from '../../services/router/router-model';
import {
computeAllPatchSets,
computeLatestPatchNum,
@@ -38,6 +38,13 @@
import {UserModel} from '../user/user-model';
import {define} from '../dependency';
import {isOwner} from '../../utils/change-util';
+import {
+ ChangeViewModel,
+ createChangeUrl,
+ createDiffUrl,
+ createEditUrl,
+} from '../views/change';
+import {NavigationService} from '../../elements/core/gr-navigation/gr-navigation';
export enum LoadingStatus {
NOT_LOADED = 'NOT_LOADED',
@@ -56,12 +63,6 @@
loadingStatus: LoadingStatus;
change?: ParsedChangeInfo;
/**
- * The name of the file user is viewing in the diff view mode. File path is
- * specified in the url or derived from the commentId.
- * Does not apply to change-view or edit-view.
- */
- diffPath?: string;
- /**
* The list of reviewed files, kept in the model because we want changes made
* in one view to reflect on other views without re-rendering the other views.
* Undefined means it's still loading and empty set means no files reviewed.
@@ -76,7 +77,7 @@
export function updateChangeWithEdit(
change?: ParsedChangeInfo,
edit?: EditInfo,
- routerPatchNum?: PatchSetNum
+ viewModelPatchNum?: PatchSetNum
): ParsedChangeInfo | undefined {
if (!change || !edit) return change;
assertIsDefined(edit.commit.commit, 'edit.commit.commit');
@@ -95,7 +96,7 @@
// which is still done in change-view. `_patchRange.patchNum` should
// eventually also be model managed, so we can reconcile these two code
// snippets into one location.
- if (routerPatchNum === undefined) {
+ if (viewModelPatchNum === undefined) {
change.current_revision = edit.commit.commit;
}
return change;
@@ -103,20 +104,20 @@
/**
* Derives the base patchset number from all the data that can potentially
- * influence it. Mostly just returns `routerBasePatchNum` or PARENT, but has
+ * influence it. Mostly just returns `viewModelBasePatchNum` or PARENT, but has
* some special logic when looking at merge commits.
*
- * NOTE: At the moment this returns just `routerBasePatchNum ?? PARENT`, see
+ * NOTE: At the moment this returns just `viewModelBasePatchNum ?? PARENT`, see
* TODO below.
*/
function computeBase(
- routerBasePatchNum: BasePatchSetNum | undefined,
+ viewModelBasePatchNum: BasePatchSetNum | undefined,
patchNum: RevisionPatchSetNum | undefined,
change: ParsedChangeInfo | undefined,
preferences: PreferencesInfo
): BasePatchSetNum {
- if (routerBasePatchNum && routerBasePatchNum !== PARENT) {
- return routerBasePatchNum;
+ if (viewModelBasePatchNum && viewModelBasePatchNum !== PARENT) {
+ return viewModelBasePatchNum;
}
if (!change || !patchNum) return PARENT;
@@ -129,7 +130,7 @@
// but we are not sure whether this was ever 100% working correctly. A
// major challenge is being able to select PARENT explicitly even if your
// preference for the default choice is FIRST_PARENT. <gr-file-list-header>
- // just uses `navigation.setUrl()` and the router does not have any
+ // just uses `navigation.setUrl()` and the view model does not have any
// way of forcing the basePatchSetNum to stick to PARENT without being
// altered back to FIRST_PARENT here.
// See also corresponding TODO in gr-settings-view.
@@ -150,7 +151,11 @@
export class ChangeModel extends Model<ChangeState> {
private change?: ParsedChangeInfo;
- private patchNum?: PatchSetNum;
+ private patchNum?: RevisionPatchSetNum;
+
+ private basePatchNum?: BasePatchSetNum;
+
+ private latestPatchNum?: PatchSetNumber;
public readonly change$ = select(
this.state$,
@@ -162,11 +167,6 @@
changeState => changeState.loadingStatus
);
- public readonly diffPath$ = select(
- this.state$,
- changeState => changeState?.diffPath
- );
-
public readonly reviewedFiles$ = select(
this.state$,
changeState => changeState?.reviewedFiles
@@ -178,8 +178,17 @@
public readonly labels$ = select(this.change$, change => change?.labels);
- public readonly latestPatchNum$ = select(this.change$, change =>
- computeLatestPatchNum(computeAllPatchSets(change))
+ public readonly revisions$ = select(
+ this.change$,
+ change => change?.revisions
+ );
+
+ public readonly patchsets$ = select(this.change$, change =>
+ computeAllPatchSets(change)
+ );
+
+ public readonly latestPatchNum$ = select(this.patchsets$, patchsets =>
+ computeLatestPatchNum(patchsets)
);
/**
@@ -192,57 +201,57 @@
public readonly patchNum$: Observable<RevisionPatchSetNum | undefined> =
select(
combineLatest([
- this.routerModel.state$,
+ this.viewModel.state$,
this.state$,
this.latestPatchNum$,
]).pipe(
/**
- * If you depend on both, router and change state, then you want to
- * filter out inconsistent state, e.g. router changeNum already updated,
- * change not yet reset to undefined.
+ * If you depend on both, view model and change state, then you want to
+ * filter out inconsistent state, e.g. view model changeNum already
+ * updated, change not yet reset to undefined.
*/
- filter(([routerState, changeState, _latestPatchN]) => {
+ filter(([viewModelState, changeState, _latestPatchN]) => {
const changeNum = changeState.change?._number;
- const routerChangeNum = routerState.changeNum;
- return changeNum === undefined || changeNum === routerChangeNum;
+ const viewModelChangeNum = viewModelState?.changeNum;
+ return changeNum === undefined || changeNum === viewModelChangeNum;
})
),
- ([routerState, _changeState, latestPatchN]) =>
- routerState?.patchNum || latestPatchN
+ ([viewModelState, _changeState, latestPatchN]) =>
+ viewModelState?.patchNum || latestPatchN
);
/**
* Emits the base patchset number. This is identical to the
- * `routerBasePatchNum$`, but has some special logic for merges.
+ * `viewModel.basePatchNum$`, but has some special logic for merges.
*
* Note that this selector can emit without the change being available!
*/
public readonly basePatchNum$: Observable<BasePatchSetNum> =
/**
- * If you depend on both, router and change state, then you want to filter
- * out inconsistent state, e.g. router changeNum already updated, change not
- * yet reset to undefined.
+ * If you depend on both, view model and change state, then you want to
+ * filter out inconsistent state, e.g. view model changeNum already
+ * updated, change not yet reset to undefined.
*/
select(
combineLatest([
- this.routerModel.state$,
+ this.viewModel.state$,
this.state$,
this.userModel.state$,
]).pipe(
- filter(([routerState, changeState, _]) => {
+ filter(([viewModelState, changeState, _]) => {
const changeNum = changeState.change?._number;
- const routerChangeNum = routerState.changeNum;
- return changeNum === undefined || changeNum === routerChangeNum;
+ const viewModelChangeNum = viewModelState?.changeNum;
+ return changeNum === undefined || changeNum === viewModelChangeNum;
}),
withLatestFrom(
- this.routerModel.routerBasePatchNum$,
+ this.viewModel.basePatchNum$,
this.patchNum$,
this.change$,
this.userModel.preferences$
)
),
- ([_, routerBasePatchNum, patchNum, change, preferences]) =>
- computeBase(routerBasePatchNum, patchNum, change, preferences)
+ ([_, viewModelBasePatchNum, patchNum, change, preferences]) =>
+ computeBase(viewModelBasePatchNum, patchNum, change, preferences)
);
public readonly isOwner$: Observable<boolean> = select(
@@ -257,13 +266,14 @@
);
constructor(
- private readonly routerModel: RouterModel,
+ private readonly navigation: NavigationService,
+ private readonly viewModel: ChangeViewModel,
private readonly restApiService: RestApiService,
private readonly userModel: UserModel
) {
super(initialState);
this.subscriptions = [
- combineLatest([this.routerModel.routerChangeNum$, this.reload$])
+ combineLatest([this.viewModel.changeNum$, this.reload$])
.pipe(
map(([changeNum, _]) => changeNum),
switchMap(changeNum => {
@@ -272,7 +282,7 @@
const edit = from(this.restApiService.getChangeEdit(changeNum));
return forkJoin([change, edit]);
}),
- withLatestFrom(this.routerModel.routerPatchNum$),
+ withLatestFrom(this.viewModel.patchNum$),
map(([[change, edit], patchNum]) =>
updateChangeWithEdit(change, edit, patchNum)
)
@@ -289,6 +299,12 @@
}),
this.change$.subscribe(change => (this.change = change)),
this.patchNum$.subscribe(patchNum => (this.patchNum = patchNum)),
+ this.basePatchNum$.subscribe(
+ basePatchNum => (this.basePatchNum = basePatchNum)
+ ),
+ this.latestPatchNum$.subscribe(
+ latestPatchNum => (this.latestPatchNum = latestPatchNum)
+ ),
combineLatest([this.patchNum$, this.changeNum$, this.userModel.loggedIn$])
.pipe(
switchMap(([patchNum, changeNum, loggedIn]) => {
@@ -303,11 +319,6 @@
];
}
- // Temporary workaround until path is derived in the model itself.
- updatePath(diffPath?: string) {
- this.updateState({diffPath});
- }
-
updateStateReviewedFiles(reviewedFiles: string[]) {
this.updateState({reviewedFiles});
}
@@ -372,6 +383,65 @@
return this.getState().change;
}
+ diffUrl(
+ diffView: {path: string; lineNum?: number},
+ patchNum = this.patchNum,
+ basePatchNum = this.basePatchNum
+ ) {
+ if (!this.change) return;
+ if (!this.patchNum) return;
+ return createDiffUrl({
+ change: this.change,
+ patchNum,
+ basePatchNum,
+ diffView,
+ });
+ }
+
+ navigateToDiff(
+ diffView: {path: string; lineNum?: number},
+ patchNum = this.patchNum,
+ basePatchNum = this.basePatchNum
+ ) {
+ const url = this.diffUrl(diffView, patchNum, basePatchNum);
+ if (!url) return;
+ this.navigation.setUrl(url);
+ }
+
+ changeUrl(openReplyDialog = false) {
+ if (!this.change) return;
+ const isLatest = this.latestPatchNum === this.patchNum;
+ return createChangeUrl({
+ change: this.change,
+ patchNum:
+ isLatest && this.basePatchNum === PARENT ? undefined : this.patchNum,
+ basePatchNum: this.basePatchNum,
+ openReplyDialog,
+ });
+ }
+
+ navigateToChange(openReplyDialog = false) {
+ const url = this.changeUrl(openReplyDialog);
+ if (!url) return;
+ this.navigation.setUrl(url);
+ }
+
+ editUrl(editView: {path: string; lineNum?: number}) {
+ if (!this.change) return;
+ return createEditUrl({
+ changeNum: this.change._number,
+ repo: this.change.project,
+ patchNum: this.patchNum,
+ editView,
+ });
+ }
+
+ navigateToEdit(editView: {path: string; lineNum?: number}) {
+ const url = this.editUrl(editView);
+ if (!url) return;
+ this.navigation.setUrl(url);
+ }
+
/**
* Check whether there is no newer patch than the latest patch that was
* available when this change was loaded.
diff --git a/polygerrit-ui/app/models/change/change-model_test.ts b/polygerrit-ui/app/models/change/change-model_test.ts
index a2fc7c9..c11c15b 100644
--- a/polygerrit-ui/app/models/change/change-model_test.ts
+++ b/polygerrit-ui/app/models/change/change-model_test.ts
@@ -9,6 +9,7 @@
import {
createChange,
createChangeMessageInfo,
+ createChangeViewState,
createEditInfo,
createParsedChange,
createRevision,
@@ -28,12 +29,13 @@
} from '../../types/common';
import {ParsedChangeInfo} from '../../types/types';
import {getAppContext} from '../../services/app-context';
-import {GerritView, routerModelToken} from '../../services/router/router-model';
import {ChangeState, LoadingStatus, updateChangeWithEdit} from './change-model';
import {ChangeModel} from './change-model';
import {assert} from '@open-wc/testing';
import {testResolver} from '../../test/common-test-setup';
import {userModelToken} from '../user/user-model';
+import {changeViewModelToken} from '../views/change';
+import {navigationToken} from '../../elements/core/gr-navigation/gr-navigation';
suite('updateChangeWithEdit() tests', () => {
test('undefined change', async () => {
@@ -83,7 +85,8 @@
setup(() => {
changeModel = new ChangeModel(
- testResolver(routerModelToken),
+ testResolver(navigationToken),
+ testResolver(changeViewModelToken),
getAppContext().restApiService,
testResolver(userModelToken)
);
@@ -121,10 +124,7 @@
assert.equal(stub.callCount, 0);
assert.isUndefined(state?.change);
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
- changeNum: knownChange._number,
- });
+ testResolver(changeViewModelToken).setState(createChangeViewState());
state = await waitForLoadingStatus(LoadingStatus.LOADING);
assert.equal(stub.callCount, 1);
assert.isUndefined(state?.change);
@@ -140,10 +140,7 @@
const promise = mockPromise<ParsedChangeInfo | undefined>();
const stub = stubRestApi('getChangeDetail').callsFake(() => promise);
let state: ChangeState;
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
- changeNum: knownChange._number,
- });
+ testResolver(changeViewModelToken).setState(createChangeViewState());
promise.resolve(knownChange);
state = await waitForLoadingStatus(LoadingStatus.LOADED);
@@ -164,10 +161,7 @@
let promise = mockPromise<ParsedChangeInfo | undefined>();
const stub = stubRestApi('getChangeDetail').callsFake(() => promise);
let state: ChangeState;
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
- changeNum: knownChange._number,
- });
+ testResolver(changeViewModelToken).setState(createChangeViewState());
promise.resolve(knownChange);
state = await waitForLoadingStatus(LoadingStatus.LOADED);
@@ -178,8 +172,8 @@
_number: 123 as NumericChangeId,
};
promise = mockPromise<ParsedChangeInfo | undefined>();
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
+ testResolver(changeViewModelToken).setState({
+ ...createChangeViewState(),
changeNum: otherChange._number,
});
state = await waitForLoadingStatus(LoadingStatus.LOADING);
@@ -197,10 +191,7 @@
let promise = mockPromise<ParsedChangeInfo | undefined>();
const stub = stubRestApi('getChangeDetail').callsFake(() => promise);
let state: ChangeState;
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
- changeNum: knownChange._number,
- });
+ testResolver(changeViewModelToken).setState(createChangeViewState());
promise.resolve(knownChange);
state = await waitForLoadingStatus(LoadingStatus.LOADED);
@@ -208,10 +199,7 @@
promise = mockPromise<ParsedChangeInfo | undefined>();
promise.resolve(undefined);
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
- changeNum: undefined,
- });
+ testResolver(changeViewModelToken).setState(undefined);
state = await waitForLoadingStatus(LoadingStatus.NOT_LOADED);
assert.equal(stub.callCount, 2);
assert.isUndefined(state?.change);
@@ -220,10 +208,7 @@
promise = mockPromise<ParsedChangeInfo | undefined>();
promise.resolve(knownChange);
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
- changeNum: knownChange._number,
- });
+ testResolver(changeViewModelToken).setState(createChangeViewState());
state = await waitForLoadingStatus(LoadingStatus.LOADED);
assert.equal(stub.callCount, 3);
assert.equal(state?.change, knownChange);
@@ -299,7 +284,7 @@
assert.equal(spy.lastCall.firstArg, PARENT);
// test update
- testResolver(routerModelToken).updateState({
+ testResolver(changeViewModelToken).updateState({
basePatchNum: 1 as PatchSetNumber,
});
assert.equal(spy.callCount, 2);
diff --git a/polygerrit-ui/app/models/change/files-model.ts b/polygerrit-ui/app/models/change/files-model.ts
index 07e64a2..0683af0 100644
--- a/polygerrit-ui/app/models/change/files-model.ts
+++ b/polygerrit-ui/app/models/change/files-model.ts
@@ -23,6 +23,9 @@
import {ChangeModel} from './change-model';
import {CommentsModel} from '../comments/comments-model';
+export type FileNameToNormalizedFileInfoMap = {
+ [name: string]: NormalizedFileInfo;
+};
export interface NormalizedFileInfo extends FileInfo {
__path: string;
// Compared to `FileInfo` these four props are required here.
@@ -115,7 +118,12 @@
export class FilesModel extends Model<FilesState> {
public readonly files$ = select(this.state$, state => state.files);
- public readonly filesWithUnmodified$ = select(
+ /**
+ * `files$` only includes the files that were modified. Here we also include
+ * all unmodified files that have comments with
+ * `status: FileInfoStatus.UNMODIFIED`.
+ */
+ public readonly filesIncludingUnmodified$ = select(
combineLatest([this.files$, this.commentsModel.commentedPaths$]),
([files, commentedPaths]) => addUnmodified(files, commentedPaths)
);
diff --git a/polygerrit-ui/app/models/comments/comments-model.ts b/polygerrit-ui/app/models/comments/comments-model.ts
index 51b4591..1fdf342 100644
--- a/polygerrit-ui/app/models/comments/comments-model.ts
+++ b/polygerrit-ui/app/models/comments/comments-model.ts
@@ -29,7 +29,6 @@
} from '../../utils/comment-util';
import {deepEqual} from '../../utils/deep-util';
import {select} from '../../utils/observable-util';
-import {RouterModel} from '../../services/router/router-model';
import {define} from '../dependency';
import {combineLatest, forkJoin, from, Observable, of} from 'rxjs';
import {fire, fireAlert, fireEvent} from '../../utils/event-util';
@@ -54,6 +53,7 @@
switchMap,
} from 'rxjs/operators';
import {isDefined} from '../../types/types';
+import {ChangeViewModel} from '../views/change';
export interface CommentState {
/** undefined means 'still loading' */
@@ -415,7 +415,7 @@
private discardedDrafts: DraftInfo[] = [];
constructor(
- private readonly routerModel: RouterModel,
+ private readonly changeViewModel: ChangeViewModel,
private readonly changeModel: ChangeModel,
private readonly accountsModel: AccountsModel,
private readonly restApiService: RestApiService,
@@ -432,7 +432,7 @@
this.changeModel.patchNum$.subscribe(x => (this.patchNum = x))
);
this.subscriptions.push(
- this.routerModel.routerChangeNum$.subscribe(changeNum => {
+ this.changeViewModel.changeNum$.subscribe(changeNum => {
this.changeNum = changeNum;
this.setState({...initialState});
this.reloadAllComments();
diff --git a/polygerrit-ui/app/models/comments/comments-model_test.ts b/polygerrit-ui/app/models/comments/comments-model_test.ts
index cab36d2..a689e42 100644
--- a/polygerrit-ui/app/models/comments/comments-model_test.ts
+++ b/polygerrit-ui/app/models/comments/comments-model_test.ts
@@ -6,6 +6,7 @@
import '../../test/common-test-setup';
import {
createAccountWithEmail,
+ createChangeViewState,
createDraft,
} from '../../test/test-data-generators';
import {
@@ -20,17 +21,16 @@
import {
createComment,
createParsedChange,
- TEST_NUMERIC_CHANGE_ID,
} from '../../test/test-data-generators';
import {stubRestApi, waitUntil, waitUntilCalled} from '../../test/test-utils';
import {getAppContext} from '../../services/app-context';
-import {GerritView, routerModelToken} from '../../services/router/router-model';
import {PathToCommentsInfoMap} from '../../types/common';
import {changeModelToken} from '../change/change-model';
import {assert} from '@open-wc/testing';
import {testResolver} from '../../test/common-test-setup';
import {accountsModelToken} from '../accounts-model/accounts-model';
import {ChangeComments} from '../../elements/diff/gr-comment-api/gr-comment-api';
+import {changeViewModelToken} from '../views/change';
suite('comments model tests', () => {
test('updateStateDeleteDraft', () => {
@@ -72,7 +72,7 @@
test('loads comments', async () => {
const model = new CommentsModel(
- testResolver(routerModelToken),
+ testResolver(changeViewModelToken),
testResolver(changeModelToken),
testResolver(accountsModelToken),
getAppContext().restApiService,
@@ -100,10 +100,7 @@
model.portedComments$.subscribe(c => (portedComments = c ?? {}))
);
- testResolver(routerModelToken).setState({
- view: GerritView.CHANGE,
- changeNum: TEST_NUMERIC_CHANGE_ID,
- });
+ testResolver(changeViewModelToken).setState(createChangeViewState());
testResolver(changeModelToken).updateStateChange(createParsedChange());
await waitUntilCalled(diffCommentsSpy, 'diffCommentsSpy');
@@ -133,7 +130,7 @@
};
stubRestApi('getAccountDetails').returns(Promise.resolve(account));
const model = new CommentsModel(
- testResolver(routerModelToken),
+ testResolver(changeViewModelToken),
testResolver(changeModelToken),
testResolver(accountsModelToken),
getAppContext().restApiService,
@@ -161,7 +158,7 @@
};
stubRestApi('getAccountDetails').returns(Promise.resolve(account));
const model = new CommentsModel(
- testResolver(routerModelToken),
+ testResolver(changeViewModelToken),
testResolver(changeModelToken),
testResolver(accountsModelToken),
getAppContext().restApiService,
@@ -199,7 +196,7 @@
})
);
const model = new CommentsModel(
- testResolver(routerModelToken),
+ testResolver(changeViewModelToken),
testResolver(changeModelToken),
testResolver(accountsModelToken),
getAppContext().restApiService,
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 31d511a..a206037 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -10,6 +10,7 @@
BasePatchSetNum,
ChangeInfo,
PatchSetNumber,
+ EDIT,
} from '../../api/rest-api';
import {Tab} from '../../constants/constants';
import {GerritView} from '../../services/router/router-model';
@@ -26,18 +27,31 @@
import {Model} from '../model';
import {ViewState} from './base';
+export enum ChangeChildView {
+ OVERVIEW = 'OVERVIEW',
+ DIFF = 'DIFF',
+ EDIT = 'EDIT',
+}
+
export interface ChangeViewState extends ViewState {
view: GerritView.CHANGE;
+ childView: ChangeChildView;
changeNum: NumericChangeId;
repo: RepoName;
- edit?: boolean;
patchNum?: RevisionPatchSetNum;
basePatchNum?: BasePatchSetNum;
+ /** Refers to comment on COMMENTS tab in OVERVIEW. */
commentId?: UrlEncodedCommentId;
+
+ // TODO: Move properties that only apply to OVERVIEW into a submessage.
+
+ edit?: boolean;
/** This can be a string only for plugin provided tabs. */
tab?: Tab | string;
+ // TODO: Move properties that only apply to CHECKS tab into a submessage.
+
/** Checks related view state */
/** selected patchset for check runs (undefined=latest) */
@@ -61,6 +75,19 @@
forceReload?: boolean;
/** triggers opening the reply dialog */
openReplyDialog?: boolean;
+
+ /** These properties apply to the DIFF child view only. */
+ diffView?: {
+ path?: string;
+ lineNum?: number;
+ leftSide?: boolean;
+ };
+
+ /** These properties apply to the EDIT child view only. */
+ editView?: {
+ path?: string;
+ lineNum?: number;
+ };
}
/**
@@ -70,7 +97,7 @@
*/
export type CreateChangeUrlObject = Omit<
ChangeViewState,
- 'view' | 'changeNum' | 'repo'
+ 'view' | 'childView' | 'changeNum' | 'repo'
> & {
change: Pick<ChangeInfo, '_number' | 'project'>;
};
@@ -82,7 +109,9 @@
}
export function objToState(
- obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view'>
+ obj:
+ | (CreateChangeUrlObject & {childView: ChangeChildView})
+ | Omit<ChangeViewState, 'view'>
): ChangeViewState {
if (isCreateChangeUrlObject(obj)) {
return {
@@ -95,15 +124,26 @@
return {...obj, view: GerritView.CHANGE};
}
-export function createChangeUrl(
- obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view'>
-) {
- const state: ChangeViewState = objToState(obj);
- let range = getPatchRangeExpression(state);
- if (range.length) {
- range = '/' + range;
+export function createChangeViewUrl(state: ChangeViewState): string {
+ switch (state.childView) {
+ case ChangeChildView.OVERVIEW:
+ return createChangeUrl(state);
+ case ChangeChildView.DIFF:
+ return createDiffUrl(state);
+ case ChangeChildView.EDIT:
+ return createEditUrl(state);
}
- let suffix = `${range}`;
+}
+
+export function createChangeUrl(
+ obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view' | 'childView'>
+) {
+ const state: ChangeViewState = objToState({
+ ...obj,
+ childView: ChangeChildView.OVERVIEW,
+ });
+
+ let suffix = '';
const queries = [];
if (state.checksPatchset && state.checksPatchset > 0) {
queries.push(`checksPatchset=${state.checksPatchset}`);
@@ -136,7 +176,7 @@
suffix += ',edit';
}
if (state.commentId) {
- suffix = suffix + `/comments/${state.commentId}`;
+ suffix += `/comments/${state.commentId}`;
}
if (queries.length > 0) {
suffix += '?' + queries.join('&');
@@ -144,18 +184,99 @@
if (state.messageHash) {
suffix += state.messageHash;
}
- if (state.repo) {
- const encodedProject = encodeURL(state.repo, true);
- return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
- } else {
- return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
+
+ return `${createChangeUrlCommon(state)}${suffix}`;
+}
+
+export function createDiffUrl(
+ obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view' | 'childView'>
+) {
+ const state: ChangeViewState = objToState({
+ ...obj,
+ childView: ChangeChildView.DIFF,
+ });
+
+ const path = `/${encodeURL(state.diffView?.path ?? '', true)}`;
+
+ let suffix = '';
+ // TODO: Move creating of comment URLs to a separate function. We are
+ // "abusing" the `commentId` property, which should only be used for pointing
+ // to comment in the COMMENTS tab of the OVERVIEW page.
+ if (state.commentId) {
+ suffix += `comment/${state.commentId}/`;
}
+
+ if (state.diffView?.lineNum) {
+ suffix += '#';
+ if (state.diffView?.leftSide) {
+ suffix += 'b';
+ }
+ suffix += state.diffView.lineNum;
+ }
+
+ return `${createChangeUrlCommon(state)}${path}${suffix}`;
+}
+
+export function createEditUrl(
+ obj: Omit<ChangeViewState, 'view' | 'childView'>
+): string {
+ const state: ChangeViewState = objToState({
+ ...obj,
+ childView: ChangeChildView.DIFF,
+ patchNum: obj.patchNum ?? EDIT,
+ });
+
+ const path = `/${encodeURL(state.editView?.path ?? '', true)}`;
+ const line = state.editView?.lineNum;
+ const suffix = line ? `#${line}` : '';
+
+ return `${createChangeUrlCommon(state)}${path},edit${suffix}`;
+}
+
+/**
+ * The shared part of creating a change URL between OVERVIEW, DIFF and EDIT
+ * child views.
+ */
+function createChangeUrlCommon(state: ChangeViewState) {
+ let range = getPatchRangeExpression(state);
+ if (range.length) range = '/' + range;
+
+ let repo = '';
+ if (state.repo) repo = `${encodeURL(state.repo, true)}/+/`;
+
+ return `${getBaseUrl()}/c/${repo}${state.changeNum}${range}`;
}
export const changeViewModelToken =
define<ChangeViewModel>('change-view-model');
export class ChangeViewModel extends Model<ChangeViewState | undefined> {
+ public readonly changeNum$ = select(this.state$, state => state?.changeNum);
+
+ public readonly patchNum$ = select(this.state$, state => state?.patchNum);
+
+ public readonly basePatchNum$ = select(
+ this.state$,
+ state => state?.basePatchNum
+ );
+
+ public readonly diffPath$ = select(
+ this.state$,
+ state => state?.diffView?.path
+ );
+
+ public readonly diffLine$ = select(
+ this.state$,
+ state => state?.diffView?.lineNum
+ );
+
+ public readonly diffLeftSide$ = select(
+ this.state$,
+ state => state?.diffView?.leftSide ?? false
+ );
+
+ public readonly childView$ = select(this.state$, state => state?.childView);
+
public readonly tab$ = select(this.state$, state => state?.tab);
public readonly checksPatchset$ = select(
diff --git a/polygerrit-ui/app/models/views/change_test.ts b/polygerrit-ui/app/models/views/change_test.ts
index b34a1ba..837e362 100644
--- a/polygerrit-ui/app/models/views/change_test.ts
+++ b/polygerrit-ui/app/models/views/change_test.ts
@@ -6,73 +6,145 @@
import {assert} from '@open-wc/testing';
import {
BasePatchSetNum,
- NumericChangeId,
RepoName,
RevisionPatchSetNum,
} from '../../api/rest-api';
-import {GerritView} from '../../services/router/router-model';
import '../../test/common-test-setup';
-import {createChangeUrl, ChangeViewState} from './change';
-
-const STATE: ChangeViewState = {
- view: GerritView.CHANGE,
- changeNum: 1234 as NumericChangeId,
- repo: 'test' as RepoName,
-};
+import {
+ createChangeViewState,
+ createDiffViewState,
+ createEditViewState,
+} from '../../test/test-data-generators';
+import {
+ createChangeUrl,
+ createDiffUrl,
+ createEditUrl,
+ ChangeViewState,
+} from './change';
suite('change view state tests', () => {
test('createChangeUrl()', () => {
- const state: ChangeViewState = {...STATE};
+ const state: ChangeViewState = createChangeViewState();
- assert.equal(createChangeUrl(state), '/c/test/+/1234');
+ assert.equal(createChangeUrl(state), '/c/test-project/+/42');
state.patchNum = 10 as RevisionPatchSetNum;
- assert.equal(createChangeUrl(state), '/c/test/+/1234/10');
+ assert.equal(createChangeUrl(state), '/c/test-project/+/42/10');
state.basePatchNum = 5 as BasePatchSetNum;
- assert.equal(createChangeUrl(state), '/c/test/+/1234/5..10');
+ assert.equal(createChangeUrl(state), '/c/test-project/+/42/5..10');
state.messageHash = '#123';
- assert.equal(createChangeUrl(state), '/c/test/+/1234/5..10#123');
+ assert.equal(createChangeUrl(state), '/c/test-project/+/42/5..10#123');
});
test('createChangeUrl() baseUrl', () => {
window.CANONICAL_PATH = '/base';
- const state: ChangeViewState = {...STATE};
+ const state: ChangeViewState = createChangeViewState();
assert.equal(createChangeUrl(state).substring(0, 5), '/base');
window.CANONICAL_PATH = undefined;
});
test('createChangeUrl() checksRunsSelected', () => {
const state: ChangeViewState = {
- ...STATE,
+ ...createChangeViewState(),
checksRunsSelected: new Set(['asdf']),
};
assert.equal(
createChangeUrl(state),
- '/c/test/+/1234?checksRunsSelected=asdf'
+ '/c/test-project/+/42?checksRunsSelected=asdf'
);
});
test('createChangeUrl() checksResultsFilter', () => {
const state: ChangeViewState = {
- ...STATE,
+ ...createChangeViewState(),
checksResultsFilter: 'asdf.*qwer',
};
assert.equal(
createChangeUrl(state),
- '/c/test/+/1234?checksResultsFilter=asdf.*qwer'
+ '/c/test-project/+/42?checksResultsFilter=asdf.*qwer'
);
});
test('createChangeUrl() with repo name encoding', () => {
const state: ChangeViewState = {
- view: GerritView.CHANGE,
- changeNum: 1234 as NumericChangeId,
+ ...createChangeViewState(),
repo: 'x+/y+/z+/w' as RepoName,
};
- assert.equal(createChangeUrl(state), '/c/x%252B/y%252B/z%252B/w/+/1234');
+ assert.equal(createChangeUrl(state), '/c/x%252B/y%252B/z%252B/w/+/42');
+ });
+
+ test('createDiffUrl', () => {
+ const params: ChangeViewState = {
+ ...createDiffViewState(),
+ patchNum: 12 as RevisionPatchSetNum,
+ diffView: {path: 'x+y/path.cpp'},
+ };
+ assert.equal(
+ createDiffUrl(params),
+ '/c/test-project/+/42/12/x%252By/path.cpp'
+ );
+
+ window.CANONICAL_PATH = '/base';
+ assert.equal(createDiffUrl(params).substring(0, 5), '/base');
+ window.CANONICAL_PATH = undefined;
+
+ params.repo = 'test' as RepoName;
+ assert.equal(createDiffUrl(params), '/c/test/+/42/12/x%252By/path.cpp');
+
+ params.basePatchNum = 6 as BasePatchSetNum;
+ assert.equal(createDiffUrl(params), '/c/test/+/42/6..12/x%252By/path.cpp');
+
+ params.diffView = {
+ path: 'foo bar/my+file.txt%',
+ };
+ params.patchNum = 2 as RevisionPatchSetNum;
+ delete params.basePatchNum;
+ assert.equal(
+ createDiffUrl(params),
+ '/c/test/+/42/2/foo+bar/my%252Bfile.txt%2525'
+ );
+
+ params.diffView = {
+ path: 'file.cpp',
+ lineNum: 123,
+ };
+ assert.equal(createDiffUrl(params), '/c/test/+/42/2/file.cpp#123');
+
+ params.diffView = {
+ path: 'file.cpp',
+ lineNum: 123,
+ leftSide: true,
+ };
+ assert.equal(createDiffUrl(params), '/c/test/+/42/2/file.cpp#b123');
+ });
+
+ test('diff with repo name encoding', () => {
+ const params: ChangeViewState = {
+ ...createDiffViewState(),
+ patchNum: 12 as RevisionPatchSetNum,
+ repo: 'x+/y' as RepoName,
+ diffView: {path: 'x+y/path.cpp'},
+ };
+ assert.equal(createDiffUrl(params), '/c/x%252B/y/+/42/12/x%252By/path.cpp');
+ });
+
+ test('createEditUrl', () => {
+ const params: ChangeViewState = {
+ ...createEditViewState(),
+ patchNum: 12 as RevisionPatchSetNum,
+ editView: {path: 'x+y/path.cpp' as RepoName, lineNum: 31},
+ };
+ assert.equal(
+ createEditUrl(params),
+ '/c/test-project/+/42/12/x%252By/path.cpp,edit#31'
+ );
+
+ window.CANONICAL_PATH = '/base';
+ assert.equal(createEditUrl(params).substring(0, 5), '/base');
+ window.CANONICAL_PATH = undefined;
});
});
diff --git a/polygerrit-ui/app/models/views/diff.ts b/polygerrit-ui/app/models/views/diff.ts
deleted file mode 100644
index 34f4ee7..0000000
--- a/polygerrit-ui/app/models/views/diff.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {
- NumericChangeId,
- RepoName,
- RevisionPatchSetNum,
- BasePatchSetNum,
- ChangeInfo,
-} from '../../api/rest-api';
-import {GerritView} from '../../services/router/router-model';
-import {UrlEncodedCommentId} from '../../types/common';
-import {
- encodeURL,
- getBaseUrl,
- getPatchRangeExpression,
-} from '../../utils/url-util';
-import {define} from '../dependency';
-import {Model} from '../model';
-import {ViewState} from './base';
-
-export interface DiffViewState extends ViewState {
- view: GerritView.DIFF;
- changeNum: NumericChangeId;
- repo?: RepoName;
- commentId?: UrlEncodedCommentId;
- path?: string;
- patchNum?: RevisionPatchSetNum;
- basePatchNum?: BasePatchSetNum;
- lineNum?: number;
- leftSide?: boolean;
- commentLink?: boolean;
-}
-
-/**
- * This is a convenience type such that you can pass a `ChangeInfo` object
- * as the `change` property instead of having to set both the `changeNum` and
- * `project` properties explicitly.
- */
-export type CreateChangeUrlObject = Omit<
- DiffViewState,
- 'view' | 'changeNum' | 'project'
-> & {
- change: Pick<ChangeInfo, '_number' | 'project'>;
-};
-
-export function isCreateChangeUrlObject(
- state: CreateChangeUrlObject | Omit<DiffViewState, 'view'>
-): state is CreateChangeUrlObject {
- return !!(state as CreateChangeUrlObject).change;
-}
-
-export function objToState(
- obj: CreateChangeUrlObject | Omit<DiffViewState, 'view'>
-): DiffViewState {
- if (isCreateChangeUrlObject(obj)) {
- return {
- ...obj,
- view: GerritView.DIFF,
- changeNum: obj.change._number,
- repo: obj.change.project,
- };
- }
- return {...obj, view: GerritView.DIFF};
-}
-
-export function createDiffUrl(
- obj: CreateChangeUrlObject | Omit<DiffViewState, 'view'>
-) {
- const state: DiffViewState = objToState(obj);
- let range = getPatchRangeExpression(state);
- if (range.length) range = '/' + range;
-
- let suffix = `${range}/${encodeURL(state.path || '', true)}`;
-
- if (state.lineNum) {
- suffix += '#';
- if (state.leftSide) {
- suffix += 'b';
- }
- suffix += state.lineNum;
- }
-
- if (state.commentId) {
- suffix = `/comment/${state.commentId}` + suffix;
- }
-
- if (state.repo) {
- const encodedProject = encodeURL(state.repo, true);
- return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
- } else {
- return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
- }
-}
-
-export const diffViewModelToken = define<DiffViewModel>('diff-view-model');
-
-export class DiffViewModel extends Model<DiffViewState | undefined> {
- constructor() {
- super(undefined);
- }
-}
diff --git a/polygerrit-ui/app/models/views/diff_test.ts b/polygerrit-ui/app/models/views/diff_test.ts
deleted file mode 100644
index 7fab2a4..0000000
--- a/polygerrit-ui/app/models/views/diff_test.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {assert} from '@open-wc/testing';
-import {
- BasePatchSetNum,
- NumericChangeId,
- RepoName,
- RevisionPatchSetNum,
-} from '../../api/rest-api';
-import {GerritView} from '../../services/router/router-model';
-import '../../test/common-test-setup';
-import {createDiffUrl, DiffViewState} from './diff';
-
-suite('diff view state tests', () => {
- test('createDiffUrl', () => {
- const params: DiffViewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- path: 'x+y/path.cpp' as RepoName,
- patchNum: 12 as RevisionPatchSetNum,
- repo: '' as RepoName,
- };
- assert.equal(createDiffUrl(params), '/c/42/12/x%252By/path.cpp');
-
- window.CANONICAL_PATH = '/base';
- assert.equal(createDiffUrl(params).substring(0, 5), '/base');
- window.CANONICAL_PATH = undefined;
-
- params.repo = 'test' as RepoName;
- assert.equal(createDiffUrl(params), '/c/test/+/42/12/x%252By/path.cpp');
-
- params.basePatchNum = 6 as BasePatchSetNum;
- assert.equal(createDiffUrl(params), '/c/test/+/42/6..12/x%252By/path.cpp');
-
- params.path = 'foo bar/my+file.txt%';
- params.patchNum = 2 as RevisionPatchSetNum;
- delete params.basePatchNum;
- assert.equal(
- createDiffUrl(params),
- '/c/test/+/42/2/foo+bar/my%252Bfile.txt%2525'
- );
-
- params.path = 'file.cpp';
- params.lineNum = 123;
- assert.equal(createDiffUrl(params), '/c/test/+/42/2/file.cpp#123');
-
- params.leftSide = true;
- assert.equal(createDiffUrl(params), '/c/test/+/42/2/file.cpp#b123');
- });
-
- test('diff with repo name encoding', () => {
- const params: DiffViewState = {
- view: GerritView.DIFF,
- changeNum: 42 as NumericChangeId,
- path: 'x+y/path.cpp',
- patchNum: 12 as RevisionPatchSetNum,
- repo: 'x+/y' as RepoName,
- };
- assert.equal(createDiffUrl(params), '/c/x%252B/y/+/42/12/x%252By/path.cpp');
- });
-});
diff --git a/polygerrit-ui/app/models/views/edit.ts b/polygerrit-ui/app/models/views/edit.ts
deleted file mode 100644
index 3893576..0000000
--- a/polygerrit-ui/app/models/views/edit.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {
- EDIT,
- NumericChangeId,
- RepoName,
- RevisionPatchSetNum,
-} from '../../api/rest-api';
-import {GerritView} from '../../services/router/router-model';
-import {
- encodeURL,
- getBaseUrl,
- getPatchRangeExpression,
-} from '../../utils/url-util';
-import {define} from '../dependency';
-import {Model} from '../model';
-import {ViewState} from './base';
-
-export interface EditViewState extends ViewState {
- view: GerritView.EDIT;
- changeNum: NumericChangeId;
- repo: RepoName;
- path: string;
- patchNum: RevisionPatchSetNum;
- lineNum?: number;
-}
-
-export function createEditUrl(state: Omit<EditViewState, 'view'>): string {
- if (state.patchNum === undefined) {
- state = {...state, patchNum: EDIT};
- }
- let range = getPatchRangeExpression(state);
- if (range.length) range = '/' + range;
-
- let suffix = `${range}/${encodeURL(state.path || '', true)}`;
- suffix += ',edit';
-
- if (state.lineNum) {
- suffix += '#';
- suffix += state.lineNum;
- }
-
- if (state.repo) {
- const encodedProject = encodeURL(state.repo, true);
- return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
- } else {
- return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
- }
-}
-
-export const editViewModelToken = define<EditViewModel>('edit-view-model');
-
-export class EditViewModel extends Model<EditViewState | undefined> {
- constructor() {
- super(undefined);
- }
-}
diff --git a/polygerrit-ui/app/models/views/edit_test.ts b/polygerrit-ui/app/models/views/edit_test.ts
deleted file mode 100644
index 00bc805..0000000
--- a/polygerrit-ui/app/models/views/edit_test.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {assert} from '@open-wc/testing';
-import {
- NumericChangeId,
- RepoName,
- RevisionPatchSetNum,
-} from '../../api/rest-api';
-import {GerritView} from '../../services/router/router-model';
-import '../../test/common-test-setup';
-import {createEditUrl, EditViewState} from './edit';
-
-suite('edit view state tests', () => {
- test('createEditUrl', () => {
- const params: EditViewState = {
- view: GerritView.EDIT,
- changeNum: 42 as NumericChangeId,
- repo: 'test-project' as RepoName,
- path: 'x+y/path.cpp' as RepoName,
- patchNum: 12 as RevisionPatchSetNum,
- lineNum: 31,
- };
- assert.equal(
- createEditUrl(params),
- '/c/test-project/+/42/12/x%252By/path.cpp,edit#31'
- );
-
- window.CANONICAL_PATH = '/base';
- assert.equal(createEditUrl(params).substring(0, 5), '/base');
- window.CANONICAL_PATH = undefined;
- });
-});
diff --git a/polygerrit-ui/app/services/app-context-init.ts b/polygerrit-ui/app/services/app-context-init.ts
index 1857bad..14fb253 100644
--- a/polygerrit-ui/app/services/app-context-init.ts
+++ b/polygerrit-ui/app/services/app-context-init.ts
@@ -50,12 +50,10 @@
agreementViewModelToken,
} from '../models/views/agreement';
import {ChangeViewModel, changeViewModelToken} from '../models/views/change';
-import {DiffViewModel, diffViewModelToken} from '../models/views/diff';
import {
DocumentationViewModel,
documentationViewModelToken,
} from '../models/views/documentation';
-import {EditViewModel, editViewModelToken} from '../models/views/edit';
import {GroupViewModel, groupViewModelToken} from '../models/views/group';
import {PluginViewModel, pluginViewModelToken} from '../models/views/plugin';
import {RepoViewModel, repoViewModelToken} from '../models/views/repo';
@@ -112,9 +110,7 @@
[agreementViewModelToken, () => new AgreementViewModel()],
[changeViewModelToken, () => new ChangeViewModel()],
[dashboardViewModelToken, () => new DashboardViewModel()],
- [diffViewModelToken, () => new DiffViewModel()],
[documentationViewModelToken, () => new DocumentationViewModel()],
- [editViewModelToken, () => new EditViewModel()],
[groupViewModelToken, () => new GroupViewModel()],
[pluginViewModelToken, () => new PluginViewModel()],
[repoViewModelToken, () => new RepoViewModel()],
@@ -139,9 +135,7 @@
resolver(agreementViewModelToken),
resolver(changeViewModelToken),
resolver(dashboardViewModelToken),
- resolver(diffViewModelToken),
resolver(documentationViewModelToken),
- resolver(editViewModelToken),
resolver(groupViewModelToken),
resolver(pluginViewModelToken),
resolver(repoViewModelToken),
@@ -154,7 +148,8 @@
changeModelToken,
() =>
new ChangeModel(
- resolver(routerModelToken),
+ resolver(navigationToken),
+ resolver(changeViewModelToken),
appContext.restApiService,
resolver(userModelToken)
),
@@ -163,7 +158,7 @@
commentsModelToken,
() =>
new CommentsModel(
- resolver(routerModelToken),
+ resolver(changeViewModelToken),
resolver(changeModelToken),
resolver(accountsModelToken),
appContext.restApiService,
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 572e107..2a5dff2 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -22,4 +22,5 @@
SUGGEST_EDIT = 'UiFeature__suggest_edit',
MENTION_USERS = 'UiFeature__mention_users',
RENDER_MARKDOWN = 'UiFeature__render_markdown',
+ REBASE_CHAIN = 'UiFeature__rebase_chain',
}
diff --git a/polygerrit-ui/app/services/router/router-model.ts b/polygerrit-ui/app/services/router/router-model.ts
index edde7a4..c3c1cb6 100644
--- a/polygerrit-ui/app/services/router/router-model.ts
+++ b/polygerrit-ui/app/services/router/router-model.ts
@@ -4,11 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Observable} from 'rxjs';
-import {
- NumericChangeId,
- RevisionPatchSetNum,
- BasePatchSetNum,
-} from '../../types/common';
import {Model} from '../../models/model';
import {select} from '../../utils/observable-util';
import {define} from '../../models/dependency';
@@ -18,9 +13,7 @@
AGREEMENTS = 'agreements',
CHANGE = 'change',
DASHBOARD = 'dashboard',
- DIFF = 'diff',
DOCUMENTATION_SEARCH = 'documentation-search',
- EDIT = 'edit',
GROUP = 'group',
PLUGIN_SCREEN = 'plugin-screen',
REPO = 'repo',
@@ -31,9 +24,6 @@
export interface RouterState {
// Note that this router model view must be updated before view model state.
view?: GerritView;
- changeNum?: NumericChangeId;
- patchNum?: RevisionPatchSetNum;
- basePatchNum?: BasePatchSetNum;
}
export const routerModelToken = define<RouterModel>('router-model');
@@ -43,17 +33,6 @@
state => state.view
);
- readonly routerChangeNum$: Observable<NumericChangeId | undefined> = select(
- this.state$,
- state => state.changeNum
- );
-
- readonly routerPatchNum$: Observable<RevisionPatchSetNum | undefined> =
- select(this.state$, state => state.patchNum);
-
- readonly routerBasePatchNum$: Observable<BasePatchSetNum | undefined> =
- select(this.state$, state => state.basePatchNum);
-
constructor() {
super({});
}
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index 23a5794..f0a4cbe 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -105,7 +105,6 @@
import {EditRevisionInfo, ParsedChangeInfo} from '../types/types';
import {
DetailedLabelInfo,
- FileInfo,
QuickLabelInfo,
SubmitRequirementExpressionInfo,
SubmitRequirementResultInfo,
@@ -115,8 +114,8 @@
import {Category, RunStatus} from '../api/checks';
import {DiffInfo} from '../api/diff';
import {SearchViewState} from '../models/views/search';
-import {ChangeViewState} from '../models/views/change';
-import {EditViewState} from '../models/views/edit';
+import {ChangeChildView, ChangeViewState} from '../models/views/change';
+import {NormalizedFileInfo} from '../models/change/files-model';
const TEST_DEFAULT_EXPRESSION = 'label:Verified=MAX -label:Verified=MIN';
export const TEST_PROJECT_NAME: RepoName = 'test-project' as RepoName;
@@ -400,10 +399,15 @@
return messages;
}
-export function createFileInfo(): FileInfo {
+export function createFileInfo(
+ path = 'test-path/test-file.txt'
+): NormalizedFileInfo {
return {
size: 314,
size_delta: 7,
+ lines_deleted: 0,
+ lines_inserted: 0,
+ __path: path,
};
}
@@ -701,6 +705,7 @@
export function createChangeViewState(): ChangeViewState {
return {
view: GerritView.CHANGE,
+ childView: ChangeChildView.OVERVIEW,
changeNum: TEST_NUMERIC_CHANGE_ID,
repo: TEST_PROJECT_NAME,
};
@@ -716,12 +721,22 @@
};
}
-export function createEditViewState(): EditViewState {
+export function createEditViewState(): ChangeViewState {
return {
- view: GerritView.EDIT,
+ view: GerritView.CHANGE,
+ childView: ChangeChildView.EDIT,
changeNum: TEST_NUMERIC_CHANGE_ID,
patchNum: EDIT,
- path: 'foo/bar.baz',
+ repo: TEST_PROJECT_NAME,
+ editView: {path: 'foo/bar.baz'},
+ };
+}
+
+export function createDiffViewState(): ChangeViewState {
+ return {
+ view: GerritView.CHANGE,
+ childView: ChangeChildView.DIFF,
+ changeNum: TEST_NUMERIC_CHANGE_ID,
repo: TEST_PROJECT_NAME,
};
}
diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts
index 8d55e36..557e3a0 100644
--- a/polygerrit-ui/app/types/types.ts
+++ b/polygerrit-ui/app/types/types.ts
@@ -10,7 +10,6 @@
AccountInfo,
BasePatchSetNum,
ChangeViewChangeInfo,
- CommitId,
CommitInfo,
EditPatchSet,
PatchSetNum,
@@ -28,11 +27,6 @@
requestAvailability(): void;
}
-export interface CommitRange {
- baseCommit: CommitId;
- commit: CommitId;
-}
-
export type {CoverageRange} from '../api/diff';
export {CoverageType} from '../api/diff';
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index 8af5beb..a92f0f8 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -598,3 +598,17 @@
.includes(account.email)
);
}
+
+export function findComment(
+ comments: {
+ [path: string]: (CommentInfo | DraftInfo)[];
+ },
+ commentId: UrlEncodedCommentId
+) {
+ if (!commentId) return undefined;
+ let comment;
+ for (const path of Object.keys(comments)) {
+ comment = comment || comments[path].find(c => c.id === commentId);
+ }
+ return comment;
+}
diff --git a/polygerrit-ui/app/utils/path-list-util.ts b/polygerrit-ui/app/utils/path-list-util.ts
index 1116123..b007d47 100644
--- a/polygerrit-ui/app/utils/path-list-util.ts
+++ b/polygerrit-ui/app/utils/path-list-util.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {SpecialFilePath, FileInfoStatus} from '../constants/constants';
-import {FileInfo} from '../types/common';
+import {FileInfo, FileNameToFileInfoMap} from '../types/common';
import {hasOwnProperty} from './common-util';
export function specialFilePathCompare(a: string, b: string) {
@@ -55,7 +55,7 @@
// In case there are files with comments on them but they are unchanged, then
// we explicitly displays the file to render the comments with Unchanged status
export function addUnmodifiedFiles(
- files: {[filename: string]: FileInfo},
+ files: FileNameToFileInfoMap,
commentedPaths: {[fileName: string]: boolean}
) {
if (!commentedPaths) return;
diff --git a/polygerrit-ui/app/utils/path-list-util_test.ts b/polygerrit-ui/app/utils/path-list-util_test.ts
index 50f5c0e..3c9e0d3 100644
--- a/polygerrit-ui/app/utils/path-list-util_test.ts
+++ b/polygerrit-ui/app/utils/path-list-util_test.ts
@@ -12,9 +12,9 @@
specialFilePathCompare,
truncatePath,
} from './path-list-util';
-import {FileInfo} from '../api/rest-api';
import {hasOwnProperty} from './common-util';
import {assert} from '@open-wc/testing';
+import {FileNameToFileInfoMap} from '../types/common';
suite('path-list-utl tests', () => {
test('special sort', () => {
@@ -117,7 +117,7 @@
'file1.txt': true,
};
- const files: {[filename: string]: FileInfo} = {
+ const files: FileNameToFileInfoMap = {
'file2.txt': {
status: FileInfoStatus.REWRITTEN,
size_delta: 10,
diff --git a/resources/com/google/gerrit/server/mime/mime-types.properties b/resources/com/google/gerrit/server/mime/mime-types.properties
index 2f9561b..8e97ba7 100644
--- a/resources/com/google/gerrit/server/mime/mime-types.properties
+++ b/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -39,6 +39,7 @@
cpp = text/x-c++src
cql = text/x-cassandra
cxx = text/x-c++src
+cu = text/x-c++src
cyp = application/x-cypher-query
cypher = application/x-cypher-query
c++ = text/x-c++src