summaryrefslogtreecommitdiffstats
path: root/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
diff options
context:
space:
mode:
Diffstat (limited to 'javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java')
-rw-r--r--javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java508
1 files changed, 508 insertions, 0 deletions
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
new file mode 100644
index 0000000000..943b052e07
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -0,0 +1,508 @@
+// Copyright (C) 2015 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.acceptance.git;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.SubscribeSection;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.project.ProjectConfig;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.StreamSupport;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+
+public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
+
+ protected SubmitType getSubmitType() {
+ return cfg.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
+ }
+
+ protected static Config submitByMergeAlways() {
+ Config cfg = new Config();
+ cfg.setBoolean("change", null, "submitWholeTopic", true);
+ cfg.setEnum("project", null, "submitType", SubmitType.MERGE_ALWAYS);
+ return cfg;
+ }
+
+ protected static Config submitByMergeIfNecessary() {
+ Config cfg = new Config();
+ cfg.setBoolean("change", null, "submitWholeTopic", true);
+ cfg.setEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
+ return cfg;
+ }
+
+ protected static Config submitByCherryPickConfig() {
+ Config cfg = new Config();
+ cfg.setBoolean("change", null, "submitWholeTopic", true);
+ cfg.setEnum("project", null, "submitType", SubmitType.CHERRY_PICK);
+ return cfg;
+ }
+
+ protected static Config submitByRebaseAlwaysConfig() {
+ Config cfg = new Config();
+ cfg.setBoolean("change", null, "submitWholeTopic", true);
+ cfg.setEnum("project", null, "submitType", SubmitType.REBASE_ALWAYS);
+ return cfg;
+ }
+
+ protected static Config submitByRebaseIfNecessaryConfig() {
+ Config cfg = new Config();
+ cfg.setBoolean("change", null, "submitWholeTopic", true);
+ cfg.setEnum("project", null, "submitType", SubmitType.REBASE_IF_NECESSARY);
+ return cfg;
+ }
+
+ protected TestRepository<?> createProjectWithPush(
+ String name,
+ @Nullable Project.NameKey parent,
+ boolean createEmptyCommit,
+ SubmitType submitType)
+ throws Exception {
+ Project.NameKey project = createProject(name, parent, createEmptyCommit, submitType);
+ grant(project, "refs/heads/*", Permission.PUSH);
+ grant(project, "refs/for/refs/heads/*", Permission.SUBMIT);
+ return cloneProject(project);
+ }
+
+ protected TestRepository<?> createProjectWithPush(String name, @Nullable Project.NameKey parent)
+ throws Exception {
+ return createProjectWithPush(name, parent, true, getSubmitType());
+ }
+
+ protected TestRepository<?> createProjectWithPush(String name, boolean createEmptyCommit)
+ throws Exception {
+ return createProjectWithPush(name, null, createEmptyCommit, getSubmitType());
+ }
+
+ protected TestRepository<?> createProjectWithPush(String name) throws Exception {
+ return createProjectWithPush(name, null, true, getSubmitType());
+ }
+
+ private static AtomicInteger contentCounter = new AtomicInteger(0);
+
+ protected ObjectId pushChangeTo(
+ TestRepository<?> repo, String ref, String file, String content, String message, String topic)
+ throws Exception {
+ ObjectId ret =
+ repo.branch("HEAD").commit().insertChangeId().message(message).add(file, content).create();
+
+ String pushedRef = ref;
+ if (!topic.isEmpty()) {
+ pushedRef += "/" + name(topic);
+ }
+ String refspec = "HEAD:" + pushedRef;
+
+ Iterable<PushResult> res =
+ repo.git().push().setRemote("origin").setRefSpecs(new RefSpec(refspec)).call();
+
+ RemoteRefUpdate u = Iterables.getOnlyElement(res).getRemoteUpdate(pushedRef);
+ assertThat(u).isNotNull();
+ assertThat(u.getStatus()).isEqualTo(Status.OK);
+ assertThat(u.getNewObjectId()).isEqualTo(ret);
+
+ return ret;
+ }
+
+ protected ObjectId pushChangeTo(TestRepository<?> repo, String ref, String message, String topic)
+ throws Exception {
+ return pushChangeTo(
+ repo, ref, "a.txt", "a contents: " + contentCounter.incrementAndGet(), message, topic);
+ }
+
+ protected ObjectId pushChangeTo(TestRepository<?> repo, String branch) throws Exception {
+ return pushChangeTo(repo, "refs/heads/" + branch, "some change", "");
+ }
+
+ protected ObjectId pushChangesTo(TestRepository<?> repo, String branch, int numChanges)
+ throws Exception {
+ for (int i = 0; i < numChanges; i++) {
+ repo.branch("HEAD")
+ .commit()
+ .insertChangeId()
+ .message("Message " + i)
+ .add(name("file"), "content" + i)
+ .create();
+ }
+ String remoteBranch = "refs/heads/" + branch;
+ Iterable<PushResult> res =
+ repo.git()
+ .push()
+ .setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:" + remoteBranch))
+ .call();
+ List<Status> status =
+ StreamSupport.stream(res.spliterator(), false)
+ .map(r -> r.getRemoteUpdate(remoteBranch).getStatus())
+ .collect(toList());
+ assertThat(status).containsExactly(Status.OK);
+ return Iterables.getLast(res).getRemoteUpdate(remoteBranch).getNewObjectId();
+ }
+
+ protected void allowSubmoduleSubscription(
+ String submodule, String subBranch, String superproject, String superBranch, boolean match)
+ throws Exception {
+ Project.NameKey sub = new Project.NameKey(name(submodule));
+ Project.NameKey superName = new Project.NameKey(name(superproject));
+ try (MetaDataUpdate md = metaDataUpdateFactory.create(sub)) {
+ md.setMessage("Added superproject subscription");
+ SubscribeSection s;
+ ProjectConfig pc = ProjectConfig.read(md);
+ if (pc.getSubscribeSections().containsKey(superName)) {
+ s = pc.getSubscribeSections().get(superName);
+ } else {
+ s = new SubscribeSection(superName);
+ }
+ String refspec;
+ if (superBranch == null) {
+ refspec = subBranch;
+ } else {
+ refspec = subBranch + ":" + superBranch;
+ }
+ if (match) {
+ s.addMatchingRefSpec(refspec);
+ } else {
+ s.addMultiMatchRefSpec(refspec);
+ }
+ pc.addSubscribeSection(s);
+ ObjectId oldId = pc.getRevision();
+ ObjectId newId = pc.commit(md);
+ assertThat(newId).isNotEqualTo(oldId);
+ projectCache.evict(pc.getProject());
+ }
+ }
+
+ protected void allowMatchingSubmoduleSubscription(
+ String submodule, String subBranch, String superproject, String superBranch)
+ throws Exception {
+ allowSubmoduleSubscription(submodule, subBranch, superproject, superBranch, true);
+ }
+
+ protected void createSubmoduleSubscription(
+ TestRepository<?> repo, String branch, String subscribeToRepo, String subscribeToBranch)
+ throws Exception {
+ Config config = new Config();
+ prepareSubmoduleConfigEntry(config, subscribeToRepo, subscribeToBranch);
+ pushSubmoduleConfig(repo, branch, config);
+ }
+
+ protected void createRelativeSubmoduleSubscription(
+ TestRepository<?> repo,
+ String branch,
+ String subscribeToRepoPrefix,
+ String subscribeToRepo,
+ String subscribeToBranch)
+ throws Exception {
+ Config config = new Config();
+ prepareRelativeSubmoduleConfigEntry(
+ config, subscribeToRepoPrefix, subscribeToRepo, subscribeToBranch);
+ pushSubmoduleConfig(repo, branch, config);
+ }
+
+ protected void prepareRelativeSubmoduleConfigEntry(
+ Config config,
+ String subscribeToRepoPrefix,
+ String subscribeToRepo,
+ String subscribeToBranch) {
+ subscribeToRepo = name(subscribeToRepo);
+ String url = subscribeToRepoPrefix + subscribeToRepo;
+ config.setString("submodule", subscribeToRepo, "path", subscribeToRepo);
+ config.setString("submodule", subscribeToRepo, "url", url);
+ if (subscribeToBranch != null) {
+ config.setString("submodule", subscribeToRepo, "branch", subscribeToBranch);
+ }
+ }
+
+ protected void prepareSubmoduleConfigEntry(
+ Config config, String subscribeToRepo, String subscribeToBranch) {
+ // The submodule subscription module checks for gerrit.canonicalWebUrl to
+ // detect if it's configured for automatic updates. It doesn't matter if
+ // it serves from that URL.
+ prepareSubmoduleConfigEntry(config, subscribeToRepo, subscribeToRepo, subscribeToBranch);
+ }
+
+ protected void prepareSubmoduleConfigEntry(
+ Config config, String subscribeToRepo, String subscribeToRepoPath, String subscribeToBranch) {
+ subscribeToRepo = name(subscribeToRepo);
+ subscribeToRepoPath = name(subscribeToRepoPath);
+ // The submodule subscription module checks for gerrit.canonicalWebUrl to
+ // detect if it's configured for automatic updates. It doesn't matter if
+ // it serves from that URL.
+ String url = cfg.getString("gerrit", null, "canonicalWebUrl") + "/" + subscribeToRepo;
+ config.setString("submodule", subscribeToRepoPath, "path", subscribeToRepoPath);
+ config.setString("submodule", subscribeToRepoPath, "url", url);
+ if (subscribeToBranch != null) {
+ config.setString("submodule", subscribeToRepoPath, "branch", subscribeToBranch);
+ }
+ }
+
+ protected void pushSubmoduleConfig(TestRepository<?> repo, String branch, Config config)
+ throws Exception {
+
+ repo.branch("HEAD")
+ .commit()
+ .insertChangeId()
+ .message("subject: adding new subscription")
+ .add(".gitmodules", config.toText())
+ .create();
+
+ repo.git()
+ .push()
+ .setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/heads/" + branch))
+ .call();
+ }
+
+ protected void expectToHaveSubmoduleState(
+ TestRepository<?> repo,
+ String branch,
+ String submodule,
+ TestRepository<?> subRepo,
+ String subBranch)
+ throws Exception {
+
+ submodule = name(submodule);
+ ObjectId commitId =
+ repo.git()
+ .fetch()
+ .setRemote("origin")
+ .call()
+ .getAdvertisedRef("refs/heads/" + branch)
+ .getObjectId();
+
+ ObjectId subHead =
+ subRepo
+ .git()
+ .fetch()
+ .setRemote("origin")
+ .call()
+ .getAdvertisedRef("refs/heads/" + subBranch)
+ .getObjectId();
+
+ RevWalk rw = repo.getRevWalk();
+ RevCommit c = rw.parseCommit(commitId);
+ rw.parseBody(c.getTree());
+
+ RevTree tree = c.getTree();
+ RevObject actualId = repo.get(tree, submodule);
+
+ assertThat(actualId).isEqualTo(subHead);
+ }
+
+ protected void expectToHaveSubmoduleState(
+ TestRepository<?> repo, String branch, String submodule, ObjectId expectedId)
+ throws Exception {
+
+ submodule = name(submodule);
+ ObjectId commitId =
+ repo.git()
+ .fetch()
+ .setRemote("origin")
+ .call()
+ .getAdvertisedRef("refs/heads/" + branch)
+ .getObjectId();
+
+ RevWalk rw = repo.getRevWalk();
+ RevCommit c = rw.parseCommit(commitId);
+ rw.parseBody(c.getTree());
+
+ RevTree tree = c.getTree();
+ RevObject actualId = repo.get(tree, submodule);
+
+ assertThat(actualId).isEqualTo(expectedId);
+ }
+
+ protected void deleteAllSubscriptions(TestRepository<?> repo, String branch) throws Exception {
+ repo.git().fetch().setRemote("origin").call();
+ repo.reset("refs/remotes/origin/" + branch);
+
+ ObjectId expectedId =
+ repo.branch("HEAD")
+ .commit()
+ .insertChangeId()
+ .message("delete contents in .gitmodules")
+ .add(".gitmodules", "") // Just remove the contents of the file!
+ .create();
+ repo.git()
+ .push()
+ .setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/heads/" + branch))
+ .call();
+
+ ObjectId actualId =
+ repo.git()
+ .fetch()
+ .setRemote("origin")
+ .call()
+ .getAdvertisedRef("refs/heads/master")
+ .getObjectId();
+ assertThat(actualId).isEqualTo(expectedId);
+ }
+
+ protected void deleteGitModulesFile(TestRepository<?> repo, String branch) throws Exception {
+ repo.git().fetch().setRemote("origin").call();
+ repo.reset("refs/remotes/origin/" + branch);
+
+ ObjectId expectedId =
+ repo.branch("HEAD")
+ .commit()
+ .insertChangeId()
+ .message("delete .gitmodules")
+ .rm(".gitmodules")
+ .create();
+ repo.git()
+ .push()
+ .setRemote("origin")
+ .setRefSpecs(new RefSpec("HEAD:refs/heads/" + branch))
+ .call();
+
+ ObjectId actualId =
+ repo.git()
+ .fetch()
+ .setRemote("origin")
+ .call()
+ .getAdvertisedRef("refs/heads/master")
+ .getObjectId();
+ assertThat(actualId).isEqualTo(expectedId);
+ }
+
+ protected boolean hasSubmodule(TestRepository<?> repo, String branch, String submodule)
+ throws Exception {
+
+ submodule = name(submodule);
+ Ref branchTip =
+ repo.git().fetch().setRemote("origin").call().getAdvertisedRef("refs/heads/" + branch);
+ if (branchTip == null) {
+ return false;
+ }
+
+ ObjectId commitId = branchTip.getObjectId();
+
+ RevWalk rw = repo.getRevWalk();
+ RevCommit c = rw.parseCommit(commitId);
+ rw.parseBody(c.getTree());
+
+ RevTree tree = c.getTree();
+ try {
+ repo.get(tree, submodule);
+ return true;
+ } catch (AssertionError e) {
+ return false;
+ }
+ }
+
+ protected void expectToHaveCommitMessage(
+ TestRepository<?> repo, String branch, String expectedMessage) throws Exception {
+
+ ObjectId commitId =
+ repo.git()
+ .fetch()
+ .setRemote("origin")
+ .call()
+ .getAdvertisedRef("refs/heads/" + branch)
+ .getObjectId();
+
+ RevWalk rw = repo.getRevWalk();
+ RevCommit c = rw.parseCommit(commitId);
+ assertThat(c.getFullMessage()).isEqualTo(expectedMessage);
+ }
+
+ protected PersonIdent getAuthor(TestRepository<?> repo, String branch) throws Exception {
+ ObjectId commitId =
+ repo.git()
+ .fetch()
+ .setRemote("origin")
+ .call()
+ .getAdvertisedRef("refs/heads/" + branch)
+ .getObjectId();
+
+ RevWalk rw = repo.getRevWalk();
+ RevCommit c = rw.parseCommit(commitId);
+ return c.getAuthorIdent();
+ }
+
+ protected void directUpdateSubmodule(String project, String refName, String path, AnyObjectId id)
+ throws Exception {
+ path = name(path);
+ try (Repository serverRepo = repoManager.openRepository(new Project.NameKey(name(project)));
+ ObjectInserter ins = serverRepo.newObjectInserter();
+ RevWalk rw = new RevWalk(serverRepo)) {
+ Ref ref = serverRepo.exactRef(refName);
+ assertThat(ref).named(refName).isNotNull();
+ ObjectId oldCommitId = ref.getObjectId();
+
+ DirCache dc = DirCache.newInCore();
+ DirCacheBuilder b = dc.builder();
+ b.addTree(
+ new byte[0], DirCacheEntry.STAGE_0, rw.getObjectReader(), rw.parseTree(oldCommitId));
+ b.finish();
+ DirCacheEditor e = dc.editor();
+ e.add(
+ new PathEdit(path) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(FileMode.GITLINK);
+ ent.setObjectId(id);
+ }
+ });
+ e.finish();
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.addParentId(oldCommitId);
+ cb.setTreeId(dc.writeTree(ins));
+ PersonIdent ident = serverIdent.get();
+ cb.setAuthor(ident);
+ cb.setCommitter(ident);
+ cb.setMessage("Direct update submodule " + path);
+ ObjectId newCommitId = ins.insert(cb);
+ ins.flush();
+
+ RefUpdate ru = serverRepo.updateRef(refName);
+ ru.setExpectedOldObjectId(oldCommitId);
+ ru.setNewObjectId(newCommitId);
+ assertThat(ru.update()).isEqualTo(RefUpdate.Result.FAST_FORWARD);
+ }
+ }
+}