Merge "Do not wrap the header title"
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 6498d1b..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,19 +301,20 @@
@Override
public Set<AccountGroup.UUID> guessRelevantGroupUUIDs() {
try (Timer0.Context ignored = guessRelevantGroupsLatency.start()) {
+ 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(
- 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());
+ Streams.concat(configuredRelevantGroups, guessedRelevantGroups).collect(toSet());
logger.atFine().log("relevant group UUIDs: %s", relevantGroupUuids);
return relevantGroupUuids;
}
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index a8ba052..22eb32c 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/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
index 804723b..3531d1c 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -166,6 +166,202 @@
}
@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 = 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.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
public void voteOnBehalfOfRequiresLabel() throws Exception {
allowCodeReviewOnBehalfOf();
PushOneCommit.Result r = createChange();
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 0b1bd80..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';
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 2624677..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';
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 c7473ca..a54a565 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
@@ -176,9 +176,9 @@
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';
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/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 08aecb9..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';
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 9d2e214..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.
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 ac54d35..bcf6937 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -70,6 +70,7 @@
ChangeViewModel,
ChangeViewState,
createChangeViewUrl,
+ createDiffUrl,
} from '../../../models/views/change';
import {
DashboardViewModel,
@@ -97,7 +98,6 @@
getPatchRangeForCommentUrl,
isInBaseOfPatchRange,
} from '../../../utils/comment-util';
-import {createDiffUrl} from '../../../models/views/diff';
import {isFileUnchanged} from '../../../embed/diff/gr-diff/gr-diff-utils';
const RoutePattern = {
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 65a5a23..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
@@ -84,8 +84,8 @@
import {sharedStyles} from '../../../styles/shared-styles';
import {ifDefined} from 'lit/directives/if-defined.js';
import {when} from 'lit/directives/when.js';
-import {createDiffUrl} from '../../../models/views/diff';
import {
+ createDiffUrl,
ChangeChildView,
changeViewModelToken,
} from '../../../models/views/change';
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 84413c8..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';
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 4467f58..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';
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/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index 2bde847..446822f 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -38,10 +38,13 @@
import {UserModel} from '../user/user-model';
import {define} from '../dependency';
import {isOwner} from '../../utils/change-util';
-import {ChangeViewModel, createChangeUrl} from '../views/change';
-import {createDiffUrl} from '../views/diff';
+import {
+ ChangeViewModel,
+ createChangeUrl,
+ createDiffUrl,
+ createEditUrl,
+} from '../views/change';
import {NavigationService} from '../../elements/core/gr-navigation/gr-navigation';
-import {createEditUrl} from '../views/edit';
export enum LoadingStatus {
NOT_LOADED = 'NOT_LOADED',
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index e3570d1..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';
@@ -25,8 +26,6 @@
import {define} from '../dependency';
import {Model} from '../model';
import {ViewState} from './base';
-import {createDiffUrl} from './diff';
-import {createEditUrl} from './edit';
export enum ChangeChildView {
OVERVIEW = 'OVERVIEW',
@@ -143,11 +142,8 @@
...obj,
childView: ChangeChildView.OVERVIEW,
});
- let range = getPatchRangeExpression(state);
- if (range.length) {
- range = '/' + range;
- }
- let suffix = `${range}`;
+
+ let suffix = '';
const queries = [];
if (state.checksPatchset && state.checksPatchset > 0) {
queries.push(`checksPatchset=${state.checksPatchset}`);
@@ -180,7 +176,7 @@
suffix += ',edit';
}
if (state.commentId) {
- suffix = suffix + `/comments/${state.commentId}`;
+ suffix += `/comments/${state.commentId}`;
}
if (queries.length > 0) {
suffix += '?' + queries.join('&');
@@ -188,12 +184,67 @@
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 =
diff --git a/polygerrit-ui/app/models/views/change_test.ts b/polygerrit-ui/app/models/views/change_test.ts
index ca6f104..837e362 100644
--- a/polygerrit-ui/app/models/views/change_test.ts
+++ b/polygerrit-ui/app/models/views/change_test.ts
@@ -10,8 +10,17 @@
RevisionPatchSetNum,
} from '../../api/rest-api';
import '../../test/common-test-setup';
-import {createChangeViewState} from '../../test/test-data-generators';
-import {createChangeUrl, ChangeViewState} from './change';
+import {
+ createChangeViewState,
+ createDiffViewState,
+ createEditViewState,
+} from '../../test/test-data-generators';
+import {
+ createChangeUrl,
+ createDiffUrl,
+ createEditUrl,
+ ChangeViewState,
+} from './change';
suite('change view state tests', () => {
test('createChangeUrl()', () => {
@@ -67,4 +76,75 @@
};
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 961c9d5..0000000
--- a/polygerrit-ui/app/models/views/diff.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {
- encodeURL,
- getBaseUrl,
- getPatchRangeExpression,
-} from '../../utils/url-util';
-import {
- ChangeChildView,
- ChangeViewState,
- CreateChangeUrlObject,
- objToState,
-} from './change';
-
-// TODO: Move to change.ts.
-export function createDiffUrl(
- obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view' | 'childView'>
-) {
- const state: ChangeViewState = objToState({
- ...obj,
- childView: ChangeChildView.DIFF,
- });
- let range = getPatchRangeExpression(state);
- if (range.length) range = '/' + range;
-
- let suffix = `${range}/${encodeURL(state.diffView?.path ?? '', true)}`;
-
- if (state.diffView?.lineNum) {
- suffix += '#';
- if (state.diffView?.leftSide) {
- suffix += 'b';
- }
- suffix += state.diffView.lineNum;
- }
-
- // 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}` + suffix;
- }
-
- if (state.repo) {
- const encodedProject = encodeURL(state.repo, true);
- return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
- } else {
- return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
- }
-}
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 851bed7..0000000
--- a/polygerrit-ui/app/models/views/diff_test.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {assert} from '@open-wc/testing';
-import {
- BasePatchSetNum,
- RepoName,
- RevisionPatchSetNum,
-} from '../../api/rest-api';
-import '../../test/common-test-setup';
-import {createDiffViewState} from '../../test/test-data-generators';
-import {ChangeViewState} from './change';
-import {createDiffUrl} from './diff';
-
-suite('diff view state tests', () => {
- 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');
- });
-});
diff --git a/polygerrit-ui/app/models/views/edit.ts b/polygerrit-ui/app/models/views/edit.ts
deleted file mode 100644
index a12cd85..0000000
--- a/polygerrit-ui/app/models/views/edit.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {EDIT} from '../../api/rest-api';
-import {
- encodeURL,
- getBaseUrl,
- getPatchRangeExpression,
-} from '../../utils/url-util';
-import {ChangeViewState} from './change';
-
-// TODO: Move to change.ts.
-export function createEditUrl(
- state: Omit<ChangeViewState, 'view' | 'childView'>
-): string {
- if (state.patchNum === undefined) {
- state = {...state, patchNum: EDIT};
- }
- let range = getPatchRangeExpression(state);
- if (range.length) range = '/' + range;
-
- let suffix = `${range}/${encodeURL(state.editView?.path ?? '', true)}`;
- suffix += ',edit';
-
- if (state.editView?.lineNum) {
- suffix += '#';
- suffix += state.editView.lineNum;
- }
-
- if (state.repo) {
- const encodedProject = encodeURL(state.repo, true);
- return `${getBaseUrl()}/c/${encodedProject}/+/${state.changeNum}${suffix}`;
- } else {
- return `${getBaseUrl()}/c/${state.changeNum}${suffix}`;
- }
-}
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 be8fb70..0000000
--- a/polygerrit-ui/app/models/views/edit_test.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {assert} from '@open-wc/testing';
-import {RepoName, RevisionPatchSetNum} from '../../api/rest-api';
-import '../../test/common-test-setup';
-import {createEditViewState} from '../../test/test-data-generators';
-import {ChangeViewState} from './change';
-import {createEditUrl} from './edit';
-
-suite('edit view state tests', () => {
- 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/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',
}