blob: 8724b9a9c709ee68d914a4a0a21b91fc70ed1265 [file] [log] [blame]
// Copyright (C) 2020 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.submit;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.SubmoduleSubscription;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import java.io.IOException;
import java.util.Optional;
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.errors.RepositoryNotFoundException;
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.RevWalk;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
public class SubmoduleCommitsTest {
private static final String MASTER = "refs/heads/master";
private static final Project.NameKey superProject = Project.nameKey("superproject");
private static final Project.NameKey subProject = Project.nameKey("subproject");
private static final PersonIdent ident = new PersonIdent("submodule-test", "a@b.com");
private InMemoryRepositoryManager repoManager = new InMemoryRepositoryManager();
private MergeOpRepoManager mergeOpRepoManager;
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
@Mock private ProjectCache mockProjectCache;
@Mock private ProjectState mockProjectState;
@Test
public void createGitlinksCommit_subprojectMoved() throws Exception {
createRepo(subProject, MASTER);
createRepo(superProject, MASTER);
when(mockProjectCache.get(any())).thenReturn(Optional.of(mockProjectState));
mergeOpRepoManager = new MergeOpRepoManager(repoManager, mockProjectCache, null, null);
ObjectId subprojectCommit = getTip(subProject, MASTER);
RevCommit superprojectTip =
directUpdateSubmodule(superProject, MASTER, Project.nameKey("dir-x"), subprojectCommit);
assertThat(readGitLink(superProject, superprojectTip, "dir-x")).isEqualTo(subprojectCommit);
RevCommit newSubprojectCommit = addCommit(subProject, MASTER);
BranchNameKey superBranch = BranchNameKey.create(superProject, MASTER);
BranchNameKey subBranch = BranchNameKey.create(subProject, MASTER);
SubmoduleSubscription ss = new SubmoduleSubscription(superBranch, subBranch, "dir-x");
SubmoduleCommits helper = new SubmoduleCommits(mergeOpRepoManager, ident, new Config());
Optional<CodeReviewCommit> newGitLinksCommit =
helper.composeGitlinksCommit(
BranchNameKey.create(superProject, MASTER), ImmutableList.of(ss));
assertThat(newGitLinksCommit).isPresent();
assertThat(newGitLinksCommit.get().getParent(0)).isEqualTo(superprojectTip);
assertThat(readGitLink(superProject, newGitLinksCommit.get(), "dir-x"))
.isEqualTo(newSubprojectCommit);
}
@Test
public void amendGitlinksCommit_subprojectMoved() throws Exception {
createRepo(subProject, MASTER);
createRepo(superProject, MASTER);
when(mockProjectCache.get(any())).thenReturn(Optional.of(mockProjectState));
mergeOpRepoManager = new MergeOpRepoManager(repoManager, mockProjectCache, null, null);
ObjectId subprojectCommit = getTip(subProject, MASTER);
CodeReviewCommit superprojectTip =
directUpdateSubmodule(superProject, MASTER, Project.nameKey("dir-x"), subprojectCommit);
assertThat(readGitLink(superProject, superprojectTip, "dir-x")).isEqualTo(subprojectCommit);
RevCommit newSubprojectCommit = addCommit(subProject, MASTER);
BranchNameKey superBranch = BranchNameKey.create(superProject, MASTER);
BranchNameKey subBranch = BranchNameKey.create(subProject, MASTER);
SubmoduleSubscription ss = new SubmoduleSubscription(superBranch, subBranch, "dir-x");
SubmoduleCommits helper = new SubmoduleCommits(mergeOpRepoManager, ident, new Config());
CodeReviewCommit amendedCommit =
helper.amendGitlinksCommit(
BranchNameKey.create(superProject, MASTER), superprojectTip, ImmutableList.of(ss));
assertThat(amendedCommit.getParent(0)).isEqualTo(superprojectTip.getParent(0));
assertThat(readGitLink(superProject, amendedCommit, "dir-x")).isEqualTo(newSubprojectCommit);
}
/** Create repo with a commit on refName */
private void createRepo(Project.NameKey projectKey, String refName) throws Exception {
Repository repo = repoManager.createRepository(projectKey);
try (TestRepository<Repository> git = new TestRepository<>(repo)) {
RevCommit newCommit = git.commit().message("Initial commit for " + projectKey).create();
git.update(refName, newCommit);
}
}
private ObjectId getTip(Project.NameKey projectKey, String refName)
throws RepositoryNotFoundException, IOException {
return repoManager.openRepository(projectKey).exactRef(refName).getObjectId();
}
private RevCommit addCommit(Project.NameKey projectKey, String refName) throws Exception {
try (Repository serverRepo = repoManager.openRepository(projectKey);
RevWalk rw = new RevWalk(serverRepo);
TestRepository<Repository> git = new TestRepository<>(serverRepo, rw)) {
Ref ref = serverRepo.exactRef(refName);
assertWithMessage(refName).that(ref).isNotNull();
RevCommit originalTip = rw.parseCommit(ref.getObjectId());
RevCommit newTip =
git.commit().parent(originalTip).message("Added commit to " + projectKey).create();
git.update(refName, newTip);
return newTip;
}
}
private CodeReviewCommit directUpdateSubmodule(
Project.NameKey project, String refName, Project.NameKey path, AnyObjectId id)
throws Exception {
OpenRepo or = mergeOpRepoManager.getRepo(project);
Repository serverRepo = or.repo;
ObjectInserter ins = or.ins;
CodeReviewRevWalk rw = or.rw;
Ref ref = serverRepo.exactRef(refName);
assertWithMessage(refName).that(ref).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.get()) {
@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));
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);
testRefAction(() -> assertThat(ru.update()).isEqualTo(RefUpdate.Result.FAST_FORWARD));
return rw.parseCommit(newCommitId);
}
private ObjectId readGitLink(Project.NameKey projectKey, RevCommit commit, String path)
throws IOException, NoSuchProjectException {
// SubmoduleCommitHelper used mergeOpRepoManager to create the commit
// Read the repo from mergeOpRepoManager to get also the RevWalk that created the commit
return readGitLinkInCommit(mergeOpRepoManager.getRepo(projectKey).rw, commit, path);
}
private ObjectId readGitLinkInCommit(RevWalk rw, RevCommit commit, String path)
throws IOException {
DirCache dc = DirCache.newInCore();
DirCacheBuilder b = dc.builder();
b.addTree(
new byte[0], // no prefix path
DirCacheEntry.STAGE_0, // standard stage
rw.getObjectReader(),
commit.getTree());
b.finish();
DirCacheEntry entry = dc.getEntry(path);
assertThat(entry.getFileMode()).isEqualTo(FileMode.GITLINK);
return entry.getObjectId();
}
}