Merge "Add support for using diff3 for rebasing and cherry-pick with conflicts"
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index 6c77109..af944cf 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -3,7 +3,8 @@
[WARNING]
Prolog rules are no longer supported in Gerrit. Existing usages of prolog rules
-can be modified or deleted, but uploading new "rules.pl" files are rejected.
+can be modified or deleted. Uploading new "rules.pl" files will result in
+a warning being emitted.
Please use link:config-submit-requirements.html[submit requirements] instead.
Note that the link:#SubmitType[Submit Type] being deprecated in this
documentation page currently has no substitution in submit requirements.
diff --git a/java/com/google/gerrit/server/account/HashedPassword.java b/java/com/google/gerrit/server/account/HashedPassword.java
index 0911550..7a7c35b 100644
--- a/java/com/google/gerrit/server/account/HashedPassword.java
+++ b/java/com/google/gerrit/server/account/HashedPassword.java
@@ -136,6 +136,6 @@
public boolean checkPassword(String password) {
// Constant-time comparison, because we're paranoid.
- return Arrays.areEqual(hashPassword(password, salt, cost, nullTerminate), hashed);
+ return Arrays.constantTimeAreEqual(hashPassword(password, salt, cost, nullTerminate), hashed);
}
}
diff --git a/java/com/google/gerrit/server/data/PatchSetAttribute.java b/java/com/google/gerrit/server/data/PatchSetAttribute.java
index dc47057..3f7c8e4 100644
--- a/java/com/google/gerrit/server/data/PatchSetAttribute.java
+++ b/java/com/google/gerrit/server/data/PatchSetAttribute.java
@@ -17,7 +17,7 @@
import com.google.gerrit.extensions.client.ChangeKind;
import java.util.List;
-public class PatchSetAttribute {
+public class PatchSetAttribute implements Cloneable {
public int number;
public String revision;
public List<String> parents;
@@ -32,4 +32,12 @@
public List<PatchAttribute> files;
public int sizeInsertions;
public int sizeDeletions;
+
+ public PatchSetAttribute shallowClone() {
+ try {
+ return (PatchSetAttribute) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 2827f59..ac69120 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -76,6 +76,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -358,33 +359,23 @@
public void addPatchSets(
RevWalk revWalk,
+ Config repoConfig,
ChangeAttribute ca,
- Collection<PatchSet> ps,
- Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
- LabelTypes labelTypes,
- AccountAttributeLoader accountLoader) {
- addPatchSets(revWalk, ca, ps, approvals, false, null, labelTypes, accountLoader);
- }
-
- public void addPatchSets(
- RevWalk revWalk,
- ChangeAttribute ca,
- Collection<PatchSet> ps,
Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
boolean includeFiles,
- Change change,
- LabelTypes labelTypes,
+ ChangeData changeData,
AccountAttributeLoader accountLoader) {
- if (!ps.isEmpty()) {
- ca.patchSets = new ArrayList<>(ps.size());
- for (PatchSet p : ps) {
- PatchSetAttribute psa = asPatchSetAttribute(revWalk, change, p, accountLoader);
+ if (!changeData.patchSets().isEmpty()) {
+ ca.patchSets = new ArrayList<>(changeData.patchSets().size());
+ for (PatchSet p : changeData.patchSets()) {
+ PatchSetAttribute psa =
+ asPatchSetAttribute(revWalk, repoConfig, changeData, p, accountLoader);
if (approvals != null) {
- addApprovals(psa, p.id(), approvals, labelTypes, accountLoader);
+ addApprovals(psa, p.id(), approvals, changeData.getLabelTypes(), accountLoader);
}
ca.patchSets.add(psa);
if (includeFiles) {
- addPatchSetFileNames(psa, change, p);
+ addPatchSetFileNames(psa, changeData.change(), p);
}
}
}
@@ -441,13 +432,18 @@
}
}
- public PatchSetAttribute asPatchSetAttribute(RevWalk revWalk, Change change, PatchSet patchSet) {
- return asPatchSetAttribute(revWalk, change, patchSet, null);
+ public PatchSetAttribute asPatchSetAttribute(
+ RevWalk revWalk, Config repoConfig, ChangeData changeData, PatchSet patchSet) {
+ return asPatchSetAttribute(revWalk, repoConfig, changeData, patchSet, null);
}
/** Create a PatchSetAttribute for the given patchset suitable for serialization to JSON. */
public PatchSetAttribute asPatchSetAttribute(
- RevWalk revWalk, Change change, PatchSet patchSet, AccountAttributeLoader accountLoader) {
+ RevWalk revWalk,
+ Config repoConfig,
+ ChangeData changeData,
+ PatchSet patchSet,
+ AccountAttributeLoader accountLoader) {
PatchSetAttribute p = new PatchSetAttribute();
p.revision = patchSet.commitId().name();
p.number = patchSet.number();
@@ -474,12 +470,12 @@
Map<String, FileDiffOutput> modifiedFiles =
diffOperations.listModifiedFilesAgainstParent(
- change.getProject(), patchSet.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
+ changeData.project(), patchSet.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
for (FileDiffOutput fileDiff : modifiedFiles.values()) {
p.sizeDeletions += fileDiff.deletions();
p.sizeInsertions += fileDiff.insertions();
}
- p.kind = changeKindCache.getChangeKind(change, patchSet);
+ p.kind = changeKindCache.getChangeKind(revWalk, repoConfig, changeData, patchSet);
} catch (IOException | StorageException e) {
logger.atSevere().withCause(e).log("Cannot load patch set data for %s", patchSet.id());
} catch (DiffNotAvailableException e) {
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 89aebde..66e894c 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -65,6 +65,7 @@
import com.google.gerrit.server.plugincontext.PluginItemContext;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -149,6 +150,7 @@
private final PatchSetUtil psUtil;
private final ChangeNotes.Factory changeNotesFactory;
private final boolean enableDraftCommentEvents;
+ private final ChangeData.Factory changeDataFactory;
private final String gerritInstanceId;
@@ -161,7 +163,8 @@
PatchSetUtil psUtil,
ChangeNotes.Factory changeNotesFactory,
@GerritServerConfig Config config,
- @Nullable @GerritInstanceId String gerritInstanceId) {
+ @Nullable @GerritInstanceId String gerritInstanceId,
+ ChangeData.Factory changeDataFactory) {
this.dispatcher = dispatcher;
this.eventFactory = eventFactory;
this.projectCache = projectCache;
@@ -171,6 +174,7 @@
this.enableDraftCommentEvents =
config.getBoolean("event", "stream-events", "enableDraftCommentEvents", false);
this.gerritInstanceId = gerritInstanceId;
+ this.changeDataFactory = changeDataFactory;
}
private ChangeNotes getNotes(ChangeInfo info) {
@@ -206,12 +210,13 @@
}
private Supplier<PatchSetAttribute> patchSetAttributeSupplier(
- final Change change, PatchSet patchSet) {
+ final ChangeData changeData, PatchSet patchSet) {
return Suppliers.memoize(
() -> {
- try (Repository repo = repoManager.openRepository(change.getProject());
+ try (Repository repo = repoManager.openRepository(changeData.change().getProject());
RevWalk revWalk = new RevWalk(repo)) {
- return eventFactory.asPatchSetAttribute(revWalk, change, patchSet);
+ return eventFactory.asPatchSetAttribute(
+ revWalk, repo.getConfig(), changeData, patchSet);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -301,7 +306,7 @@
PatchSetCreatedEvent event = new PatchSetCreatedEvent(change);
event.change = changeAttributeSupplier(change, notes);
- event.patchSet = patchSetAttributeSupplier(change, patchSet);
+ event.patchSet = patchSetAttributeSupplier(changeDataFactory.create(notes), patchSet);
event.uploader = accountAttributeSupplier(ev.getWho());
dispatcher.run(d -> d.postEvent(change, event));
@@ -317,7 +322,8 @@
Change change = notes.getChange();
ReviewerDeletedEvent event = new ReviewerDeletedEvent(change);
event.change = changeAttributeSupplier(change, notes);
- event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
+ event.patchSet =
+ patchSetAttributeSupplier(changeDataFactory.create(notes), psUtil.current(notes));
event.reviewer = accountAttributeSupplier(ev.getReviewer());
event.remover = accountAttributeSupplier(ev.getWho());
event.comment = ev.getComment();
@@ -338,7 +344,8 @@
ReviewerAddedEvent event = new ReviewerAddedEvent(change);
event.change = changeAttributeSupplier(change, notes);
- event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
+ event.patchSet =
+ patchSetAttributeSupplier(changeDataFactory.create(notes), psUtil.current(notes));
event.adder = accountAttributeSupplier(ev.getWho());
for (AccountInfo reviewer : ev.getReviewers()) {
event.reviewer = accountAttributeSupplier(reviewer);
@@ -466,7 +473,7 @@
event.change = changeAttributeSupplier(change, notes);
event.author = accountAttributeSupplier(ev.getWho());
- event.patchSet = patchSetAttributeSupplier(change, ps);
+ event.patchSet = patchSetAttributeSupplier(changeDataFactory.create(notes), ps);
event.comment = ev.getComment();
event.approvals = approvalsAttributeSupplier(change, ev.getApprovals(), ev.getOldApprovals());
@@ -485,7 +492,8 @@
event.change = changeAttributeSupplier(change, notes);
event.restorer = accountAttributeSupplier(ev.getWho());
- event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
+ event.patchSet =
+ patchSetAttributeSupplier(changeDataFactory.create(notes), psUtil.current(notes));
event.reason = ev.getReason();
dispatcher.run(d -> d.postEvent(change, event));
@@ -503,7 +511,8 @@
event.change = changeAttributeSupplier(change, notes);
event.submitter = accountAttributeSupplier(ev.getWho());
- event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
+ event.patchSet =
+ patchSetAttributeSupplier(changeDataFactory.create(notes), psUtil.current(notes));
event.newRev = ev.getNewRevisionId();
dispatcher.run(d -> d.postEvent(change, event));
@@ -521,7 +530,8 @@
event.change = changeAttributeSupplier(change, notes);
event.abandoner = accountAttributeSupplier(ev.getWho());
- event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
+ event.patchSet =
+ patchSetAttributeSupplier(changeDataFactory.create(notes), psUtil.current(notes));
event.reason = ev.getReason();
dispatcher.run(d -> d.postEvent(change, event));
@@ -540,7 +550,7 @@
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
- event.patchSet = patchSetAttributeSupplier(change, patchSet);
+ event.patchSet = patchSetAttributeSupplier(changeDataFactory.create(notes), patchSet);
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
@@ -558,7 +568,7 @@
event.change = changeAttributeSupplier(change, notes);
event.changer = accountAttributeSupplier(ev.getWho());
- event.patchSet = patchSetAttributeSupplier(change, patchSet);
+ event.patchSet = patchSetAttributeSupplier(changeDataFactory.create(notes), patchSet);
dispatcher.run(d -> d.postEvent(change, event));
} catch (StorageException e) {
@@ -574,7 +584,8 @@
VoteDeletedEvent event = new VoteDeletedEvent(change);
event.change = changeAttributeSupplier(change, notes);
- event.patchSet = patchSetAttributeSupplier(change, psUtil.current(notes));
+ event.patchSet =
+ patchSetAttributeSupplier(changeDataFactory.create(notes), psUtil.current(notes));
event.comment = ev.getMessage();
event.reviewer = accountAttributeSupplier(ev.getReviewer());
event.remover = accountAttributeSupplier(ev.getWho());
diff --git a/java/com/google/gerrit/server/extensions/events/EventUtil.java b/java/com/google/gerrit/server/extensions/events/EventUtil.java
index 7c8777f..c9b9f7a 100644
--- a/java/com/google/gerrit/server/extensions/events/EventUtil.java
+++ b/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -96,7 +96,12 @@
public RevisionInfo revisionInfo(Project.NameKey project, PatchSet ps)
throws PatchListNotAvailableException, GpgException, IOException, PermissionBackendException {
ChangeData cd = changeDataFactory.create(project, ps.id().changeId());
- return revisionJsonFactory.create(changeOptions).getRevisionInfo(cd, ps);
+ return revisionInfo(cd, ps);
+ }
+
+ public RevisionInfo revisionInfo(ChangeData changeData, PatchSet ps)
+ throws PatchListNotAvailableException, GpgException, IOException, PermissionBackendException {
+ return revisionJsonFactory.create(changeOptions).getRevisionInfo(changeData, ps);
}
@Nullable
diff --git a/java/com/google/gerrit/server/extensions/events/RevisionCreated.java b/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
index a60d982..f6d5881 100644
--- a/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
+++ b/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
@@ -78,7 +78,7 @@
Event event =
new Event(
util.changeInfo(changeData),
- util.revisionInfo(changeData.project(), patchSet),
+ util.revisionInfo(changeData, patchSet),
util.accountInfo(uploader),
when,
notify.handling());
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index d3b5605..0fd9c0e 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -22,7 +22,6 @@
import com.google.common.collect.Lists;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.LabelTypes;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
@@ -262,7 +261,6 @@
Map<Project.NameKey, RevWalk> revWalks,
AccountAttributeLoader accountLoader)
throws IOException {
- LabelTypes labelTypes = d.getLabelTypes();
ChangeAttribute c = eventFactory.asChangeAttribute(d.change(), accountLoader);
c.hashtags = Lists.newArrayList(d.hashtags());
eventFactory.extend(c, d.change());
@@ -286,67 +284,71 @@
eventFactory.addCommitMessage(c, d.commitMessage());
}
- RevWalk rw = null;
if (includePatchSets || includeCurrentPatchSet || includeDependencies) {
Project.NameKey p = d.change().getProject();
- rw = revWalks.get(p);
+ Repository repo;
+ RevWalk rw = revWalks.get(p);
// Cache and reuse repos and revwalks.
if (rw == null) {
- Repository repo = repoManager.openRepository(p);
+ repo = repoManager.openRepository(p);
checkState(repos.put(p, repo) == null);
rw = new RevWalk(repo);
revWalks.put(p, rw);
+ } else {
+ repo = repos.get(p);
}
- }
- if (includePatchSets) {
- eventFactory.addPatchSets(
- rw,
- c,
- d.patchSets(),
- includeApprovals ? d.conditionallyLoadApprovalsWithCopied().asMap() : null,
- includeFiles,
- d.change(),
- labelTypes,
- accountLoader);
- }
-
- if (includeCurrentPatchSet) {
- PatchSet current = d.currentPatchSet();
- if (current != null) {
- c.currentPatchSet = eventFactory.asPatchSetAttribute(rw, d.change(), current);
- eventFactory.addApprovals(
- c.currentPatchSet, d.currentApprovals(), labelTypes, accountLoader);
-
- if (includeFiles) {
- eventFactory.addPatchSetFileNames(c.currentPatchSet, d.change(), d.currentPatchSet());
- }
+ if (includePatchSets) {
+ eventFactory.addPatchSets(
+ rw,
+ repo.getConfig(),
+ c,
+ includeApprovals ? d.conditionallyLoadApprovalsWithCopied().asMap() : null,
+ includeFiles,
+ d,
+ accountLoader);
if (includeComments) {
- eventFactory.addPatchSetComments(c.currentPatchSet, d.publishedComments(), accountLoader);
+ for (PatchSetAttribute attribute : c.patchSets) {
+ eventFactory.addPatchSetComments(attribute, d.publishedComments(), accountLoader);
+ }
}
}
+
+ if (includeCurrentPatchSet) {
+ PatchSet current = d.currentPatchSet();
+ if (current != null) {
+ if (includePatchSets) {
+ for (PatchSetAttribute attribute : c.patchSets) {
+ if (attribute.number == current.number()) {
+ c.currentPatchSet = attribute.shallowClone();
+ // approvals will be populated later using different logic than --patch-sets uses
+ c.currentPatchSet.approvals = null;
+ break;
+ }
+ }
+ } else {
+ c.currentPatchSet =
+ eventFactory.asPatchSetAttribute(rw, repo.getConfig(), d, current, accountLoader);
+ if (includeFiles) {
+ eventFactory.addPatchSetFileNames(c.currentPatchSet, d.change(), d.currentPatchSet());
+ }
+ if (includeComments) {
+ eventFactory.addPatchSetComments(
+ c.currentPatchSet, d.publishedComments(), accountLoader);
+ }
+ }
+ eventFactory.addApprovals(
+ c.currentPatchSet, d.currentApprovals(), d.getLabelTypes(), accountLoader);
+ }
+ }
+
+ if (includeDependencies) {
+ eventFactory.addDependencies(rw, c, d.change(), d.currentPatchSet());
+ }
}
if (includeComments) {
eventFactory.addComments(c, d.messages(), accountLoader);
- if (includePatchSets) {
- eventFactory.addPatchSets(
- rw,
- c,
- d.patchSets(),
- includeApprovals ? d.approvals().asMap() : null,
- includeFiles,
- d.change(),
- labelTypes,
- accountLoader);
- for (PatchSetAttribute attribute : c.patchSets) {
- eventFactory.addPatchSetComments(attribute, d.publishedComments(), accountLoader);
- }
- }
- }
-
- if (includeDependencies) {
- eventFactory.addDependencies(rw, c, d.change(), d.currentPatchSet());
}
ImmutableList<PluginDefinedInfo> pluginInfos = pluginInfosByChange.get(d.getId());
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index 36b859c..0a47d62 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.git.ObjectIds.abbreviateName;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static java.util.stream.Collectors.joining;
@@ -71,10 +72,10 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
-import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -99,8 +100,6 @@
"Submit all ${topicSize} changes of the same topic "
+ "(${submitSize} changes including ancestors and other "
+ "changes related by topic)";
- private static final String BLOCKED_HIDDEN_SUBMIT_TOOLTIP =
- "This change depends on other hidden changes which are not ready";
private static final String CLICK_FAILURE_TOOLTIP = "Clicking the button would fail";
private static final String CHANGE_UNMERGEABLE = "Problems with integrating this change";
@@ -240,48 +239,15 @@
*/
@Nullable
private String problemsForSubmittingChangeset(ChangeData cd, ChangeSet cs, CurrentUser user) {
- try {
- if (cs.furtherHiddenChanges()) {
- logger.atFine().log(
- "Change %d cannot be submitted by user %s because it depends on hidden changes: %s",
- cd.getId().get(), user.getLoggableName(), cs.nonVisibleChanges());
- return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
- }
- for (ChangeData c : cs.changes()) {
- Set<ChangePermission> can =
- permissionBackend
- .user(user)
- .change(c)
- .test(EnumSet.of(ChangePermission.READ, ChangePermission.SUBMIT));
- if (!can.contains(ChangePermission.READ)) {
- logger.atFine().log(
- "Change %d cannot be submitted by user %s because it depends on change %d which the user cannot read",
- cd.getId().get(), user.getLoggableName(), c.getId().get());
- return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
- }
- if (!can.contains(ChangePermission.SUBMIT)) {
- return "You don't have permission to submit change " + c.getId();
- }
- if (c.change().isWorkInProgress()) {
- return "Change " + c.getId() + " is marked work in progress";
- }
- try {
- // The data in the change index may be stale (e.g. if submit requirements have been
- // changed). For that one change for which the submit action is computed, use the
- // freshly loaded ChangeData instance 'cd' instead of the potentially stale ChangeData
- // instance 'c' that was loaded from the index. This makes a difference if the ChangeSet
- // 'cs' only contains this one single change. If the ChangeSet contains further changes
- // those may still be stale.
- MergeOp.checkSubmitRequirements(cd.getId().equals(c.getId()) ? cd : c);
- } catch (ResourceConflictException e) {
- return (c.getId() == cd.getId())
- ? String.format("Change %s is not ready: %s", cd.getId(), e.getMessage())
- : String.format(
- "Change %s must be submitted with change %s but %s is not ready: %s",
- cd.getId(), c.getId(), c.getId(), e.getMessage());
- }
- }
+ Optional<String> reason =
+ MergeOp.checkCommonSubmitProblems(cd.change(), cs, false, permissionBackend, user).stream()
+ .findFirst()
+ .map(MergeOp.ChangeProblem::getProblem);
+ if (reason.isPresent()) {
+ return reason.get();
+ }
+ try {
if (!useMergeabilityCheck) {
return null;
}
@@ -298,7 +264,7 @@
return "Problems with change(s): "
+ unmergeable.stream().map(c -> c.getId().toString()).collect(joining(", "));
}
- } catch (PermissionBackendException | IOException e) {
+ } catch (IOException e) {
logger.atSevere().withCause(e).log("Error checking if change is submittable");
throw new StorageException("Could not determine problems for the change", e);
}
@@ -331,6 +297,15 @@
mergeSuperSet
.get()
.completeChangeSet(cd.change(), resource.getUser(), /*includingTopicClosure= */ false);
+ // Replace potentially stale ChangeData for the current change with the fresher one.
+ cs =
+ new ChangeSet(
+ cs.changes().stream()
+ .map(csChange -> csChange.getId().equals(cd.getId()) ? cd : csChange)
+ .collect(toImmutableList()),
+ cs.nonVisibleChanges());
+ String submitProblems = problemsForSubmittingChangeset(cd, cs, resource.getUser());
+
String topic = change.getTopic();
int topicSize = 0;
if (!Strings.isNullOrEmpty(topic)) {
@@ -338,8 +313,6 @@
}
boolean treatWithTopic = submitWholeTopic && !Strings.isNullOrEmpty(topic) && topicSize > 1;
- String submitProblems = problemsForSubmittingChangeset(cd, cs, resource.getUser());
-
if (submitProblems != null) {
return new UiAction.Description()
.setLabel(treatWithTopic ? submitTopicLabel : (cs.size() > 1) ? labelWithParents : label)
diff --git a/java/com/google/gerrit/server/submit/MergeMetrics.java b/java/com/google/gerrit/server/submit/MergeMetrics.java
index b56d9ef..ed07f2f 100644
--- a/java/com/google/gerrit/server/submit/MergeMetrics.java
+++ b/java/com/google/gerrit/server/submit/MergeMetrics.java
@@ -54,32 +54,36 @@
.setRate());
}
- public void countChangesThatWereSubmittedWithRebaserApproval(ChangeData cd) {
- if (isRebaseOnBehalfOfUploader(cd)
- && hasCodeReviewApprovalOfRealUploader(cd)
- && !hasCodeReviewApprovalOfUserThatIsNotTheRealUploader(cd)
- && ignoresCodeReviewApprovalsOfUploader(cd)) {
- // 1. The patch set that is being submitted was created by rebasing on behalf of the uploader.
- // The uploader of the patch set is the original uploader on whom's behalf the rebase was
- // done. The real uploader is the user that did the rebase on behalf of the uploader (e.g. by
- // clicking on the rebase button).
- //
- // 2. The change has a Code-Review approval of the real uploader (aka the rebaser).
- //
- // 3. The change doesn't have a Code-Review approval of any other user (a user that is not the
- // real uploader).
- //
- // 4. Code-Review approvals of the uploader are ignored.
- //
- // If instead of a rebase on behalf of the uploader a normal rebase would have been done the
- // rebaser would have been the uploader of the patch set. In this case the Code-Review
- // approval of the rebaser would not have counted since Code-Review approvals of the uploader
- // are ignored.
- //
- // In this case we assume that the change would not be submittable if a normal rebase had been
- // done. This is not always correct (e.g. if there are approvals of multiple reviewers) but
- // it's good enough for the metric.
- countChangesThatWereSubmittedWithRebaserApproval.increment();
+ public void countChangesThatWereSubmittedWithRebaserApproval(ChangeSet cs) {
+ for (ChangeData cd : cs.changes()) {
+ if (isRebaseOnBehalfOfUploader(cd)
+ && hasCodeReviewApprovalOfRealUploader(cd)
+ && !hasCodeReviewApprovalOfUserThatIsNotTheRealUploader(cd)
+ && ignoresCodeReviewApprovalsOfUploader(cd)) {
+ // 1. The patch set that is being submitted was created by rebasing on behalf of the
+ // uploader.
+ //
+ // The uploader of the patch set is the original uploader on whose behalf the rebase was
+ // done. The real uploader is the user that did the rebase on behalf of the uploader (e.g.
+ // by clicking on the rebase button).
+ //
+ // 2. The change has a Code-Review approval of the real uploader (aka the rebaser).
+ //
+ // 3. The change doesn't have a Code-Review approval of any other user (a user that is not
+ // the real uploader).
+ //
+ // 4. Code-Review approvals of the uploader are ignored.
+ //
+ // If instead of a rebase on behalf of the uploader a normal rebase would have been done the
+ // rebaser would have been the uploader of the patch set. In this case the Code-Review
+ // approval of the rebaser would not have counted since Code-Review approvals of the
+ // uploader are ignored.
+ //
+ // In this case we assume that the change would not be submittable if a normal rebase had
+ // been done. This is not always correct (e.g. if there are approvals of multiple reviewers)
+ // but it's good enough for the metric.
+ countChangesThatWereSubmittedWithRebaserApproval.increment();
+ }
}
}
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index eb41690..233f00e 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -69,6 +69,7 @@
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.InternalUser;
import com.google.gerrit.server.change.NotifyResolver;
@@ -83,6 +84,8 @@
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.StoreSubmitRequirementsOp;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
@@ -112,6 +115,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -292,6 +296,7 @@
private final ChangeData.Factory changeDataFactory;
private final StoreSubmitRequirementsOp.Factory storeSubmitRequirementsOpFactory;
private final MergeMetrics mergeMetrics;
+ private final PermissionBackend permissionBackend;
// Changes that were updated by this MergeOp.
private final Map<Change.Id, Change> updatedChanges;
@@ -336,7 +341,8 @@
MergeMetrics mergeMetrics,
ProjectCache projectCache,
ExperimentFeatures experimentFeatures,
- @GerritServerConfig Config config) {
+ @GerritServerConfig Config config,
+ PermissionBackend permissionBackend) {
this.cmUtil = cmUtil;
this.batchUpdateFactory = batchUpdateFactory;
this.batchUpdates = batchUpdates;
@@ -362,6 +368,7 @@
hasImplicitMergeTimeoutSeconds =
ConfigUtil.getTimeUnit(
config, "change", null, "implicitMergeCalculationTimeout", 60, TimeUnit.SECONDS);
+ this.permissionBackend = permissionBackend;
}
@Override
@@ -371,6 +378,12 @@
}
}
+ /**
+ * Check that SRs are fulfilled or throw otherwise
+ *
+ * @param cd change that is being checked
+ * @throws ResourceConflictException the exception that is thrown if the SR is not fulfilled
+ */
public static void checkSubmitRequirements(ChangeData cd) throws ResourceConflictException {
PatchSet patchSet = cd.currentPatchSet();
if (patchSet == null) {
@@ -425,32 +438,119 @@
return cd.submitRecords(submitRuleOptions(/* allowClosed= */ false));
}
- private void checkSubmitRulesAndState(ChangeSet cs, boolean allowMerged)
- throws ResourceConflictException {
- checkArgument(
- !cs.furtherHiddenChanges(), "checkSubmitRulesAndState called for topic with hidden change");
+ @AutoValue
+ public abstract static class ChangeProblem {
+ public abstract Change.Id getChangeId();
+
+ public abstract String getProblem();
+
+ public static ChangeProblem create(Change.Id changeId, String problem) {
+ return new AutoValue_MergeOp_ChangeProblem(changeId, problem);
+ }
+ }
+
+ /**
+ * Returns a list of messages describing what prevents the current change from being submitted.
+ *
+ * <p>The method checks all changes in the {@code cs} for their current status, submitability and
+ * permissions.
+ *
+ * @param triggeringChange Change for which merge/submit action was initiated
+ * @param cs Set of changes that the current change depends on
+ * @param allowMerged True if change being already merged is not a problem to be reported
+ * @param permissionBackend Interface for checking user ACLs
+ * @param caller The user who is triggering a merge
+ * @return List of problems preventing merge
+ */
+ public static ImmutableList<ChangeProblem> checkCommonSubmitProblems(
+ Change triggeringChange,
+ ChangeSet cs,
+ boolean allowMerged,
+ PermissionBackend permissionBackend,
+ CurrentUser caller) {
+ ImmutableList.Builder<ChangeProblem> problems = ImmutableList.builder();
+ if (cs.furtherHiddenChanges()) {
+ logger.atFine().log(
+ "Change %d cannot be submitted by user %s because it depends on hidden changes: %s",
+ triggeringChange.getId().get(), caller.getLoggableName(), cs.nonVisibleChanges());
+ problems.add(
+ ChangeProblem.create(
+ triggeringChange.getId(),
+ String.format(
+ "Change %d depends on other hidden changes", triggeringChange.getId().get())));
+ }
for (ChangeData cd : cs.changes()) {
try {
- if (!cd.change().isNew()) {
+ Set<ChangePermission> can =
+ permissionBackend
+ .user(caller)
+ .change(cd)
+ .test(EnumSet.of(ChangePermission.READ, ChangePermission.SUBMIT));
+ if (!can.contains(ChangePermission.READ)) {
+ // The READ permission should already be handled during generation of ChangeSet, however
+ // MergeSuperSetComputation is an interface and on API level doesn't guarantee that this
+ // have been verified for all changes. Additionally, this protects against potential
+ // issues due to staleness.
+ logger.atFine().log(
+ "Change %d cannot be submitted by user %s because it depends on change %d which the"
+ + "user cannot read",
+ triggeringChange.getId().get(), caller.getLoggableName(), cd.getId().get());
+ problems.add(
+ ChangeProblem.create(
+ cd.getId(),
+ String.format(
+ "Change %d depends on other hidden changes",
+ triggeringChange.getId().get())));
+ } else if (!can.contains(ChangePermission.SUBMIT)) {
+ logger.atFine().log(
+ "Change %d cannot be submitted by user %s because it depends on change %d which the"
+ + "user cannot submit",
+ triggeringChange.getId().get(), caller.getLoggableName(), cd.getId().get());
+ problems.add(
+ ChangeProblem.create(
+ cd.getId(),
+ String.format("Insufficient permission to submit change %d", cd.getId().get())));
+ } else if (!cd.change().isNew()) {
if (!(cd.change().isMerged() && allowMerged)) {
- commitStatus.problem(
- cd.getId(), "Change " + cd.getId() + " is " + ChangeUtil.status(cd.change()));
+ problems.add(
+ ChangeProblem.create(
+ cd.getId(),
+ String.format(
+ "Change %d is %s", cd.getId().get(), ChangeUtil.status(cd.change()))));
}
} else if (cd.change().isWorkInProgress()) {
- commitStatus.problem(cd.getId(), "Change " + cd.getId() + " is work in progress");
+ problems.add(
+ ChangeProblem.create(
+ cd.getId(),
+ String.format("Change %d is marked work in progress", cd.getId().get())));
} else {
checkSubmitRequirements(cd);
- mergeMetrics.countChangesThatWereSubmittedWithRebaserApproval(cd);
}
} catch (ResourceConflictException e) {
- commitStatus.problem(cd.getId(), e.getMessage());
- } catch (StorageException e) {
+ // Exception is thrown means submit requirement is not fulfilled.
+ problems.add(
+ ChangeProblem.create(
+ cd.getId(),
+ triggeringChange.getId().equals(cd.getId())
+ ? String.format("Change %s is not ready: %s", cd.getId(), e.getMessage())
+ : String.format(
+ "Change %s must be submitted with change %s but %s is not ready: %s",
+ triggeringChange.getId(), cd.getId(), cd.getId(), e.getMessage())));
+ } catch (StorageException | PermissionBackendException e) {
String msg = "Error checking submit rules for change";
- logger.atWarning().withCause(e).log("%s %s", msg, cd.getId());
- commitStatus.problem(cd.getId(), msg);
+ logger.atWarning().withCause(e).log("%s %s", msg, triggeringChange.getId());
+ problems.add(ChangeProblem.create(cd.getId(), msg));
}
}
+ return problems.build();
+ }
+
+ private void checkSubmitRulesAndState(Change triggeringChange, ChangeSet cs, boolean allowMerged)
+ throws ResourceConflictException {
+ checkCommonSubmitProblems(triggeringChange, cs, allowMerged, permissionBackend, caller).stream()
+ .forEach(cp -> commitStatus.problem(cp.getChangeId(), cp.getProblem()));
commitStatus.maybeFailVerbose();
+ mergeMetrics.countChangesThatWereSubmittedWithRebaserApproval(cs);
}
private void bypassSubmitRulesAndRequirements(ChangeSet cs) {
@@ -580,7 +680,7 @@
this.commitStatus = new CommitStatus(filteredNoteDbChangeSet, isRetry);
if (checkSubmitRules) {
logger.atFine().log("Checking submit rules and state");
- checkSubmitRulesAndState(filteredNoteDbChangeSet, isRetry);
+ checkSubmitRulesAndState(change, filteredNoteDbChangeSet, isRetry);
} else {
logger.atFine().log("Bypassing submit rules");
bypassSubmitRulesAndRequirements(filteredNoteDbChangeSet);
diff --git a/javatests/com/google/gerrit/acceptance/api/change/DefaultSubmitRequirementsIT.java b/javatests/com/google/gerrit/acceptance/api/change/DefaultSubmitRequirementsIT.java
index fc8eaed..9af7a2d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/DefaultSubmitRequirementsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/DefaultSubmitRequirementsIT.java
@@ -41,7 +41,8 @@
.isEqualTo(
String.format(
"Failed to submit 1 change due to the following problems:\n"
- + "Change %s: submit requirement 'No-Unresolved-Comments' is unsatisfied.",
+ + "Change %1$s: Change %1$s is not ready: "
+ + "submit requirement 'No-Unresolved-Comments' is unsatisfied.",
r.getChange().getId().get()));
// Resolve the comment and check that the change can be submitted now.
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 25af040..0b86406 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -589,7 +589,7 @@
+ num
+ ": Change "
+ num
- + " is work in progress");
+ + " is marked work in progress");
}
@Test
@@ -607,7 +607,7 @@
+ num
+ ": Change "
+ num
- + " is work in progress");
+ + " is marked work in progress");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 1d8e0b8..5f1a982 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -95,12 +95,15 @@
PushOneCommit.Result change2 = createChange();
Change.Id id1 = change1.getPatchSetId().changeId();
+ Change.Id id2 = change2.getPatchSetId().changeId();
submitWithConflict(
change2.getChangeId(),
- "Failed to submit 2 changes due to the following problems:\n"
- + "Change "
- + id1
- + ": submit requirement 'Code-Review' is unsatisfied.");
+ String.format(
+ "Failed to submit 2 changes due to the following problems:\n"
+ + "Change %d"
+ + ": Change %d must be submitted with change %d but %d is not ready: "
+ + "submit requirement 'Code-Review' is unsatisfied.",
+ id1.get(), id2.get(), id1.get(), id1.get()));
RevCommit updatedHead = projectOperations.project(project).getHead("master");
assertThat(updatedHead.getId()).isEqualTo(initialHead.getId());
diff --git a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 5a4f073..4ee5967 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -16,7 +16,10 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GitUtil;
@@ -25,6 +28,8 @@
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
import com.google.gerrit.extensions.client.ChangeStatus;
@@ -32,6 +37,7 @@
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
import java.util.EnumSet;
@@ -336,6 +342,41 @@
assertSubmittedTogether(id2, id2, id1);
}
+ @Test
+ public void permissionToSubmitForSomeChangesInTopic() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.SUBMIT).ref("refs/heads/testbranch").group(REGISTERED_USERS))
+ .update();
+
+ createBranch(BranchNameKey.create(getProject(), "testbranch"));
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ // Create two independent commits and push.
+ RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
+ String id1 = getChangeId(c1_1);
+ pushHead(testRepo, "refs/for/master%topic=" + name("connectingTopic"), false);
+
+ testRepo.reset(initialHead);
+ RevCommit c2_1 = commitBuilder().add("b.txt", "2").message("subject: 2").create();
+ String id2 = getChangeId(c2_1);
+ pushHead(testRepo, "refs/for/testbranch%topic=" + name("connectingTopic"), false);
+
+ approve(id1);
+ approve(id2);
+ if (isSubmitWholeTopicEnabled()) {
+ ResourceConflictException e =
+ assertThrows(ResourceConflictException.class, () -> submit(id1));
+ assertThat(e.getMessage())
+ .contains(
+ String.format(
+ "Insufficient permission to submit change %d",
+ gApi.changes().id(id2).get()._number));
+ } else {
+ submit(id1);
+ }
+ }
+
private String getChangeId(RevCommit c) throws Exception {
return GitUtil.getChangeId(testRepo, c).get();
}
diff --git a/package.json b/package.json
index 82d3854..6aa807b 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"@typescript-eslint/parser": "^5.62.0"
},
"devDependencies": {
- "@koa/cors": "^3.4.3",
+ "@koa/cors": "^5.0.0",
"@types/page": "^1.11.9",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@web/dev-server": "^0.1.38",
diff --git a/plugins/hooks b/plugins/hooks
index 41c3ad1..4f43f5d 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 41c3ad1d584f3b81bacc59e89e022dc1e7ffc3f2
+Subproject commit 4f43f5db6b8aa7f36381f4f9a4c9ec1fc335d949
diff --git a/plugins/replication b/plugins/replication
index d1ad7d5..2e2a641 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit d1ad7d504171c8f68d2cf956bb3422fa84a8194f
+Subproject commit 2e2a6415a979e74e454a11e6e1637cb0d9d7a20a
diff --git a/polygerrit-ui/app/api/plugin.ts b/polygerrit-ui/app/api/plugin.ts
index 8630f75..c595add 100644
--- a/polygerrit-ui/app/api/plugin.ts
+++ b/polygerrit-ui/app/api/plugin.ts
@@ -9,6 +9,7 @@
import {ChangeReplyPluginApi} from './change-reply';
import {ChecksPluginApi} from './checks';
import {EventHelperPluginApi} from './event-helper';
+import {PluginElement} from './hook';
import {PopupPluginApi} from './popup';
import {ReportingPluginApi} from './reporting';
import {ChangeActionsPluginApi} from './change-actions';
@@ -63,7 +64,7 @@
suggestions(): SuggestionsPluginApi;
eventHelper(element: Node): EventHelperPluginApi;
getPluginName(): string;
- hook<T extends HTMLElement>(
+ hook<T extends PluginElement>(
endpointName: string,
opt_options?: RegisterOptions
): HookApi<T>;
@@ -72,12 +73,12 @@
popup(): Promise<PopupPluginApi>;
popup(moduleName: string): Promise<PopupPluginApi>;
popup(moduleName?: string): Promise<PopupPluginApi | null>;
- registerCustomComponent<T extends HTMLElement>(
+ registerCustomComponent<T extends PluginElement>(
endpointName: string,
moduleName?: string,
options?: RegisterOptions
): HookApi<T>;
- registerDynamicCustomComponent<T extends HTMLElement>(
+ registerDynamicCustomComponent<T extends PluginElement>(
endpointName: string,
moduleName?: string,
options?: RegisterOptions
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 c02d474..f1ef362 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
@@ -323,7 +323,6 @@
@state()
replyDisabled = true;
- @state()
private updateCheckTimerHandle?: number | null;
@state() editMode = false;
diff --git a/web-dev-server.config.mjs b/web-dev-server.config.mjs
index 2a7dca4..53a240e 100644
--- a/web-dev-server.config.mjs
+++ b/web-dev-server.config.mjs
@@ -1,5 +1,7 @@
import { esbuildPlugin } from "@web/dev-server-esbuild";
import cors from "@koa/cors";
+import path from 'node:path';
+import fs from 'node:fs';
/** @type {import('@web/dev-server').DevServerConfig} */
export default {
@@ -18,6 +20,20 @@
// (ex: gerrit-review.googlesource.com), which happens during local
// development with Gerrit FE Helper extension.
cors({ origin: "*" }),
+ // Map some static assets.
+ // When creating the bundle, the files are moved by polygerrit_bundle() in
+ // polygerrit-ui/app/rules.bzl
+ async (context, next) => {
+
+ if ( context.url.includes("/bower_components/webcomponentsjs/webcomponents-lite.js") ) {
+ context.response.redirect("/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js");
+
+ } else if ( context.url.startsWith( "/fonts/" ) ) {
+ const fontFile = path.join( "lib/fonts", path.basename(context.url) );
+ context.body = fs.createReadStream( fontFile );
+ }
+ await next();
+ },
// The issue solved here is that our production index.html does not load
// 'gr-app.js' as an ESM module due to our build process, but in development
// all our source code is written as ESM modules. When using the Gerrit FE
@@ -40,6 +56,7 @@
await next();
if (!isGrAppMjs && context.url.includes("gr-app.js")) {
+ context.set('Content-Type', 'application/javascript; charset=utf-8');
context.body = "import('./gr-app.mjs')";
}
},
diff --git a/yarn.lock b/yarn.lock
index 9a3503c..7b49a65 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -252,10 +252,10 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
-"@koa/cors@^3.4.3":
- version "3.4.3"
- resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.4.3.tgz#d669ee6e8d6e4f0ec4a7a7b0a17e7a3ed3752ebb"
- integrity sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==
+"@koa/cors@^5.0.0":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-5.0.0.tgz#0029b5f057fa0d0ae0e37dd2c89ece315a0daffd"
+ integrity sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==
dependencies:
vary "^1.1.2"