Line data Source code
1 : // Copyright (C) 2013 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.restapi.change;
16 :
17 : import static com.google.gerrit.server.project.ProjectCache.illegalState;
18 :
19 : import com.google.gerrit.entities.BranchOrderSection;
20 : import com.google.gerrit.entities.Change;
21 : import com.google.gerrit.entities.PatchSet;
22 : import com.google.gerrit.entities.SubmitTypeRecord;
23 : import com.google.gerrit.extensions.client.SubmitType;
24 : import com.google.gerrit.extensions.common.MergeableInfo;
25 : import com.google.gerrit.extensions.restapi.AuthException;
26 : import com.google.gerrit.extensions.restapi.BadRequestException;
27 : import com.google.gerrit.extensions.restapi.ResourceConflictException;
28 : import com.google.gerrit.extensions.restapi.Response;
29 : import com.google.gerrit.extensions.restapi.RestReadView;
30 : import com.google.gerrit.server.ChangeUtil;
31 : import com.google.gerrit.server.change.MergeabilityCache;
32 : import com.google.gerrit.server.change.RevisionResource;
33 : import com.google.gerrit.server.git.GitRepositoryManager;
34 : import com.google.gerrit.server.git.MergeUtilFactory;
35 : import com.google.gerrit.server.index.change.ChangeIndexer;
36 : import com.google.gerrit.server.project.ProjectCache;
37 : import com.google.gerrit.server.project.ProjectState;
38 : import com.google.gerrit.server.project.SubmitRuleEvaluator;
39 : import com.google.gerrit.server.project.SubmitRuleOptions;
40 : import com.google.gerrit.server.query.change.ChangeData;
41 : import com.google.inject.Inject;
42 : import java.io.IOException;
43 : import java.util.ArrayList;
44 : import java.util.List;
45 : import java.util.Map;
46 : import java.util.Objects;
47 : import java.util.Optional;
48 : import java.util.concurrent.Future;
49 : import org.eclipse.jgit.lib.Constants;
50 : import org.eclipse.jgit.lib.ObjectId;
51 : import org.eclipse.jgit.lib.Ref;
52 : import org.eclipse.jgit.lib.Repository;
53 : import org.kohsuke.args4j.Option;
54 :
55 : public class Mergeable implements RestReadView<RevisionResource> {
56 : @Option(
57 : name = "--other-branches",
58 : aliases = {"-o"},
59 : usage = "test mergeability for other branches too")
60 : private boolean otherBranches;
61 :
62 : private final GitRepositoryManager gitManager;
63 : private final ProjectCache projectCache;
64 : private final MergeUtilFactory mergeUtilFactory;
65 : private final ChangeData.Factory changeDataFactory;
66 : private final ChangeIndexer indexer;
67 : private final MergeabilityCache cache;
68 : private final SubmitRuleEvaluator submitRuleEvaluator;
69 :
70 : @Inject
71 : Mergeable(
72 : GitRepositoryManager gitManager,
73 : ProjectCache projectCache,
74 : MergeUtilFactory mergeUtilFactory,
75 : ChangeData.Factory changeDataFactory,
76 : ChangeIndexer indexer,
77 : MergeabilityCache cache,
78 86 : SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory) {
79 86 : this.gitManager = gitManager;
80 86 : this.projectCache = projectCache;
81 86 : this.mergeUtilFactory = mergeUtilFactory;
82 86 : this.changeDataFactory = changeDataFactory;
83 86 : this.indexer = indexer;
84 86 : this.cache = cache;
85 86 : submitRuleEvaluator = submitRuleEvaluatorFactory.create(SubmitRuleOptions.defaults());
86 86 : }
87 :
88 : public void setOtherBranches(boolean otherBranches) {
89 1 : this.otherBranches = otherBranches;
90 1 : }
91 :
92 : @Override
93 : public Response<MergeableInfo> apply(RevisionResource resource)
94 : throws AuthException, ResourceConflictException, BadRequestException, IOException {
95 2 : Change change = resource.getChange();
96 2 : PatchSet ps = resource.getPatchSet();
97 2 : MergeableInfo result = new MergeableInfo();
98 :
99 2 : if (!change.isNew()) {
100 0 : throw new ResourceConflictException("change is " + ChangeUtil.status(change));
101 2 : } else if (!ps.id().equals(change.currentPatchSetId())) {
102 : // Only the current revision is mergeable. Others always fail.
103 0 : return Response.ok(result);
104 : }
105 :
106 2 : ChangeData cd = changeDataFactory.create(resource.getNotes());
107 2 : result.submitType = getSubmitType(cd);
108 :
109 2 : try (Repository git = gitManager.openRepository(change.getProject())) {
110 2 : ObjectId commit = ps.commitId();
111 2 : Ref ref = git.getRefDatabase().exactRef(change.getDest().branch());
112 2 : ProjectState projectState =
113 2 : projectCache.get(change.getProject()).orElseThrow(illegalState(change.getProject()));
114 2 : String strategy = mergeUtilFactory.create(projectState).mergeStrategyName();
115 2 : result.strategy = strategy;
116 2 : result.mergeable = isMergable(git, change, commit, ref, result.submitType, strategy);
117 :
118 2 : if (otherBranches) {
119 1 : result.mergeableInto = new ArrayList<>();
120 1 : Optional<BranchOrderSection> branchOrder = projectState.getBranchOrderSection();
121 1 : if (branchOrder.isPresent()) {
122 1 : int prefixLen = Constants.R_HEADS.length();
123 1 : List<String> names = branchOrder.get().getMoreStable(ref.getName());
124 1 : Map<String, Ref> refs =
125 1 : git.getRefDatabase().exactRef(names.toArray(new String[names.size()]));
126 1 : for (String n : names) {
127 1 : Ref other = refs.get(n);
128 1 : if (other == null) {
129 1 : continue;
130 : }
131 1 : if (cache.get(commit, other, SubmitType.CHERRY_PICK, strategy, change.getDest(), git)) {
132 1 : result.mergeableInto.add(other.getName().substring(prefixLen));
133 : }
134 1 : }
135 : }
136 : }
137 : }
138 2 : return Response.ok(result);
139 : }
140 :
141 : private SubmitType getSubmitType(ChangeData cd) throws ResourceConflictException {
142 2 : SubmitTypeRecord rec = submitRuleEvaluator.getSubmitType(cd);
143 2 : if (rec.status != SubmitTypeRecord.Status.OK) {
144 0 : throw new ResourceConflictException("submit type rule error: " + rec.errorMessage);
145 : }
146 2 : return rec.type;
147 : }
148 :
149 : private boolean isMergable(
150 : Repository git,
151 : Change change,
152 : ObjectId commit,
153 : Ref ref,
154 : SubmitType submitType,
155 : String strategy) {
156 2 : if (commit == null) {
157 0 : return false;
158 : }
159 :
160 2 : Boolean old = cache.getIfPresent(commit, ref, submitType, strategy);
161 2 : if (old != null) {
162 1 : return old;
163 : }
164 2 : return refresh(change, commit, ref, submitType, strategy, git, old);
165 : }
166 :
167 : private boolean refresh(
168 : final Change change,
169 : ObjectId commit,
170 : final Ref ref,
171 : SubmitType type,
172 : String strategy,
173 : Repository git,
174 : Boolean old) {
175 2 : boolean mergeable = cache.get(commit, ref, type, strategy, change.getDest(), git);
176 : // TODO(dborowitz): Include something else in the change ETag that it's possible to bump here,
177 : // such as cache or secondary index update time.
178 2 : if (!Objects.equals(mergeable, old)) {
179 : @SuppressWarnings("unused")
180 2 : Future<?> possiblyIgnoredError = indexer.indexAsync(change.getProject(), change.getId());
181 : }
182 2 : return mergeable;
183 : }
184 : }
|