Merge "Fix some tests for Polymer 2"
diff --git a/java/com/google/gerrit/reviewdb/client/RefNames.java b/java/com/google/gerrit/reviewdb/client/RefNames.java
index 1f11921..3854310 100644
--- a/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -123,6 +123,11 @@
return shard(id.get(), r).append(META_SUFFIX).toString();
}
+ public static String patchSetRef(PatchSet.Id id) {
+ StringBuilder r = newStringBuilder().append(REFS_CHANGES);
+ return shard(id.changeId().get(), r).append('/').append(id.get()).toString();
+ }
+
public static String robotCommentsRef(Change.Id id) {
StringBuilder r = newStringBuilder().append(REFS_CHANGES);
return shard(id.get(), r).append(ROBOT_COMMENTS_SUFFIX).toString();
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 66e66ca..b0f8e54 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -35,12 +35,10 @@
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.ReceiveCommitsExecutor;
-import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.ProjectRunnable;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ContributorAgreementsChecker;
@@ -58,7 +56,6 @@
import com.google.inject.name.Named;
import java.io.IOException;
import java.io.OutputStream;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -66,8 +63,6 @@
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.transport.AdvertiseRefsHook;
-import org.eclipse.jgit.transport.AdvertiseRefsHookChain;
import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
@@ -297,15 +292,10 @@
receiveConfig.checkReferencedObjectsAreReachable);
}
- List<AdvertiseRefsHook> advHooks = new ArrayList<>(4);
allRefsWatcher = new AllRefsWatcher();
- advHooks.add(allRefsWatcher);
- advHooks.add(
- new DefaultAdvertiseRefsHook(perm, RefFilterOptions.builder().setFilterMeta(true).build()));
- advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName));
- advHooks.add(new HackPushNegotiateHook());
- receivePack.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
-
+ receivePack.setAdvertiseRefsHook(
+ ReceiveCommitsAdvertiseRefsHookChain.create(
+ allRefsWatcher, perm, queryProvider, projectName));
resultChangeIds = new ResultChangeIds();
receiveCommits =
factory.create(
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
index f7e0078..2ea417e 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
@@ -14,9 +14,8 @@
package com.google.gerrit.server.git.receive;
-import com.google.auto.value.AutoValue;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Maps;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.exceptions.StorageException;
@@ -29,28 +28,44 @@
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Provider;
+import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
import org.eclipse.jgit.transport.BaseReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.UploadPack;
-/** Exposes only the non refs/changes/ reference names. */
+/**
+ * Exposes only the non refs/changes/ reference names and provide additional haves.
+ *
+ * <p>Negotiation on Git push is suboptimal in that it tends to send more objects to the server than
+ * it should. This results in increased latencies for {@code git push}.
+ *
+ * <p>Ref advertisement for Git pushes still works in a "the server speaks first fashion" as Git
+ * Protocol V2 only addressed fetches Therefore the server needs to send all available references.
+ * For large repositories, this can be in the tens of megabytes to send to the client. We therefore
+ * remove all refs in refs/changes/* to scale down that footprint. Trivially, this would increase
+ * the unnecessary objects that the client has to send to the server because the common ancestor it
+ * finds in negotiation might be further back in history.
+ *
+ * <p>To work around this, we advertise the last 32 changes in that repository as additional {@code
+ * .haves}. This is a heuristical approach that aims at scaling down the number of unnecessary
+ * objects that client sends to the server. Unnecessary here refers to objects that the server
+ * already has.
+ *
+ * <p>For some code paths in {@link com.google.gerrit.server.git.DefaultAdvertiseRefsHook}, we
+ * already removed refs/changes, so the logic to skip these in this class become a no-op.
+ *
+ * <p>TODO(hiesel): Instrument this heuristic and proof its value.
+ */
public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- @VisibleForTesting
- @AutoValue
- public abstract static class Result {
- public abstract Map<String, Ref> allRefs();
-
- public abstract Set<ObjectId> additionalHaves();
- }
-
private final Provider<InternalChangeQuery> queryProvider;
private final Project.NameKey projectName;
@@ -68,28 +83,16 @@
@Override
public void advertiseRefs(BaseReceivePack rp) throws ServiceMayNotContinueException {
- Result r = advertiseRefs(HookUtil.ensureAllRefsAdvertised(rp));
- rp.setAdvertisedRefs(r.allRefs(), r.additionalHaves());
+ Map<String, Ref> advertisedRefs = HookUtil.ensureAllRefsAdvertised(rp);
+ advertisedRefs.keySet().stream()
+ .filter(ReceiveCommitsAdvertiseRefsHook::skip)
+ .collect(toImmutableList())
+ .forEach(r -> advertisedRefs.remove(r));
+ rp.setAdvertisedRefs(advertisedRefs, advertiseOpenChanges(rp.getRepository()));
}
- @VisibleForTesting
- public Result advertiseRefs(Map<String, Ref> oldRefs) {
- Map<String, Ref> r = Maps.newHashMapWithExpectedSize(oldRefs.size());
- Set<ObjectId> allPatchSets = Sets.newHashSetWithExpectedSize(oldRefs.size());
- for (Map.Entry<String, Ref> e : oldRefs.entrySet()) {
- String name = e.getKey();
- if (!skip(name)) {
- r.put(name, e.getValue());
- }
- if (name.startsWith(RefNames.REFS_CHANGES)) {
- allPatchSets.add(e.getValue().getObjectId());
- }
- }
- return new AutoValue_ReceiveCommitsAdvertiseRefsHook_Result(
- r, advertiseOpenChanges(allPatchSets));
- }
-
- private Set<ObjectId> advertiseOpenChanges(Set<ObjectId> allPatchSets) {
+ private Set<ObjectId> advertiseOpenChanges(Repository repo)
+ throws ServiceMayNotContinueException {
// Advertise some recent open changes, in case a commit is based on one.
int limit = 32;
try {
@@ -111,11 +114,17 @@
// Ensure we actually observed a patch set ref pointing to this
// object, in case the database is out of sync with the repo and the
// object doesn't actually exist.
- if (allPatchSets.contains(ps.commitId())) {
- r.add(ps.commitId());
+ try {
+ Ref psRef = repo.getRefDatabase().exactRef(RefNames.patchSetRef(ps.id()));
+ if (psRef != null) {
+ r.add(ps.commitId());
+ }
+ } catch (IOException e) {
+ throw new ServiceMayNotContinueException(e);
}
}
}
+
return r;
} catch (StorageException err) {
logger.atSevere().withCause(err).log("Cannot list open changes of %s", projectName);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java
new file mode 100644
index 0000000..d10fc9a
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.receive;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.DefaultAdvertiseRefsHook;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.inject.Provider;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.AdvertiseRefsHookChain;
+
+/**
+ * Helper to ensure that the chain for advertising refs is the same in tests and production code.
+ */
+public class ReceiveCommitsAdvertiseRefsHookChain {
+
+ /**
+ * Returns a single {@link AdvertiseRefsHook} that encompasses a chain of {@link
+ * AdvertiseRefsHook} to be used for advertising when processing a Git push.
+ */
+ public static AdvertiseRefsHook create(
+ AllRefsWatcher allRefsWatcher,
+ PermissionBackend.ForProject perm,
+ Provider<InternalChangeQuery> queryProvider,
+ Project.NameKey projectName) {
+ return create(allRefsWatcher, perm, queryProvider, projectName, false);
+ }
+
+ /**
+ * Returns a single {@link AdvertiseRefsHook} that encompasses a chain of {@link
+ * AdvertiseRefsHook} to be used for advertising when processing a Git push. Omits {@link
+ * HackPushNegotiateHook} as that does not advertise refs on it's own but adds {@code .have} based
+ * on history which is not relevant for the tests we have.
+ */
+ @VisibleForTesting
+ public static AdvertiseRefsHook createForTest(
+ PermissionBackend.ForProject perm,
+ Provider<InternalChangeQuery> queryProvider,
+ Project.NameKey projectName) {
+ return create(new AllRefsWatcher(), perm, queryProvider, projectName, true);
+ }
+
+ private static AdvertiseRefsHook create(
+ AllRefsWatcher allRefsWatcher,
+ PermissionBackend.ForProject perm,
+ Provider<InternalChangeQuery> queryProvider,
+ Project.NameKey projectName,
+ boolean skipHackPushNegotiateHook) {
+ List<AdvertiseRefsHook> advHooks = new ArrayList<>();
+ advHooks.add(allRefsWatcher);
+ advHooks.add(
+ new DefaultAdvertiseRefsHook(perm, RefFilterOptions.builder().setFilterMeta(true).build()));
+ advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName));
+ if (!skipHackPushNegotiateHook) {
+ advHooks.add(new HackPushNegotiateHook());
+ }
+ return AdvertiseRefsHookChain.newChain(advHooks);
+ }
+}
diff --git a/java/com/google/gerrit/server/git/receive/testing/BUILD b/java/com/google/gerrit/server/git/receive/testing/BUILD
new file mode 100644
index 0000000..82cd14b
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/testing/BUILD
@@ -0,0 +1,12 @@
+java_library(
+ name = "testing",
+ testonly = 1,
+ srcs = glob(["**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//lib:guava",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
+ "//lib/jgit/org.eclipse.jgit:jgit",
+ ],
+)
diff --git a/java/com/google/gerrit/server/git/receive/testing/TestRefAdvertiser.java b/java/com/google/gerrit/server/git/receive/testing/TestRefAdvertiser.java
new file mode 100644
index 0000000..c54ab25
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/testing/TestRefAdvertiser.java
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git.receive.testing;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.StreamSupport;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RefAdvertiser;
+
+/** Helper to collect advertised refs and additonal haves and verify them in tests. */
+public class TestRefAdvertiser extends RefAdvertiser {
+
+ @VisibleForTesting
+ @AutoValue
+ public abstract static class Result {
+ public abstract Map<String, Ref> allRefs();
+
+ public abstract Set<ObjectId> additionalHaves();
+
+ public static Result create(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves) {
+ return new AutoValue_TestRefAdvertiser_Result(allRefs, additionalHaves);
+ }
+ }
+
+ private final Map<String, Ref> advertisedRefs;
+ private final Set<ObjectId> additionalHaves;
+ private final Repository repo;
+
+ public TestRefAdvertiser(Repository repo) {
+ advertisedRefs = new HashMap<>();
+ additionalHaves = new HashSet<>();
+ this.repo = repo;
+ }
+
+ @Override
+ protected void writeOne(CharSequence line) throws IOException {
+ List<String> lineParts =
+ StreamSupport.stream(Splitter.on(' ').split(line).spliterator(), false)
+ .map(String::trim)
+ .collect(toImmutableList());
+ if (".have".equals(lineParts.get(1))) {
+ additionalHaves.add(ObjectId.fromString(lineParts.get(0)));
+ } else {
+ ObjectId id = ObjectId.fromString(lineParts.get(0));
+ Ref ref =
+ repo.getRefDatabase().getRefs().stream()
+ .filter(r -> r.getObjectId().equals(id))
+ .findAny()
+ .orElseThrow(
+ () ->
+ new RuntimeException(
+ line.toString() + " does not conform to expected pattern"));
+ advertisedRefs.put(lineParts.get(1), ref);
+ }
+ }
+
+ @Override
+ protected void end() {}
+
+ public Result result() {
+ return Result.create(advertisedRefs, additionalHaves);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/git/BUILD b/javatests/com/google/gerrit/acceptance/git/BUILD
index 96710e2..0de307a 100644
--- a/javatests/com/google/gerrit/acceptance/git/BUILD
+++ b/javatests/com/google/gerrit/acceptance/git/BUILD
@@ -8,6 +8,7 @@
":push_for_review",
":submodule_util",
"//java/com/google/gerrit/git",
+ "//java/com/google/gerrit/server/git/receive/testing",
"//lib/commons:lang",
],
) for f in glob(["*IT.java"])]
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index 811ef35..a3017b6 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -50,7 +50,8 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHook;
+import com.google.gerrit.server.git.receive.ReceiveCommitsAdvertiseRefsHookChain;
+import com.google.gerrit.server.git.receive.testing.TestRefAdvertiser;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -75,6 +76,8 @@
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.AdvertiseRefsHook;
+import org.eclipse.jgit.transport.ReceivePack;
import org.junit.Before;
import org.junit.Test;
@@ -544,11 +547,10 @@
@Test
public void receivePackListsOpenChangesAsAdditionalHaves() throws Exception {
- ReceiveCommitsAdvertiseRefsHook.Result r = getReceivePackRefs();
+ TestRefAdvertiser.Result r = getReceivePackRefs(admin);
assertThat(r.allRefs().keySet())
.containsExactly(
// meta refs are excluded
- "HEAD",
"refs/heads/branch",
"refs/heads/master",
"refs/meta/config",
@@ -568,7 +570,7 @@
.update();
requestScopeOperations.setApiUser(user.id());
- assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd3, 1));
+ assertThat(getReceivePackRefs(user).additionalHaves()).containsExactly(obj(cd3, 1));
}
@Test
@@ -577,7 +579,8 @@
PushOneCommit.Result r = amendChange(cd3.change().getKey().get());
r.assertOkStatus();
cd3 = r.getChange();
- assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd3, 2), obj(cd4, 1));
+ assertThat(getReceivePackRefs(admin).additionalHaves())
+ .containsExactly(obj(cd3, 2), obj(cd4, 1));
}
@Test
@@ -615,7 +618,7 @@
indexer.index(c.getProject(), c.getId());
}
- assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd4, 1));
+ assertThat(getReceivePackRefs(admin).additionalHaves()).containsExactly(obj(cd4, 1));
}
@Test
@@ -961,11 +964,16 @@
}
}
- private ReceiveCommitsAdvertiseRefsHook.Result getReceivePackRefs() throws Exception {
- ReceiveCommitsAdvertiseRefsHook hook =
- new ReceiveCommitsAdvertiseRefsHook(queryProvider, project);
+ private TestRefAdvertiser.Result getReceivePackRefs(TestAccount u) throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
- return hook.advertiseRefs(getAllRefs(repo));
+ AdvertiseRefsHook adv =
+ ReceiveCommitsAdvertiseRefsHookChain.createForTest(
+ newFilter(project, u), queryProvider, project);
+ ReceivePack rp = new ReceivePack(repo);
+ rp.setAdvertiseRefsHook(adv);
+ TestRefAdvertiser advertiser = new TestRefAdvertiser(repo);
+ rp.sendAdvertisedRefs(advertiser);
+ return advertiser.result();
}
}
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 44e5699..f43f411 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -230,7 +230,13 @@
},
_appElement() {
- return document.querySelector('#app-element');
+ // In Polymer2 you have to reach through the shadow root of the app
+ // element. This obviously breaks encapsulation.
+ // TODO(brohlfs): Make this more elegant, e.g. by exposing app-element
+ // explicitly in app, or by delegating to it.
+ return document.getElementById('app-element') ||
+ document.getElementById('app').shadowRoot.getElementById(
+ 'app-element');
},
_redirect(url) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index 7ee49bc..5874187 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -279,7 +279,7 @@
} else if (type === GrDiffBuilder.ContextButtonType.ABOVE) {
text = '+' + context + '↑';
groups = GrDiffGroup.hideInContextControl(line.contextGroups,
- context, undefined);
+ context, numLines);
} else if (type === GrDiffBuilder.ContextButtonType.BELOW) {
text = '+' + context + '↓';
groups = GrDiffGroup.hideInContextControl(line.contextGroups,