| // 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.common.truth.Truth8.assertThat; |
| 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(); |
| } |
| } |