Line data Source code
1 : // Copyright (C) 2020 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.patch; 16 : 17 : import com.google.gerrit.common.Nullable; 18 : import com.google.gerrit.entities.Project; 19 : import com.google.gerrit.entities.RefNames; 20 : import com.google.gerrit.server.config.GerritServerConfig; 21 : import com.google.gerrit.server.git.GitRepositoryManager; 22 : import com.google.gerrit.server.git.InMemoryInserter; 23 : import com.google.gerrit.server.update.RepoView; 24 : import com.google.inject.Inject; 25 : import com.google.inject.Singleton; 26 : import java.io.IOException; 27 : import java.util.Optional; 28 : import org.eclipse.jgit.lib.Config; 29 : import org.eclipse.jgit.lib.Constants; 30 : import org.eclipse.jgit.lib.ObjectId; 31 : import org.eclipse.jgit.lib.ObjectInserter; 32 : import org.eclipse.jgit.lib.ObjectReader; 33 : import org.eclipse.jgit.lib.Repository; 34 : import org.eclipse.jgit.revwalk.RevCommit; 35 : import org.eclipse.jgit.revwalk.RevObject; 36 : import org.eclipse.jgit.revwalk.RevWalk; 37 : 38 : /** A utility class for computing the base commit / parent for a specific patchset commit. */ 39 : @Singleton 40 : class BaseCommitUtil { 41 : private final AutoMerger autoMerger; 42 : private final GitRepositoryManager repoManager; 43 : 44 : /** If true, auto-merge results are stored in the repository. */ 45 : private final boolean saveAutomerge; 46 : 47 : @Inject 48 152 : BaseCommitUtil(AutoMerger am, @GerritServerConfig Config cfg, GitRepositoryManager repoManager) { 49 152 : this.autoMerger = am; 50 152 : this.saveAutomerge = AutoMerger.cacheAutomerge(cfg); 51 152 : this.repoManager = repoManager; 52 152 : } 53 : 54 : RevObject getBaseCommit(Project.NameKey project, ObjectId newCommit, @Nullable Integer parentNum) 55 : throws IOException { 56 100 : try (Repository repo = repoManager.openRepository(project); 57 100 : ObjectInserter ins = newInserter(repo); 58 100 : ObjectReader reader = ins.newReader(); 59 100 : RevWalk rw = new RevWalk(reader)) { 60 100 : return getParentCommit(repo, ins, rw, parentNum, newCommit); 61 : } 62 : } 63 : 64 : /** 65 : * Returns the number of parent commits of the commit represented by the commitId parameter. 66 : * 67 : * @param project a specific git repository. 68 : * @param commitId 20 bytes commitId SHA-1 hash. 69 : * @return an integer representing the number of parents of the designated commit. 70 : */ 71 : int getNumParents(Project.NameKey project, ObjectId commitId) throws IOException { 72 104 : try (Repository repo = repoManager.openRepository(project); 73 104 : ObjectInserter ins = repo.newObjectInserter(); 74 104 : ObjectReader reader = ins.newReader(); 75 104 : RevWalk rw = new RevWalk(reader)) { 76 104 : RevCommit current = rw.parseCommit(commitId); 77 104 : return current.getParentCount(); 78 : } 79 : } 80 : 81 : /** 82 : * Returns the parent commit Object of the commit represented by the commitId parameter. 83 : * 84 : * @param repo a git repository. 85 : * @param ins a git object inserter in the database. 86 : * @param rw a {@link RevWalk} object of the repository. 87 : * @param parentNum used to identify the parent number for merge commits. If parentNum is null and 88 : * {@code commitId} has two parents, the auto-merge commit will be returned. If {@code 89 : * commitId} has a single parent, it will be returned. 90 : * @param commitId 20 bytes commitId SHA-1 hash. 91 : * @return Returns the parent commit of the commit represented by the commitId parameter. Note 92 : * that auto-merge is not supported for commits having more than two parents. 93 : */ 94 : @Nullable 95 : RevObject getParentCommit( 96 : Repository repo, 97 : ObjectInserter ins, 98 : RevWalk rw, 99 : @Nullable Integer parentNum, 100 : ObjectId commitId) 101 : throws IOException { 102 100 : RevCommit current = rw.parseCommit(commitId); 103 100 : switch (current.getParentCount()) { 104 : case 0: 105 0 : return rw.parseAny(emptyTree(ins)); 106 : case 1: 107 100 : return current.getParent(0); 108 : default: 109 31 : if (parentNum != null) { 110 10 : RevCommit r = current.getParent(parentNum - 1); 111 10 : rw.parseBody(r); 112 10 : return r; 113 : } 114 : // Only support auto-merge for 2 parents, not octopus merges 115 31 : if (current.getParentCount() == 2) { 116 31 : if (!saveAutomerge) { 117 0 : throw new IOException( 118 : "diff against auto-merge commits is only supported if 'change.cacheAutomerge' config is set to true."); 119 : } 120 : // TODO(ghareeb): Avoid persisting auto-merge commits. 121 31 : return getAutoMergeFromGitOrCreate(repo, ins, rw, current); 122 : } 123 0 : return null; 124 : } 125 : } 126 : 127 : /** 128 : * Gets the auto-merge commit from git if it already exists. If not, the auto-merge is created, 129 : * persisted in git and the cache-automerge ref is updated for the merge commit. 130 : * 131 : * @return the auto-merge {@link RevCommit} 132 : */ 133 : private RevCommit getAutoMergeFromGitOrCreate( 134 : Repository repo, ObjectInserter ins, RevWalk rw, RevCommit mergeCommit) throws IOException { 135 31 : String refName = RefNames.refsCacheAutomerge(mergeCommit.name()); 136 31 : Optional<RevCommit> autoMergeCommit = autoMerger.lookupCommit(repo, rw, refName); 137 31 : if (autoMergeCommit.isPresent()) { 138 30 : return autoMergeCommit.get(); 139 : } 140 1 : ObjectId autoMergeId = 141 1 : autoMerger.createAutoMergeCommit(new RepoView(repo, rw, ins), rw, ins, mergeCommit); 142 1 : ins.flush(); 143 1 : return rw.parseCommit(autoMergeId); 144 : } 145 : 146 : private ObjectInserter newInserter(Repository repo) { 147 100 : return saveAutomerge ? repo.newObjectInserter() : new InMemoryInserter(repo); 148 : } 149 : 150 : private static ObjectId emptyTree(ObjectInserter ins) throws IOException { 151 0 : ObjectId id = ins.insert(Constants.OBJ_TREE, new byte[] {}); 152 0 : ins.flush(); 153 0 : return id; 154 : } 155 : }