blob: 763b25fe8ef71ea4cbde56f5edfd8e1f7525b5cc [file] [log] [blame]
// Copyright (C) 2013 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.gerrit.acceptance.GitUtil.cloneProject;
import static com.google.gerrit.acceptance.GitUtil.createProject;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.SshSession;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.git.CommitMergeStatus;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@NoHttpd
public class SubmitOnPushIT extends AbstractDaemonTest {
@Inject
private SchemaFactory<ReviewDb> reviewDbProvider;
@Inject
private GitRepositoryManager repoManager;
@Inject
private ApprovalsUtil approvalsUtil;
@Inject
private MetaDataUpdate.Server metaDataUpdateFactory;
@Inject
private ProjectCache projectCache;
@Inject
private GroupCache groupCache;
@Inject
private ChangeNotes.Factory changeNotesFactory;
@Inject
private @GerritPersonIdent PersonIdent serverIdent;
@Inject
private PushOneCommit.Factory pushFactory;
private Project.NameKey project;
private Git git;
private ReviewDb db;
@Before
public void setUp() throws Exception {
project = new Project.NameKey("p");
SshSession sshSession = new SshSession(server, admin);
createProject(sshSession, project.get());
git = cloneProject(sshSession.getUrl() + "/" + project.get());
sshSession.close();
db = reviewDbProvider.open();
}
@After
public void cleanup() {
db.close();
}
@Test
public void submitOnPush() throws GitAPIException, OrmException,
IOException, ConfigInvalidException {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
PushOneCommit.Result r = pushTo("refs/for/master%submit");
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
assertSubmitApproval(r.getPatchSetId());
assertCommit(project, "refs/heads/master");
}
@Test
public void submitOnPushWithTag() throws GitAPIException, OrmException,
IOException, ConfigInvalidException {
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
grant(Permission.CREATE, project, "refs/tags/*");
final String tag = "v1.0";
PushOneCommit push = pushFactory.create(db, admin.getIdent());
push.setTag(tag);
PushOneCommit.Result r = push.to(git, "refs/for/master%submit");
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
assertSubmitApproval(r.getPatchSetId());
assertCommit(project, "refs/heads/master");
assertTag(project, "refs/heads/master", tag);
}
@Test
public void submitOnPushToRefsMetaConfig() throws GitAPIException,
OrmException, IOException, ConfigInvalidException {
grant(Permission.SUBMIT, project, "refs/for/refs/meta/config");
git.fetch().setRefSpecs(new RefSpec("refs/meta/config:refs/meta/config")).call();
ObjectId objectId = git.getRepository().getRef("refs/meta/config").getObjectId();
git.checkout().setName(objectId.getName()).call();
PushOneCommit.Result r = pushTo("refs/for/refs/meta/config%submit");
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
assertSubmitApproval(r.getPatchSetId());
assertCommit(project, "refs/meta/config");
}
@Test
public void submitOnPushMergeConflict() throws GitAPIException, OrmException,
IOException, ConfigInvalidException {
String master = "refs/heads/master";
ObjectId objectId = git.getRepository().getRef(master).getObjectId();
push(master, "one change", "a.txt", "some content");
git.checkout().setName(objectId.getName()).call();
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
PushOneCommit.Result r =
push("refs/for/master%submit", "other change", "a.txt", "other content");
r.assertOkStatus();
r.assertChange(Change.Status.NEW, null, admin);
r.assertMessage(CommitMergeStatus.PATH_CONFLICT.getMessage());
}
@Test
public void submitOnPushSuccessfulMerge() throws GitAPIException, OrmException,
IOException, ConfigInvalidException {
String master = "refs/heads/master";
ObjectId objectId = git.getRepository().getRef(master).getObjectId();
push(master, "one change", "a.txt", "some content");
git.checkout().setName(objectId.getName()).call();
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
PushOneCommit.Result r =
push("refs/for/master%submit", "other change", "b.txt", "other content");
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
assertMergeCommit(master, "other change");
}
@Test
public void submitOnPushNewPatchSet() throws GitAPIException,
OrmException, IOException, ConfigInvalidException {
PushOneCommit.Result r =
push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
grant(Permission.SUBMIT, project, "refs/for/refs/heads/master");
r = push("refs/for/master%submit", PushOneCommit.SUBJECT, "a.txt",
"other content", r.getChangeId());
r.assertOkStatus();
r.assertChange(Change.Status.MERGED, null, admin);
Change c = Iterables.getOnlyElement(db.changes().byKey(
new Change.Key(r.getChangeId())).toList());
assertEquals(2, db.patchSets().byChange(c.getId()).toList().size());
assertSubmitApproval(r.getPatchSetId());
assertCommit(project, "refs/heads/master");
}
@Test
public void submitOnPushNotAllowed_Error() throws GitAPIException,
OrmException, IOException {
PushOneCommit.Result r = pushTo("refs/for/master%submit");
r.assertErrorStatus("submit not allowed");
}
@Test
public void submitOnPushNewPatchSetNotAllowed_Error() throws GitAPIException,
OrmException, IOException, ConfigInvalidException {
PushOneCommit.Result r =
push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
r = push("refs/for/master%submit", PushOneCommit.SUBJECT, "a.txt",
"other content", r.getChangeId());
r.assertErrorStatus("submit not allowed");
}
@Test
public void submitOnPushingDraft_Error() throws GitAPIException,
OrmException, IOException {
PushOneCommit.Result r = pushTo("refs/for/master%draft,submit");
r.assertErrorStatus("cannot submit draft");
}
@Test
public void submitOnPushToNonExistingBranch_Error() throws GitAPIException,
OrmException, IOException {
String branchName = "non-existing";
PushOneCommit.Result r = pushTo("refs/for/" + branchName + "%submit");
r.assertErrorStatus("branch " + branchName + " not found");
}
@Test
public void mergeOnPushToBranch() throws Exception {
grant(Permission.PUSH, project, "refs/heads/master");
PushOneCommit.Result r =
push("refs/for/master", PushOneCommit.SUBJECT, "a.txt", "some content");
r.assertOkStatus();
git.push()
.setRefSpecs(new RefSpec(r.getCommitId().name() + ":refs/heads/master"))
.call();
assertCommit(project, "refs/heads/master");
assertNull(getSubmitter(r.getPatchSetId()));
Change c = db.changes().get(r.getPatchSetId().getParentKey());
assertEquals(Change.Status.MERGED, c.getStatus());
}
private void grant(String permission, Project.NameKey project, String ref)
throws RepositoryNotFoundException, IOException, ConfigInvalidException {
MetaDataUpdate md = metaDataUpdateFactory.create(project);
md.setMessage(String.format("Grant %s on %s", permission, ref));
ProjectConfig config = ProjectConfig.read(md);
AccessSection s = config.getAccessSection(ref, true);
Permission p = s.getPermission(permission, true);
AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators"));
p.add(new PermissionRule(config.resolve(adminGroup)));
config.commit(md);
projectCache.evict(config.getProject());
}
private PatchSetApproval getSubmitter(PatchSet.Id patchSetId)
throws OrmException {
Change c = db.changes().get(patchSetId.getParentKey());
ChangeNotes notes = changeNotesFactory.create(c).load();
return approvalsUtil.getSubmitter(db, notes, patchSetId);
}
private void assertSubmitApproval(PatchSet.Id patchSetId) throws OrmException {
PatchSetApproval a = getSubmitter(patchSetId);
assertTrue(a.isSubmit());
assertEquals(1, a.getValue());
assertEquals(admin.id, a.getAccountId());
}
private void assertCommit(Project.NameKey project, String branch) throws IOException {
Repository r = repoManager.openRepository(project);
try {
RevWalk rw = new RevWalk(r);
try {
RevCommit c = rw.parseCommit(r.getRef(branch).getObjectId());
assertEquals(PushOneCommit.SUBJECT, c.getShortMessage());
assertEquals(admin.email, c.getAuthorIdent().getEmailAddress());
assertEquals(admin.email, c.getCommitterIdent().getEmailAddress());
} finally {
rw.close();
}
} finally {
r.close();
}
}
private void assertMergeCommit(String branch, String subject) throws IOException {
Repository r = repoManager.openRepository(project);
try {
RevWalk rw = new RevWalk(r);
try {
RevCommit c = rw.parseCommit(r.getRef(branch).getObjectId());
assertEquals(2, c.getParentCount());
assertEquals("Merge \"" + subject + "\"", c.getShortMessage());
assertEquals(admin.email, c.getAuthorIdent().getEmailAddress());
assertEquals(serverIdent.getEmailAddress(), c.getCommitterIdent().getEmailAddress());
} finally {
rw.close();
}
} finally {
r.close();
}
}
private void assertTag(Project.NameKey project, String branch, String tagName)
throws IOException {
Repository r = repoManager.openRepository(project);
try {
ObjectId headCommit = r.getRef(branch).getObjectId();
ObjectId taggedCommit = r.getRef(tagName).getObjectId();
assertEquals(headCommit, taggedCommit);
} finally {
r.close();
}
}
private PushOneCommit.Result pushTo(String ref) throws GitAPIException,
IOException {
PushOneCommit push = pushFactory.create(db, admin.getIdent());
return push.to(git, ref);
}
private PushOneCommit.Result push(String ref, String subject,
String fileName, String content) throws GitAPIException, IOException {
PushOneCommit push =
pushFactory.create(db, admin.getIdent(), subject, fileName, content);
return push.to(git, ref);
}
private PushOneCommit.Result push(String ref, String subject,
String fileName, String content, String changeId) throws GitAPIException,
IOException {
PushOneCommit push = pushFactory.create(db, admin.getIdent(), subject,
fileName, content, changeId);
return push.to(git, ref);
}
}