
Gerrit moved to a model where it wraps around RefDatabase to tell JGit which refs are visible to a user. This was done in I0bb85de2b. This meant moving filtering code from a AdvertiseRefsHook into a wrapper of RefDatabase. There is one case in which the logic that got moved would not only filter refs but add a ref in addition that is not present in the input: when authenticated users interact with All-Users. In this case, Gerrit adds a refs/users/self symbolic reference to make it easier to interact with one's account data. This led to a problem with the new inferface and smelled as a whole since concerns weren't clearly separated. The problem with the new interface would occur when using RefDatabase#exactRef - a method that expects {0,1} refs but got two for All-Users since refs/users/self was added. This change fixes the issue by separating the addition of the symref into an AdvertiseRefsHook and leaving ref filtering by just that. RefAdvertisementIT has different tests for testing the advertisement of refs/users/self and PushAccountIT covers push cases. The tests succeeded before, because the problem could only be encountered when the in-memory ref cache on the push path is not used. We add a config to run push tests without in-memory ref cache to make sure the code path that failed previosuly gets executed now. Bug: Issue 12027 Change-Id: I07777b698ddb86ad5cec1407ac5671874866fee5
1490 lines
52 KiB
Java
1490 lines
52 KiB
Java
// Copyright (C) 2014 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 com.google.common.truth.Truth.assertWithMessage;
|
|
import static com.google.common.truth.TruthJUnit.assume;
|
|
import static com.google.gerrit.acceptance.GitUtil.fetch;
|
|
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
|
|
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
|
|
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
|
|
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
|
|
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
|
import static java.util.stream.Collectors.toList;
|
|
import static java.util.stream.Collectors.toMap;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
|
import com.google.gerrit.acceptance.GerritConfig;
|
|
import com.google.gerrit.acceptance.NoHttpd;
|
|
import com.google.gerrit.acceptance.PushOneCommit;
|
|
import com.google.gerrit.acceptance.TestAccount;
|
|
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
|
|
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
|
|
import com.google.gerrit.common.Nullable;
|
|
import com.google.gerrit.common.data.AccessSection;
|
|
import com.google.gerrit.common.data.GlobalCapability;
|
|
import com.google.gerrit.common.data.Permission;
|
|
import com.google.gerrit.entities.AccountGroup;
|
|
import com.google.gerrit.entities.Change;
|
|
import com.google.gerrit.entities.Patch;
|
|
import com.google.gerrit.entities.PatchSet;
|
|
import com.google.gerrit.entities.Project;
|
|
import com.google.gerrit.entities.RefNames;
|
|
import com.google.gerrit.extensions.api.changes.DraftInput;
|
|
import com.google.gerrit.extensions.api.groups.GroupInput;
|
|
import com.google.gerrit.extensions.api.projects.BranchInput;
|
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
|
import com.google.gerrit.server.config.AllUsersName;
|
|
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;
|
|
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
|
|
import com.google.gerrit.server.query.change.ChangeData;
|
|
import com.google.gerrit.testing.ConfigSuite;
|
|
import com.google.inject.Inject;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.function.Function;
|
|
import java.util.function.Predicate;
|
|
import org.eclipse.jgit.api.Git;
|
|
import org.eclipse.jgit.junit.TestRepository;
|
|
import org.eclipse.jgit.lib.Config;
|
|
import org.eclipse.jgit.lib.Constants;
|
|
import org.eclipse.jgit.lib.ObjectId;
|
|
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.transport.AdvertiseRefsHook;
|
|
import org.eclipse.jgit.transport.ReceivePack;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
|
|
@NoHttpd
|
|
public class RefAdvertisementIT extends AbstractDaemonTest {
|
|
@Inject private AllUsersName allUsersName;
|
|
@Inject private ChangeNoteUtil noteUtil;
|
|
@Inject private PermissionBackend permissionBackend;
|
|
@Inject private ProjectOperations projectOperations;
|
|
@Inject private RequestScopeOperations requestScopeOperations;
|
|
|
|
private AccountGroup.UUID admins;
|
|
private AccountGroup.UUID nonInteractiveUsers;
|
|
|
|
private RevCommit rcMaster;
|
|
private RevCommit rcBranch;
|
|
|
|
private ChangeData cd1;
|
|
private String psRef1;
|
|
private String metaRef1;
|
|
|
|
private ChangeData cd2;
|
|
private String psRef2;
|
|
private String metaRef2;
|
|
|
|
private ChangeData cd3;
|
|
private String psRef3;
|
|
private String metaRef3;
|
|
|
|
private ChangeData cd4;
|
|
private String psRef4;
|
|
private String metaRef4;
|
|
|
|
@ConfigSuite.Config
|
|
public static Config enableFullRefEvaluation() {
|
|
Config cfg = new Config();
|
|
cfg.setBoolean("auth", null, "skipFullRefEvaluationIfAllRefsAreVisible", false);
|
|
return cfg;
|
|
}
|
|
|
|
@Before
|
|
public void setUp() throws Exception {
|
|
admins = adminGroupUuid();
|
|
nonInteractiveUsers = groupUuid("Non-Interactive Users");
|
|
setUpPermissions();
|
|
setUpChanges();
|
|
}
|
|
|
|
// This method is idempotent, so it is safe to call it on every test setup.
|
|
private void setUpPermissions() throws Exception {
|
|
// Remove read permissions for all users besides admin.
|
|
try (ProjectConfigUpdate u = updateProject(allProjects)) {
|
|
for (AccessSection sec : u.getConfig().getAccessSections()) {
|
|
sec.removePermission(Permission.READ);
|
|
}
|
|
u.save();
|
|
}
|
|
projectOperations
|
|
.allProjectsForUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(admins))
|
|
.update();
|
|
|
|
// Remove all read permissions on All-Users.
|
|
try (ProjectConfigUpdate u = updateProject(allUsers)) {
|
|
for (AccessSection sec : u.getConfig().getAccessSections()) {
|
|
sec.removePermission(Permission.READ);
|
|
}
|
|
u.save();
|
|
}
|
|
}
|
|
|
|
// Building the following:
|
|
// rcMaster (c1 master master-tag) <-- rcBranch (c2 branch branch-tag)
|
|
// \ \
|
|
// (c3_open) (c4_open)
|
|
//
|
|
private void setUpChanges() throws Exception {
|
|
gApi.projects().name(project.get()).branch("branch").create(new BranchInput());
|
|
|
|
// First 2 changes are merged, which means the tags pointing to them are
|
|
// visible.
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.SUBMIT).ref("refs/for/refs/heads/*").group(admins))
|
|
.update();
|
|
|
|
// rcMaster (c1 master)
|
|
PushOneCommit.Result mr =
|
|
pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master%submit");
|
|
mr.assertOkStatus();
|
|
cd1 = mr.getChange();
|
|
rcMaster = mr.getCommit();
|
|
psRef1 = cd1.currentPatchSet().id().toRefName();
|
|
metaRef1 = RefNames.changeMetaRef(cd1.getId());
|
|
|
|
// rcMaster (c1 master) <-- rcBranch (c2 branch)
|
|
PushOneCommit.Result br =
|
|
pushFactory.create(admin.newIdent(), testRepo).to("refs/for/branch%submit");
|
|
br.assertOkStatus();
|
|
cd2 = br.getChange();
|
|
rcBranch = br.getCommit();
|
|
psRef2 = cd2.currentPatchSet().id().toRefName();
|
|
metaRef2 = RefNames.changeMetaRef(cd2.getId());
|
|
|
|
// Second 2 changes are unmerged.
|
|
// rcMaster (c1 master) <-- rcBranch (c2 branch)
|
|
// \
|
|
// (c3_open)
|
|
//
|
|
mr = pushFactory.create(admin.newIdent(), testRepo).to("refs/for/master");
|
|
mr.assertOkStatus();
|
|
cd3 = mr.getChange();
|
|
psRef3 = cd3.currentPatchSet().id().toRefName();
|
|
metaRef3 = RefNames.changeMetaRef(cd3.getId());
|
|
|
|
// rcMaster (c1 master) <-- rcBranch (c2 branch)
|
|
// \ \
|
|
// (c3_open) (c4_open)
|
|
br = pushFactory.create(admin.newIdent(), testRepo).to("refs/for/branch");
|
|
br.assertOkStatus();
|
|
cd4 = br.getChange();
|
|
psRef4 = cd4.currentPatchSet().id().toRefName();
|
|
metaRef4 = RefNames.changeMetaRef(cd4.getId());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// rcMaster (c1 master master-tag) <-- rcBranch (c2 branch)
|
|
// \ \
|
|
// (c3_open) (c4_open)
|
|
RefUpdate mtu = repo.updateRef("refs/tags/master-tag");
|
|
mtu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
mtu.setNewObjectId(repo.exactRef("refs/heads/master").getObjectId());
|
|
assertThat(mtu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
|
|
// rcMaster (c1 master master-tag) <-- rcBranch (c2 branch branch-tag)
|
|
// \ \
|
|
// (c3_open) (c4_open)
|
|
RefUpdate btu = repo.updateRef("refs/tags/branch-tag");
|
|
btu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
btu.setNewObjectId(repo.exactRef("refs/heads/branch").getObjectId());
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
|
|
// Create a tag for the tree of the commit on 'master'
|
|
// tree-tag -> master.tree
|
|
RefUpdate ttu = repo.updateRef("refs/tags/tree-tag");
|
|
ttu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
ttu.setNewObjectId(rcMaster.getTree().toObjectId());
|
|
assertThat(ttu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "false")
|
|
public void uploadPackAllRefsVisibleNoRefsMetaConfig() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(admins))
|
|
.setExclusiveGroup(permissionKey(Permission.READ).ref(RefNames.REFS_CONFIG), true)
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertUploadPackRefs(
|
|
"HEAD",
|
|
psRef1,
|
|
metaRef1,
|
|
psRef2,
|
|
metaRef2,
|
|
psRef3,
|
|
metaRef3,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/heads/master",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag");
|
|
// tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
|
|
}
|
|
|
|
@Test
|
|
@GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "true")
|
|
public void uploadPackAllRefsVisibleNoRefsMetaConfigSkipFullRefEval() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(admins))
|
|
.setExclusiveGroup(permissionKey(Permission.READ).ref(RefNames.REFS_CONFIG), true)
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertUploadPackRefs(
|
|
"HEAD",
|
|
psRef1,
|
|
metaRef1,
|
|
psRef2,
|
|
metaRef2,
|
|
psRef3,
|
|
metaRef3,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/heads/master",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag",
|
|
"refs/tags/tree-tag");
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackAllRefsVisibleWithRefsMetaConfig() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
assertUploadPackRefs(
|
|
"HEAD",
|
|
psRef1,
|
|
metaRef1,
|
|
psRef2,
|
|
metaRef2,
|
|
psRef3,
|
|
metaRef3,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/heads/master",
|
|
RefNames.REFS_CONFIG,
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag",
|
|
"refs/tags/tree-tag");
|
|
}
|
|
|
|
@Test
|
|
public void grantReadOnRefsTagsIsNoOp() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/tags/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertUploadPackRefs(); // We expect no refs returned
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackSubsetOfBranchesVisibleIncludingHead() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
|
|
.add(deny(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertUploadPackRefs(
|
|
"HEAD", psRef1, metaRef1, psRef3, metaRef3, "refs/heads/master", "refs/tags/master-tag");
|
|
// tree-tag is not visible because we don't look at trees reachable from
|
|
// refs
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackSubsetOfBranchesVisibleNotIncludingHead() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(deny(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// master branch is not visible but master-tag is reachable from branch
|
|
// (since PushOneCommit always bases changes on each other).
|
|
"refs/tags/master-tag");
|
|
// tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackSubsetOfBranchesVisibleWithEdit() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
// Admin's edit is not visible.
|
|
requestScopeOperations.setApiUser(admin.id());
|
|
gApi.changes().id(cd3.getId().get()).edit().create();
|
|
|
|
// User's edit is visible.
|
|
requestScopeOperations.setApiUser(user.id());
|
|
gApi.changes().id(cd3.getId().get()).edit().create();
|
|
|
|
assertUploadPackRefs(
|
|
"HEAD",
|
|
psRef1,
|
|
metaRef1,
|
|
psRef3,
|
|
metaRef3,
|
|
"refs/heads/master",
|
|
"refs/tags/master-tag",
|
|
"refs/users/01/1000001/edit-" + cd3.getId() + "/1");
|
|
// tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackSubsetOfBranchesAndEditsVisibleWithViewPrivateChanges() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
|
|
.add(allow(Permission.VIEW_PRIVATE_CHANGES).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
// Admin's edit on change3 is visible.
|
|
requestScopeOperations.setApiUser(admin.id());
|
|
gApi.changes().id(cd3.getId().get()).edit().create();
|
|
|
|
// Admin's edit on change4 is not visible since user cannot see the change.
|
|
gApi.changes().id(cd4.getId().get()).edit().create();
|
|
|
|
// User's edit is visible.
|
|
requestScopeOperations.setApiUser(user.id());
|
|
gApi.changes().id(cd3.getId().get()).edit().create();
|
|
|
|
assertUploadPackRefs(
|
|
"HEAD",
|
|
psRef1,
|
|
metaRef1,
|
|
psRef3,
|
|
metaRef3,
|
|
"refs/heads/master",
|
|
"refs/tags/master-tag",
|
|
"refs/users/00/1000000/edit-" + cd3.getId() + "/1",
|
|
"refs/users/01/1000001/edit-" + cd3.getId() + "/1");
|
|
// tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackSubsetOfRefsVisibleWithAccessDatabase() throws Exception {
|
|
projectOperations
|
|
.allProjectsForUpdate()
|
|
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
|
|
.update();
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(deny(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(admin.id());
|
|
gApi.changes().id(cd3.getId().get()).edit().create();
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
assertUploadPackRefs(
|
|
// Change 1 is visible due to accessDatabase capability, even though
|
|
// refs/heads/master is not.
|
|
psRef1,
|
|
metaRef1,
|
|
psRef2,
|
|
metaRef2,
|
|
psRef3,
|
|
metaRef3,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag",
|
|
// All edits are visible due to accessDatabase capability.
|
|
"refs/users/00/1000000/edit-" + cd3.getId() + "/1");
|
|
// tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackNoSearchingChangeCacheImplMaster() throws Exception {
|
|
uploadPackNoSearchingChangeCacheImpl();
|
|
}
|
|
|
|
@Test
|
|
@GerritConfig(name = "container.slave", value = "true")
|
|
public void uploadPackNoSearchingChangeCacheImplSlave() throws Exception {
|
|
uploadPackNoSearchingChangeCacheImpl();
|
|
}
|
|
|
|
private void uploadPackNoSearchingChangeCacheImpl() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertRefs(
|
|
project,
|
|
user,
|
|
// Can't use stored values from the index so DB must be enabled.
|
|
false,
|
|
"HEAD",
|
|
psRef1,
|
|
metaRef1,
|
|
psRef2,
|
|
metaRef2,
|
|
psRef3,
|
|
metaRef3,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/heads/master",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag");
|
|
// tree-tag not visible. See comment in subsetOfBranchesVisibleIncludingHead.
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackSequencesWithAccessDatabase() throws Exception {
|
|
assertRefs(allProjects, user, true);
|
|
|
|
projectOperations
|
|
.allProjectsForUpdate()
|
|
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
|
|
.update();
|
|
assertRefs(allProjects, user, true, "refs/sequences/changes");
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackAllRefsAreVisibleOrphanedTag() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
// Delete the pending change on 'branch' and 'branch' itself so that the tag gets orphaned
|
|
gApi.changes().id(cd4.getId().get()).delete();
|
|
gApi.projects().name(project.get()).branch("refs/heads/branch").delete();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertUploadPackRefs(
|
|
"HEAD",
|
|
"refs/meta/config",
|
|
psRef1,
|
|
metaRef1,
|
|
psRef2,
|
|
metaRef2,
|
|
psRef3,
|
|
metaRef3,
|
|
"refs/heads/master",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag",
|
|
"refs/tags/tree-tag");
|
|
}
|
|
|
|
@Test
|
|
public void uploadPackSubsetRefsVisibleOrphanedTagInvisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
// Create a tag for the pending change on 'branch' so that the tag is orphaned
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// change4-tag -> psRef4
|
|
RefUpdate ctu = repo.updateRef("refs/tags/change4-tag");
|
|
ctu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
ctu.setNewObjectId(repo.exactRef(psRef4).getObjectId());
|
|
assertThat(ctu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
}
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
}
|
|
|
|
// first ls-remote: rcMaster (c1 master)
|
|
// second ls-remote: rcMaster (c1 master) <- newchange1 (master-newtag)
|
|
@Test
|
|
public void uploadPackNewCommitOrphanTagInvisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
// rcMaster (c1 master)
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
PushOneCommit.Result r =
|
|
pushFactory.create(admin.newIdent(), testRepo).setParent(rcMaster).to("refs/for/master");
|
|
r.assertOkStatus();
|
|
|
|
// rcMaster (c1 master) <- newchange1 (master-newtag)
|
|
RefUpdate btu = repo.updateRef("refs/tags/master-newtag");
|
|
btu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
btu.setNewObjectId(r.getCommit());
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
|
|
// second ls-remote: rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
|
|
@Test
|
|
public void uploadPackNewReachableTagVisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// c2 <- newcommit1 (branch)
|
|
PushOneCommit.Result r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(rcBranch)
|
|
.to("refs/heads/branch");
|
|
r.assertOkStatus();
|
|
RevCommit tagRc = r.getCommit();
|
|
|
|
// c2 <- newcommit1 <- newcommit2 (branch)
|
|
r = pushFactory.create(admin.newIdent(), testRepo).setParent(tagRc).to("refs/heads/branch");
|
|
r.assertOkStatus();
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
|
|
// c2 <- newcommit1 (branch-newtag) <- newcommit2 (branch)
|
|
RefUpdate btu = repo.updateRef("refs/tags/branch-newtag");
|
|
btu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
btu.setNewObjectId(tagRc);
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/branch-newtag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2) <- newcommit1 (branch)
|
|
// second ls-remote: rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
|
|
// third ls-remote: rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
|
|
@Test
|
|
public void uploadPackBranchFFNewTagOldBranchVisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// rcBranch (c2) <- newcommit1 (branch)
|
|
PushOneCommit.Result r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(rcBranch)
|
|
.to("refs/heads/branch");
|
|
r.assertOkStatus();
|
|
RevCommit tagRc = r.getCommit();
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
|
|
// rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
|
|
r = pushFactory.create(admin.newIdent(), testRepo).setParent(tagRc).to("refs/heads/branch");
|
|
r.assertOkStatus();
|
|
|
|
// rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
|
|
RefUpdate btu = repo.updateRef("refs/tags/branch-newtag");
|
|
btu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
btu.setNewObjectId(tagRc);
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/branch-newtag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2) <- newcommit1 (branch-oldtag) <- newcommit2 (branch)
|
|
// second ls-remote: rcBranch (c2 branch) <- newcommit1 (branch-oldtag)
|
|
@Test
|
|
public void uploadPackBranchRewindMakeTagUnreachableInVisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// rcBranch (c2) <- newcommit1 (branch)
|
|
PushOneCommit.Result r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(rcBranch)
|
|
.to("refs/heads/branch");
|
|
r.assertOkStatus();
|
|
RevCommit tagRc = r.getCommit();
|
|
|
|
// rcBranch (c2) <- newcommit1 <- newcommit2 (branch)
|
|
r = pushFactory.create(admin.newIdent(), testRepo).setParent(tagRc).to("refs/heads/branch");
|
|
r.assertOkStatus();
|
|
RevCommit bRc = r.getCommit();
|
|
|
|
// rcBranch (c2) <- newcommit1 (branch-oldtag) <- newcommit2 (branch)
|
|
RefUpdate btu = repo.updateRef("refs/tags/branch-oldtag");
|
|
btu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
btu.setNewObjectId(tagRc);
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/branch-oldtag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
|
|
// rcBranch (c2 branch) <- newcommit1 (branch-oldtag) <- newcommit2
|
|
btu = repo.updateRef("refs/heads/branch");
|
|
btu.setExpectedOldObjectId(bRc);
|
|
btu.setNewObjectId(rcBranch);
|
|
btu.setForceUpdate(true);
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.FORCED);
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
// See comment in subsetOfBranchesVisibleNotIncludingHead.
|
|
"refs/tags/master-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag)
|
|
// second ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag) <- newcommit2 (new-branch)
|
|
@Test
|
|
public void uploadPackCreateBranchTagReachableVisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/new-branch").group(REGISTERED_USERS))
|
|
.add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// rcBranch (c2 branch) <- newcommit1 (branch-newtag)
|
|
PushOneCommit.Result r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(rcBranch)
|
|
.to("refs/tags/new-tag");
|
|
r.assertOkStatus();
|
|
RevCommit tagRc = r.getCommit();
|
|
|
|
assertUploadPackRefs();
|
|
|
|
// rcBranch (c2) <- newcommit1 (branch-newtag) <- newcommit2 (branch)
|
|
r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(tagRc)
|
|
.to("refs/heads/new-branch");
|
|
r.assertOkStatus();
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
"refs/heads/new-branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag",
|
|
"refs/tags/new-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2 branch) <- newcommit1 (updated-tag)
|
|
// second ls-remote: rcBranch (c2 branch updated-tag)
|
|
@Test
|
|
public void uploadPackTagUpdatedReachableVisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// rcBranch (c2 branch) <- newcommit1 (updated-tag)
|
|
PushOneCommit.Result r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(rcBranch)
|
|
.to("refs/tags/updated-tag");
|
|
r.assertOkStatus();
|
|
RevCommit tagRc = r.getCommit();
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag");
|
|
|
|
// rcBranch (c2 branch updated-tag)
|
|
RefUpdate btu = repo.updateRef("refs/tags/updated-tag");
|
|
btu.setExpectedOldObjectId(tagRc);
|
|
btu.setNewObjectId(rcBranch);
|
|
btu.setForceUpdate(true);
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.FORCED);
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag",
|
|
"refs/tags/updated-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2 branch updated-tag)
|
|
// second ls-remote: rcBranch (c2 branch) <- newcommit1 (updated-tag)
|
|
@Test
|
|
public void uploadPackTagUpdatedUnreachableInvisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// rcBranch (c2 branch updated-tag)
|
|
RefUpdate btu = repo.updateRef("refs/tags/updated-tag");
|
|
btu.setExpectedOldObjectId(ObjectId.zeroId());
|
|
btu.setNewObjectId(rcBranch);
|
|
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag",
|
|
"refs/tags/updated-tag");
|
|
|
|
// rcBranch (c2 branch) <- newcommit1 (updated-tag)
|
|
PushOneCommit.Result r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(rcBranch)
|
|
.to("refs/tags/updated-tag");
|
|
r.assertOkStatus();
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2 branch branch-tag)
|
|
// second ls-remote: rcBranch (c2 branch)
|
|
@Test
|
|
public void uploadPackTagDeleted() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.add(allow(Permission.DELETE).ref("refs/tags/branch-tag").group(REGISTERED_USERS))
|
|
.add(allow(Permission.PUSH).ref("refs/tags/branch-tag").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
// rcBranch (c2 branch branch-tag)
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag");
|
|
|
|
// rcBranch (c2 branch)
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
RefUpdate btu = repo.updateRef("refs/tags/branch-tag");
|
|
btu.setExpectedOldObjectId(rcBranch);
|
|
btu.setNewObjectId(ObjectId.zeroId());
|
|
btu.setForceUpdate(true);
|
|
assertThat(btu.delete()).isEqualTo(RefUpdate.Result.FORCED);
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2, metaRef2, psRef4, metaRef4, "refs/heads/branch", "refs/tags/master-tag");
|
|
}
|
|
|
|
// first ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag) <- newcommit2 (new-branch)
|
|
// second ls-remote: rcBranch (c2 branch) <- newcommit1 (new-tag)
|
|
@Test
|
|
public void uploadPackBranchDeleteTagUnreachableInvisible() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.add(allow(Permission.READ).ref("refs/heads/new-branch").group(REGISTERED_USERS))
|
|
.add(allow(Permission.DELETE).ref("refs/heads/new-branch").group(REGISTERED_USERS))
|
|
.add(allow(Permission.PUSH).ref("refs/tags/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
// rcBranch (branch) <- newcommit1 (new-tag)
|
|
PushOneCommit.Result r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(rcBranch)
|
|
.to("refs/tags/new-tag");
|
|
r.assertOkStatus();
|
|
RevCommit tagRc = r.getCommit();
|
|
|
|
// rcBranch (c2 branch) <- newcommit1 (new-tag) <- newcommit2 (new-branch)
|
|
r =
|
|
pushFactory
|
|
.create(admin.newIdent(), testRepo)
|
|
.setParent(tagRc)
|
|
.to("refs/heads/new-branch");
|
|
r.assertOkStatus();
|
|
}
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/heads/new-branch",
|
|
"refs/tags/new-tag",
|
|
"refs/tags/master-tag");
|
|
|
|
// rcBranch (c2 branch) <- newcommit1 (new-tag)
|
|
gApi.projects().name(project.get()).branch("refs/heads/new-branch").delete();
|
|
|
|
assertUploadPackRefs(
|
|
psRef2,
|
|
metaRef2,
|
|
psRef4,
|
|
metaRef4,
|
|
"refs/heads/branch",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag");
|
|
}
|
|
|
|
@Test
|
|
public void receivePackListsOpenChangesAsAdditionalHaves() throws Exception {
|
|
TestRefAdvertiser.Result r = getReceivePackRefs();
|
|
assertThat(r.allRefs().keySet())
|
|
.containsExactly(
|
|
// meta refs are excluded
|
|
"refs/heads/branch",
|
|
"refs/heads/master",
|
|
"refs/meta/config",
|
|
"refs/tags/branch-tag",
|
|
"refs/tags/master-tag",
|
|
"refs/tags/tree-tag");
|
|
assertThat(r.additionalHaves()).containsExactly(obj(cd3, 1), obj(cd4, 1));
|
|
}
|
|
|
|
@Test
|
|
public void receivePackRespectsVisibilityOfOpenChanges() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
|
|
.add(deny(Permission.READ).ref("refs/heads/branch").group(REGISTERED_USERS))
|
|
.update();
|
|
requestScopeOperations.setApiUser(user.id());
|
|
|
|
assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd3, 1));
|
|
}
|
|
|
|
@Test
|
|
public void receivePackListsOnlyLatestPatchSet() throws Exception {
|
|
testRepo.reset(obj(cd3, 1));
|
|
PushOneCommit.Result r = amendChange(cd3.change().getKey().get());
|
|
r.assertOkStatus();
|
|
cd3 = r.getChange();
|
|
assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd3, 2), obj(cd4, 1));
|
|
}
|
|
|
|
@Test
|
|
public void receivePackOmitsMissingObject() throws Exception {
|
|
String rev = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
|
|
try (Repository repo = repoManager.openRepository(project);
|
|
TestRepository<Repository> tr = new TestRepository<>(repo)) {
|
|
String subject = "Subject for missing commit";
|
|
Change c = new Change(cd3.change());
|
|
PatchSet.Id psId = PatchSet.id(cd3.getId(), 2);
|
|
c.setCurrentPatchSet(psId, subject, c.getOriginalSubject());
|
|
|
|
PersonIdent committer = serverIdent.get();
|
|
PersonIdent author =
|
|
noteUtil.newIdent(getAccount(admin.id()), committer.getWhen(), committer);
|
|
tr.branch(RefNames.changeMetaRef(cd3.getId()))
|
|
.commit()
|
|
.author(author)
|
|
.committer(committer)
|
|
.message(
|
|
"Update patch set "
|
|
+ psId.get()
|
|
+ "\n"
|
|
+ "\n"
|
|
+ "Patch-set: "
|
|
+ psId.get()
|
|
+ "\n"
|
|
+ "Commit: "
|
|
+ rev
|
|
+ "\n"
|
|
+ "Subject: "
|
|
+ subject
|
|
+ "\n")
|
|
.create();
|
|
indexer.index(c.getProject(), c.getId());
|
|
}
|
|
|
|
assertThat(getReceivePackRefs().additionalHaves()).containsExactly(obj(cd4, 1));
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesDontShowUserBranchWithoutRead() throws Exception {
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getUserRefs(git)).isEmpty();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesOmitUserBranchesOfOtherUsers() throws Exception {
|
|
projectOperations
|
|
.project(allUsersName)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref(RefNames.REFS_USERS + "*").group(REGISTERED_USERS))
|
|
.update();
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getUserRefs(git))
|
|
.containsExactly(RefNames.REFS_USERS_SELF, RefNames.refsUsers(user.id()));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesIncludeAllUserBranchesWithAccessDatabase() throws Exception {
|
|
projectOperations
|
|
.allProjectsForUpdate()
|
|
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
|
|
.update();
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getUserRefs(git))
|
|
.containsExactly(
|
|
RefNames.REFS_USERS_SELF,
|
|
RefNames.refsUsers(user.id()),
|
|
RefNames.refsUsers(admin.id()));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesDontShowGroupBranchToOwnerWithoutRead() throws Exception {
|
|
createSelfOwnedGroup("Foos", user);
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getGroupRefs(git)).isEmpty();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesOmitGroupBranchesOfNonOwnedGroups() throws Exception {
|
|
projectOperations
|
|
.project(allUsersName)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
|
|
.update();
|
|
AccountGroup.UUID users = createGroup("Users", admins, user);
|
|
AccountGroup.UUID foos = createGroup("Foos", users);
|
|
AccountGroup.UUID bars = createSelfOwnedGroup("Bars", user);
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getGroupRefs(git))
|
|
.containsExactly(RefNames.refsGroups(foos), RefNames.refsGroups(bars));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesIncludeAllGroupBranchesWithAccessDatabase() throws Exception {
|
|
projectOperations
|
|
.allProjectsForUpdate()
|
|
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
|
|
.update();
|
|
AccountGroup.UUID users = createGroup("Users", admins);
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getGroupRefs(git))
|
|
.containsExactly(
|
|
RefNames.refsGroups(admins),
|
|
RefNames.refsGroups(nonInteractiveUsers),
|
|
RefNames.refsGroups(users));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesIncludeAllGroupBranchesForAdmins() throws Exception {
|
|
projectOperations
|
|
.project(allUsersName)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref(RefNames.REFS_GROUPS + "*").group(REGISTERED_USERS))
|
|
.update();
|
|
projectOperations
|
|
.allProjectsForUpdate()
|
|
.add(allowCapability(GlobalCapability.ADMINISTRATE_SERVER).group(REGISTERED_USERS))
|
|
.update();
|
|
AccountGroup.UUID users = createGroup("Users", admins);
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getGroupRefs(git))
|
|
.containsExactly(
|
|
RefNames.refsGroups(admins),
|
|
RefNames.refsGroups(nonInteractiveUsers),
|
|
RefNames.refsGroups(users));
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesOmitNoteDbNotesBranches() throws Exception {
|
|
projectOperations
|
|
.project(allUsersName)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref(RefNames.REFS + "*").group(REGISTERED_USERS))
|
|
.update();
|
|
TestRepository<?> userTestRepository = cloneProject(allUsers, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
assertThat(getRefs(git)).containsNoneOf(RefNames.REFS_EXTERNAL_IDS, RefNames.REFS_GROUPNAMES);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesOmitPrivateChangesOfOtherUsers() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
TestRepository<?> userTestRepository = cloneProject(project, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
String change3RefName = cd3.currentPatchSet().refName();
|
|
assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName);
|
|
|
|
gApi.changes().id(cd3.getId().get()).setPrivate(true, null);
|
|
assertThat(getRefs(git)).doesNotContain(change3RefName);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesIncludePrivateChangesWhenAllRefsMayBeRead() throws Exception {
|
|
assume()
|
|
.that(baseConfig.getBoolean("auth", "skipFullRefEvaluationIfAllRefsAreVisible", true))
|
|
.isTrue();
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
TestRepository<?> userTestRepository = cloneProject(project, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
String change3RefName = cd3.currentPatchSet().refName();
|
|
assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName);
|
|
|
|
gApi.changes().id(cd3.getId().get()).setPrivate(true, null);
|
|
assertThat(getRefs(git)).contains(change3RefName);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@GerritConfig(name = "auth.skipFullRefEvaluationIfAllRefsAreVisible", value = "false")
|
|
public void advertisedReferencesOmitPrivateChangesOfOtherUsersWhenShortcutDisabled()
|
|
throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
TestRepository<?> userTestRepository = cloneProject(project, user);
|
|
try (Git git = userTestRepository.git()) {
|
|
String change3RefName = cd3.currentPatchSet().refName();
|
|
assertWithMessage("Precondition violated").that(getRefs(git)).contains(change3RefName);
|
|
|
|
gApi.changes().id(cd3.getId().get()).setPrivate(true, null);
|
|
assertThat(getRefs(git)).doesNotContain(change3RefName);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesOmitDraftCommentRefsOfOtherUsers() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
projectOperations
|
|
.project(allUsersName)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
DraftInput draftInput = new DraftInput();
|
|
draftInput.line = 1;
|
|
draftInput.message = "nit: trailing whitespace";
|
|
draftInput.path = Patch.COMMIT_MSG;
|
|
gApi.changes().id(cd3.getId().get()).current().createDraft(draftInput);
|
|
String draftCommentRef = RefNames.refsDraftComments(cd3.getId(), user.id());
|
|
|
|
// user can see the draft comment ref of the own draft comment
|
|
assertThat(lsRemote(allUsersName, user)).contains(draftCommentRef);
|
|
|
|
// user2 can't see the draft comment ref of user's draft comment
|
|
assertThat(lsRemote(allUsersName, accountCreator.user2())).doesNotContain(draftCommentRef);
|
|
}
|
|
|
|
@Test
|
|
public void advertisedReferencesOmitStarredChangesRefsOfOtherUsers() throws Exception {
|
|
projectOperations
|
|
.project(project)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
projectOperations
|
|
.project(allUsersName)
|
|
.forUpdate()
|
|
.add(allow(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
|
|
.update();
|
|
|
|
requestScopeOperations.setApiUser(user.id());
|
|
gApi.accounts().self().starChange(cd3.getId().toString());
|
|
String starredChangesRef = RefNames.refsStarredChanges(cd3.getId(), user.id());
|
|
|
|
// user can see the starred changes ref of the own star
|
|
assertThat(lsRemote(allUsersName, user)).contains(starredChangesRef);
|
|
|
|
// user2 can't see the starred changes ref of admin's star
|
|
assertThat(lsRemote(allUsersName, accountCreator.user2())).doesNotContain(starredChangesRef);
|
|
}
|
|
|
|
@Test
|
|
public void hideMetadata() throws Exception {
|
|
projectOperations
|
|
.allProjectsForUpdate()
|
|
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
|
|
.update();
|
|
// create change
|
|
TestRepository<?> allUsersRepo = cloneProject(allUsers);
|
|
fetch(allUsersRepo, RefNames.REFS_USERS_SELF + ":userRef");
|
|
allUsersRepo.reset("userRef");
|
|
PushOneCommit.Result mr =
|
|
pushFactory
|
|
.create(admin.newIdent(), allUsersRepo)
|
|
.to("refs/for/" + RefNames.REFS_USERS_SELF);
|
|
mr.assertOkStatus();
|
|
|
|
List<String> expectedNonMetaRefs =
|
|
ImmutableList.of(
|
|
RefNames.refsUsers(admin.id()),
|
|
RefNames.refsUsers(user.id()),
|
|
RefNames.REFS_EXTERNAL_IDS,
|
|
RefNames.REFS_GROUPNAMES,
|
|
RefNames.refsGroups(admins),
|
|
RefNames.refsGroups(nonInteractiveUsers),
|
|
RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS,
|
|
RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS,
|
|
RefNames.REFS_CONFIG,
|
|
Constants.HEAD);
|
|
|
|
List<String> expectedMetaRefs =
|
|
new ArrayList<>(ImmutableList.of(mr.getPatchSetId().toRefName()));
|
|
expectedMetaRefs.add(RefNames.changeMetaRef(mr.getChange().getId()));
|
|
|
|
List<String> expectedAllRefs = new ArrayList<>(expectedNonMetaRefs);
|
|
expectedAllRefs.addAll(expectedMetaRefs);
|
|
|
|
try (Repository repo = repoManager.openRepository(allUsers)) {
|
|
Map<String, Ref> all = getAllRefs(repo);
|
|
|
|
PermissionBackend.ForProject forProject = newFilter(allUsers, admin);
|
|
assertThat(forProject.filter(all, repo, RefFilterOptions.defaults()).keySet())
|
|
.containsExactlyElementsIn(expectedAllRefs);
|
|
assertThat(
|
|
forProject
|
|
.filter(all, repo, RefFilterOptions.builder().setFilterMeta(true).build())
|
|
.keySet())
|
|
.containsExactlyElementsIn(expectedNonMetaRefs);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void fetchSingleChangeWithoutIndexAccess() throws Exception {
|
|
PushOneCommit.Result change = createChange();
|
|
String patchSetRef = change.getPatchSetId().toRefName();
|
|
try (AutoCloseable ignored = disableChangeIndex();
|
|
Repository repo = repoManager.openRepository(project)) {
|
|
Map<String, Ref> singleRef = ImmutableMap.of(patchSetRef, repo.exactRef(patchSetRef));
|
|
Map<String, Ref> filteredRefs =
|
|
permissionBackend
|
|
.user(user(admin))
|
|
.project(project)
|
|
.filter(singleRef, repo, RefFilterOptions.defaults());
|
|
assertThat(filteredRefs).isEqualTo(singleRef);
|
|
}
|
|
}
|
|
|
|
private List<String> lsRemote(Project.NameKey p, TestAccount a) throws Exception {
|
|
TestRepository<?> testRepository = cloneProject(p, a);
|
|
try (Git git = testRepository.git()) {
|
|
return git.lsRemote().call().stream().map(Ref::getName).collect(toList());
|
|
}
|
|
}
|
|
|
|
private List<String> getRefs(Git git) throws Exception {
|
|
return getRefs(git, x -> true);
|
|
}
|
|
|
|
private List<String> getUserRefs(Git git) throws Exception {
|
|
return getRefs(git, RefNames::isRefsUsers);
|
|
}
|
|
|
|
private List<String> getGroupRefs(Git git) throws Exception {
|
|
return getRefs(git, RefNames::isRefsGroups);
|
|
}
|
|
|
|
private List<String> getRefs(Git git, Predicate<String> predicate) throws Exception {
|
|
return git.lsRemote().call().stream().map(Ref::getName).filter(predicate).collect(toList());
|
|
}
|
|
|
|
/**
|
|
* Assert that refs seen by a non-admin user match the expected refs.
|
|
*
|
|
* @param expectedRefs expected refs.
|
|
* @throws Exception
|
|
*/
|
|
private void assertUploadPackRefs(String... expectedRefs) throws Exception {
|
|
assertRefs(project, user, true, expectedRefs);
|
|
}
|
|
|
|
private void assertRefs(
|
|
Project.NameKey project, TestAccount user, boolean disableDb, String... expectedRefs)
|
|
throws Exception {
|
|
AutoCloseable ctx = null;
|
|
if (disableDb) {
|
|
ctx = disableNoteDb();
|
|
}
|
|
try {
|
|
assertThat(lsRemote(project, user)).containsExactlyElementsIn(expectedRefs);
|
|
} finally {
|
|
if (disableDb) {
|
|
ctx.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
private TestRefAdvertiser.Result getReceivePackRefs() throws Exception {
|
|
try (Repository repo = repoManager.openRepository(project)) {
|
|
AdvertiseRefsHook adv =
|
|
ReceiveCommitsAdvertiseRefsHookChain.createForTest(
|
|
queryProvider, project, identifiedUserFactory.create(admin.id()));
|
|
ReceivePack rp = new ReceivePack(repo);
|
|
rp.setAdvertiseRefsHook(adv);
|
|
TestRefAdvertiser advertiser = new TestRefAdvertiser(repo);
|
|
rp.sendAdvertisedRefs(advertiser);
|
|
return advertiser.result();
|
|
}
|
|
}
|
|
|
|
private PermissionBackend.ForProject newFilter(Project.NameKey project, TestAccount u) {
|
|
return permissionBackend.user(user(u)).project(project);
|
|
}
|
|
|
|
private static ObjectId obj(ChangeData cd, int psNum) throws Exception {
|
|
PatchSet.Id psId = PatchSet.id(cd.getId(), psNum);
|
|
PatchSet ps = cd.patchSet(psId);
|
|
assertWithMessage("%s not found in %s", psId, cd.patchSets()).that(ps).isNotNull();
|
|
return ps.commitId();
|
|
}
|
|
|
|
private AccountGroup.UUID createSelfOwnedGroup(String name, TestAccount... members)
|
|
throws RestApiException {
|
|
return createGroup(name, null, members);
|
|
}
|
|
|
|
private AccountGroup.UUID createGroup(
|
|
String name, @Nullable AccountGroup.UUID ownerGroup, TestAccount... members)
|
|
throws RestApiException {
|
|
GroupInput groupInput = new GroupInput();
|
|
groupInput.name = name(name);
|
|
groupInput.ownerId = ownerGroup != null ? ownerGroup.get() : null;
|
|
groupInput.members =
|
|
Arrays.stream(members).map(m -> String.valueOf(m.id().get())).collect(toList());
|
|
return AccountGroup.uuid(gApi.groups().create(groupInput).get().id);
|
|
}
|
|
|
|
private static Map<String, Ref> getAllRefs(Repository repo) throws IOException {
|
|
return repo.getRefDatabase().getRefs().stream()
|
|
.collect(toMap(Ref::getName, Function.identity()));
|
|
}
|
|
}
|