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.project;
16 :
17 : import static com.google.gerrit.entities.RefNames.isConfigRef;
18 :
19 : import com.google.common.collect.ComparisonChain;
20 : import com.google.common.collect.ImmutableList;
21 : import com.google.common.collect.Sets;
22 : import com.google.gerrit.entities.RefNames;
23 : import com.google.gerrit.extensions.api.projects.BranchInfo;
24 : import com.google.gerrit.extensions.api.projects.ProjectApi.ListRefsRequest;
25 : import com.google.gerrit.extensions.common.ActionInfo;
26 : import com.google.gerrit.extensions.common.WebLinkInfo;
27 : import com.google.gerrit.extensions.registration.DynamicMap;
28 : import com.google.gerrit.extensions.restapi.AuthException;
29 : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
30 : import com.google.gerrit.extensions.restapi.Response;
31 : import com.google.gerrit.extensions.restapi.RestApiException;
32 : import com.google.gerrit.extensions.restapi.RestReadView;
33 : import com.google.gerrit.extensions.restapi.RestView;
34 : import com.google.gerrit.extensions.webui.UiAction;
35 : import com.google.gerrit.server.CurrentUser;
36 : import com.google.gerrit.server.WebLinks;
37 : import com.google.gerrit.server.extensions.webui.UiActions;
38 : import com.google.gerrit.server.git.GitRepositoryManager;
39 : import com.google.gerrit.server.permissions.PermissionBackend;
40 : import com.google.gerrit.server.permissions.PermissionBackendException;
41 : import com.google.gerrit.server.permissions.RefPermission;
42 : import com.google.gerrit.server.project.BranchResource;
43 : import com.google.gerrit.server.project.ProjectResource;
44 : import com.google.gerrit.server.project.ProjectState;
45 : import com.google.gerrit.server.project.RefFilter;
46 : import com.google.inject.Inject;
47 : import java.io.IOException;
48 : import java.util.ArrayList;
49 : import java.util.Collection;
50 : import java.util.Comparator;
51 : import java.util.List;
52 : import java.util.Set;
53 : import java.util.TreeMap;
54 : import org.eclipse.jgit.errors.RepositoryNotFoundException;
55 : import org.eclipse.jgit.lib.Constants;
56 : import org.eclipse.jgit.lib.Ref;
57 : import org.eclipse.jgit.lib.Repository;
58 : import org.kohsuke.args4j.Option;
59 :
60 : public class ListBranches implements RestReadView<ProjectResource> {
61 : private final GitRepositoryManager repoManager;
62 : private final PermissionBackend permissionBackend;
63 : private final DynamicMap<RestView<BranchResource>> branchViews;
64 : private final UiActions uiActions;
65 : private final WebLinks webLinks;
66 :
67 : @Option(
68 : name = "--limit",
69 : aliases = {"-n"},
70 : metaVar = "CNT",
71 : usage = "maximum number of branches to list")
72 : public void setLimit(int limit) {
73 7 : this.limit = limit;
74 7 : }
75 :
76 : @Option(
77 : name = "--start",
78 : aliases = {"-S", "-s"},
79 : metaVar = "CNT",
80 : usage = "number of branches to skip")
81 : public void setStart(int start) {
82 7 : this.start = start;
83 7 : }
84 :
85 : @Option(
86 : name = "--match",
87 : aliases = {"-m"},
88 : metaVar = "MATCH",
89 : usage = "match branches substring")
90 : public void setMatchSubstring(String matchSubstring) {
91 7 : this.matchSubstring = matchSubstring;
92 7 : }
93 :
94 : @Option(
95 : name = "--regex",
96 : aliases = {"-r"},
97 : metaVar = "REGEX",
98 : usage = "match branches regex")
99 : public void setMatchRegex(String matchRegex) {
100 7 : this.matchRegex = matchRegex;
101 7 : }
102 :
103 : private int limit;
104 : private int start;
105 : private String matchSubstring;
106 : private String matchRegex;
107 :
108 : @Inject
109 : public ListBranches(
110 : GitRepositoryManager repoManager,
111 : PermissionBackend permissionBackend,
112 : DynamicMap<RestView<BranchResource>> branchViews,
113 : UiActions uiActions,
114 15 : WebLinks webLinks) {
115 15 : this.repoManager = repoManager;
116 15 : this.permissionBackend = permissionBackend;
117 15 : this.branchViews = branchViews;
118 15 : this.uiActions = uiActions;
119 15 : this.webLinks = webLinks;
120 15 : }
121 :
122 : public ListBranches request(ListRefsRequest<BranchInfo> request) {
123 7 : this.setLimit(request.getLimit());
124 7 : this.setStart(request.getStart());
125 7 : this.setMatchSubstring(request.getSubstring());
126 7 : this.setMatchRegex(request.getRegex());
127 7 : return this;
128 : }
129 :
130 : @Override
131 : public Response<ImmutableList<BranchInfo>> apply(ProjectResource rsrc)
132 : throws RestApiException, IOException, PermissionBackendException {
133 8 : rsrc.getProjectState().checkStatePermitsRead();
134 8 : return Response.ok(
135 : new RefFilter<BranchInfo>(Constants.R_HEADS)
136 8 : .subString(matchSubstring)
137 8 : .regex(matchRegex)
138 8 : .start(start)
139 8 : .limit(limit)
140 8 : .filter(allBranches(rsrc)));
141 : }
142 :
143 : BranchInfo toBranchInfo(BranchResource rsrc)
144 : throws IOException, ResourceNotFoundException, PermissionBackendException {
145 10 : try (Repository db = repoManager.openRepository(rsrc.getNameKey())) {
146 10 : String refName = rsrc.getRef();
147 10 : if (RefNames.isRefsUsersSelf(refName, rsrc.getProjectState().isAllUsers())) {
148 0 : refName = RefNames.refsUsers(rsrc.getUser().getAccountId());
149 : }
150 10 : Ref r = db.exactRef(refName);
151 10 : if (r == null) {
152 0 : throw new ResourceNotFoundException();
153 : }
154 10 : return toBranchInfo(rsrc, ImmutableList.of(r)).get(0);
155 0 : } catch (RepositoryNotFoundException noRepo) {
156 0 : throw new ResourceNotFoundException(rsrc.getNameKey().get(), noRepo);
157 : }
158 : }
159 :
160 : private List<BranchInfo> allBranches(ProjectResource rsrc)
161 : throws IOException, ResourceNotFoundException, PermissionBackendException {
162 : List<Ref> refs;
163 8 : try (Repository db = repoManager.openRepository(rsrc.getNameKey())) {
164 8 : Collection<Ref> heads = db.getRefDatabase().getRefsByPrefix(Constants.R_HEADS);
165 8 : refs = new ArrayList<>(heads.size() + 3);
166 8 : refs.addAll(heads);
167 8 : refs.addAll(
168 8 : db.getRefDatabase()
169 8 : .exactRef(Constants.HEAD, RefNames.REFS_CONFIG, RefNames.REFS_USERS_DEFAULT)
170 8 : .values());
171 0 : } catch (RepositoryNotFoundException noGitRepository) {
172 0 : throw new ResourceNotFoundException(rsrc.getNameKey().get(), noGitRepository);
173 8 : }
174 8 : return toBranchInfo(rsrc, refs);
175 : }
176 :
177 : private List<BranchInfo> toBranchInfo(ProjectResource rsrc, List<Ref> refs)
178 : throws PermissionBackendException {
179 15 : Set<String> targets = Sets.newHashSetWithExpectedSize(1);
180 15 : for (Ref ref : refs) {
181 15 : if (ref.isSymbolic()) {
182 9 : targets.add(ref.getTarget().getName());
183 : }
184 15 : }
185 :
186 15 : PermissionBackend.ForProject perm = permissionBackend.currentUser().project(rsrc.getNameKey());
187 15 : List<BranchInfo> branches = new ArrayList<>(refs.size());
188 15 : for (Ref ref : refs) {
189 15 : if (ref.isSymbolic()) {
190 : // A symbolic reference to another branch, instead of
191 : // showing the resolved value, show the name it references.
192 : //
193 9 : String target = ref.getTarget().getName();
194 :
195 : try {
196 9 : perm.ref(target).check(RefPermission.READ);
197 1 : } catch (AuthException e) {
198 1 : continue;
199 9 : }
200 :
201 9 : if (target.startsWith(Constants.R_HEADS)) {
202 9 : target = target.substring(Constants.R_HEADS.length());
203 : }
204 :
205 9 : BranchInfo b = new BranchInfo();
206 9 : b.ref = ref.getName();
207 9 : b.revision = target;
208 9 : branches.add(b);
209 :
210 9 : if (!Constants.HEAD.equals(ref.getName())) {
211 0 : if (isConfigRef(ref.getName())) {
212 : // Never allow to delete the meta config branch.
213 0 : b.canDelete = null;
214 : } else {
215 0 : b.canDelete =
216 0 : perm.ref(ref.getName()).testOrFalse(RefPermission.DELETE)
217 0 : && rsrc.getProjectState().statePermitsWrite()
218 0 : ? true
219 0 : : null;
220 : }
221 : }
222 : continue;
223 : }
224 :
225 : try {
226 15 : perm.ref(ref.getName()).check(RefPermission.READ);
227 15 : branches.add(
228 15 : createBranchInfo(
229 15 : perm.ref(ref.getName()), ref, rsrc.getProjectState(), rsrc.getUser(), targets));
230 1 : } catch (AuthException e) {
231 : // Do nothing.
232 15 : }
233 15 : }
234 15 : branches.sort(new BranchComparator());
235 15 : return branches;
236 : }
237 :
238 : private static class BranchComparator implements Comparator<BranchInfo> {
239 : @Override
240 : public int compare(BranchInfo a, BranchInfo b) {
241 8 : return ComparisonChain.start()
242 8 : .compareTrueFirst(isHead(a), isHead(b))
243 8 : .compareTrueFirst(isConfig(a), isConfig(b))
244 8 : .compare(a.ref, b.ref)
245 8 : .result();
246 : }
247 :
248 : private static boolean isHead(BranchInfo i) {
249 8 : return Constants.HEAD.equals(i.ref);
250 : }
251 :
252 : private static boolean isConfig(BranchInfo i) {
253 8 : return RefNames.REFS_CONFIG.equals(i.ref);
254 : }
255 : }
256 :
257 : private BranchInfo createBranchInfo(
258 : PermissionBackend.ForRef perm,
259 : Ref ref,
260 : ProjectState projectState,
261 : CurrentUser user,
262 : Set<String> targets) {
263 15 : BranchInfo info = new BranchInfo();
264 15 : info.ref = ref.getName();
265 15 : info.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
266 :
267 15 : if (isConfigRef(ref.getName())) {
268 : // Never allow to delete the meta config branch.
269 10 : info.canDelete = null;
270 : } else {
271 14 : info.canDelete =
272 14 : !targets.contains(ref.getName())
273 13 : && perm.testOrFalse(RefPermission.DELETE)
274 14 : && projectState.statePermitsWrite()
275 12 : ? true
276 14 : : null;
277 : }
278 :
279 15 : BranchResource rsrc = new BranchResource(projectState, user, ref);
280 15 : for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
281 0 : if (info.actions == null) {
282 0 : info.actions = new TreeMap<>();
283 : }
284 0 : info.actions.put(d.getId(), new ActionInfo(d));
285 0 : }
286 :
287 15 : ImmutableList<WebLinkInfo> links =
288 15 : webLinks.getBranchLinks(projectState.getName(), ref.getName());
289 15 : info.webLinks = links.isEmpty() ? null : links;
290 15 : return info;
291 : }
292 : }
|