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.entities;
16 :
17 : import com.google.common.base.Splitter;
18 : import com.google.common.collect.ImmutableList;
19 : import com.google.gerrit.common.Nullable;
20 : import com.google.gerrit.common.UsedAt;
21 : import java.util.List;
22 :
23 : /** Constants and utilities for Gerrit-specific ref names. */
24 : public class RefNames {
25 : public static final String HEAD = "HEAD";
26 :
27 : public static final String REFS = "refs/";
28 :
29 : public static final String REFS_HEADS = "refs/heads/";
30 :
31 : public static final String REFS_TAGS = "refs/tags/";
32 :
33 : public static final String REFS_CHANGES = "refs/changes/";
34 :
35 : public static final String REFS_META = "refs/meta/";
36 :
37 : /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
38 : public static final String REFS_REJECT_COMMITS = "refs/meta/reject-commits";
39 :
40 : /** Configuration settings for a project {@code refs/meta/config} */
41 : public static final String REFS_CONFIG = "refs/meta/config";
42 :
43 : /** Note tree listing external IDs */
44 : public static final String REFS_EXTERNAL_IDS = "refs/meta/external-ids";
45 :
46 : /** Magic user branch in All-Users {@code refs/users/self} */
47 : public static final String REFS_USERS_SELF = "refs/users/self";
48 :
49 : /** Default user preference settings */
50 : public static final String REFS_USERS_DEFAULT = RefNames.REFS_USERS + "default";
51 :
52 : /** Configurations of project-specific dashboards (canned search queries). */
53 : public static final String REFS_DASHBOARDS = "refs/meta/dashboards/";
54 :
55 : /** Sequence counters in NoteDb. */
56 : public static final String REFS_SEQUENCES = "refs/sequences/";
57 :
58 : /** NoteDb schema version number. */
59 : public static final String REFS_VERSION = "refs/meta/version";
60 :
61 : /**
62 : * Prefix applied to merge commit base nodes.
63 : *
64 : * <p>References in this directory should take the form {@code refs/cache-automerge/xx/yyyy...}
65 : * where xx is the first two digits of the merge commit's object name, and yyyyy... is the
66 : * remaining 38. The reference should point to a treeish that is the automatic merge result of the
67 : * merge commit's parents.
68 : */
69 : public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
70 :
71 : /** Suffix of a meta ref in the NoteDb. */
72 : public static final String META_SUFFIX = "/meta";
73 :
74 : /** Suffix of a ref that stores robot comments in the NoteDb. */
75 : public static final String ROBOT_COMMENTS_SUFFIX = "/robot-comments";
76 :
77 : public static final String EDIT_PREFIX = "edit-";
78 :
79 : /*
80 : * The following refs contain an account ID and should be visible only to that account.
81 : *
82 : * Parsing the account ID from the ref is implemented in Account.Id#fromRef(String). This ensures
83 : * that VisibleRefFilter hides those refs from other users.
84 : *
85 : * This applies to:
86 : * - User branches (e.g. 'refs/users/23/1011123')
87 : * - Draft comment refs (e.g. 'refs/draft-comments/73/67473/1011123')
88 : * - Starred changes refs (e.g. 'refs/starred-changes/73/67473/1011123')
89 : */
90 :
91 : /** Preference settings for a user {@code refs/users} */
92 : public static final String REFS_USERS = "refs/users/";
93 :
94 : /** NoteDb ref for a group {@code refs/groups} */
95 : public static final String REFS_GROUPS = "refs/groups/";
96 :
97 : /** NoteDb ref for the NoteMap of all group names */
98 : public static final String REFS_GROUPNAMES = "refs/meta/group-names";
99 :
100 : /**
101 : * NoteDb ref for deleted groups {@code refs/deleted-groups}. This ref namespace is foreseen as an
102 : * attic for deleted groups (it's reserved but not used yet)
103 : */
104 : public static final String REFS_DELETED_GROUPS = "refs/deleted-groups/";
105 :
106 : /** Draft inline comments of a user on a change */
107 : public static final String REFS_DRAFT_COMMENTS = "refs/draft-comments/";
108 :
109 : /** A change starred by a user */
110 : public static final String REFS_STARRED_CHANGES = "refs/starred-changes/";
111 :
112 : /**
113 : * List of refs managed by Gerrit. Covers all Gerrit internal refs.
114 : *
115 : * <p><b>Caution</b> Any ref not in this list will be served if the user was granted a READ
116 : * permission on it using Gerrit's permission model.
117 : */
118 158 : public static final ImmutableList<String> GERRIT_REFS =
119 158 : ImmutableList.of(
120 : REFS_CHANGES,
121 : REFS_EXTERNAL_IDS,
122 : REFS_CACHE_AUTOMERGE,
123 : REFS_DRAFT_COMMENTS,
124 : REFS_DELETED_GROUPS,
125 : REFS_SEQUENCES,
126 : REFS_GROUPS,
127 : REFS_GROUPNAMES,
128 : REFS_USERS,
129 : REFS_STARRED_CHANGES,
130 : REFS_REJECT_COMMITS);
131 :
132 158 : private static final Splitter SPLITTER = Splitter.on("/");
133 :
134 : public static String fullName(String ref) {
135 152 : return (ref.startsWith(REFS) || ref.equals(HEAD)) ? ref : REFS_HEADS + ref;
136 : }
137 :
138 : public static final String shortName(String ref) {
139 108 : if (ref.startsWith(REFS_HEADS)) {
140 106 : return ref.substring(REFS_HEADS.length());
141 18 : } else if (ref.startsWith(REFS_TAGS)) {
142 5 : return ref.substring(REFS_TAGS.length());
143 : }
144 14 : return ref;
145 : }
146 :
147 : /**
148 : * Warning: Change refs have to manually be advertised in {@code
149 : * com.google.gerrit.server.permissions.DefaultRefFilter}; this should be done when adding new
150 : * change refs.
151 : */
152 : public static String changeMetaRef(Change.Id id) {
153 104 : StringBuilder r = newStringBuilder().append(REFS_CHANGES);
154 104 : return shard(id.get(), r).append(META_SUFFIX).toString();
155 : }
156 :
157 : public static String patchSetRef(PatchSet.Id id) {
158 64 : StringBuilder r = newStringBuilder().append(REFS_CHANGES);
159 64 : return shard(id.changeId().get(), r).append('/').append(id.get()).toString();
160 : }
161 :
162 : public static String changeRefPrefix(Change.Id id) {
163 47 : StringBuilder r = newStringBuilder().append(REFS_CHANGES);
164 47 : return shard(id.get(), r).append('/').toString();
165 : }
166 :
167 : public static String robotCommentsRef(Change.Id id) {
168 104 : StringBuilder r = newStringBuilder().append(REFS_CHANGES);
169 104 : return shard(id.get(), r).append(ROBOT_COMMENTS_SUFFIX).toString();
170 : }
171 :
172 : public static boolean isNoteDbMetaRef(String ref) {
173 50 : if (ref.startsWith(REFS_CHANGES)
174 5 : && (ref.endsWith(META_SUFFIX) || ref.endsWith(ROBOT_COMMENTS_SUFFIX))) {
175 5 : return true;
176 : }
177 49 : if (ref.startsWith(REFS_DRAFT_COMMENTS) || ref.startsWith(REFS_STARRED_CHANGES)) {
178 0 : return true;
179 : }
180 49 : return false;
181 : }
182 :
183 : /** True if the provided ref is in {@code refs/changes/*}. */
184 : public static boolean isRefsChanges(String ref) {
185 98 : return ref.startsWith(REFS_CHANGES);
186 : }
187 :
188 : public static String refsGroups(AccountGroup.UUID groupUuid) {
189 153 : return REFS_GROUPS + shardUuid(groupUuid.get());
190 : }
191 :
192 : public static String refsDeletedGroups(AccountGroup.UUID groupUuid) {
193 3 : return REFS_DELETED_GROUPS + shardUuid(groupUuid.get());
194 : }
195 :
196 : public static String refsUsers(Account.Id accountId) {
197 152 : StringBuilder r = newStringBuilder().append(REFS_USERS);
198 152 : return shard(accountId.get(), r).toString();
199 : }
200 :
201 : public static String refsDraftComments(Change.Id changeId, Account.Id accountId) {
202 31 : return buildRefsPrefix(REFS_DRAFT_COMMENTS, changeId.get()).append(accountId.get()).toString();
203 : }
204 :
205 : public static String refsDraftCommentsPrefix(Change.Id changeId) {
206 104 : return buildRefsPrefix(REFS_DRAFT_COMMENTS, changeId.get()).toString();
207 : }
208 :
209 : public static String refsStarredChanges(Change.Id changeId, Account.Id accountId) {
210 104 : return buildRefsPrefix(REFS_STARRED_CHANGES, changeId.get()).append(accountId.get()).toString();
211 : }
212 :
213 : public static String refsStarredChangesPrefix(Change.Id changeId) {
214 104 : return buildRefsPrefix(REFS_STARRED_CHANGES, changeId.get()).toString();
215 : }
216 :
217 : private static StringBuilder buildRefsPrefix(String prefix, int id) {
218 104 : StringBuilder r = newStringBuilder().append(prefix);
219 104 : return shard(id, r).append('/');
220 : }
221 :
222 : public static String refsCacheAutomerge(String hash) {
223 31 : return REFS_CACHE_AUTOMERGE + hash.substring(0, 2) + '/' + hash.substring(2);
224 : }
225 :
226 : @Nullable
227 : public static String shard(int id) {
228 47 : if (id < 0) {
229 1 : return null;
230 : }
231 47 : return shard(id, newStringBuilder()).toString();
232 : }
233 :
234 : private static StringBuilder shard(int id, StringBuilder sb) {
235 152 : int n = id % 100;
236 152 : if (n < 10) {
237 152 : sb.append('0');
238 : }
239 152 : sb.append(n);
240 152 : sb.append('/');
241 152 : sb.append(id);
242 152 : return sb;
243 : }
244 :
245 : @UsedAt(UsedAt.Project.PLUGINS_ALL)
246 : public static String shardUuid(String uuid) {
247 153 : if (uuid == null || uuid.length() < 2) {
248 1 : throw new IllegalArgumentException("UUIDs must consist of at least two characters");
249 : }
250 153 : return uuid.substring(0, 2) + '/' + uuid;
251 : }
252 :
253 : /**
254 : * Returns reference for this change edit with sharded user and change number:
255 : * refs/users/UU/UUUU/edit-CCCC/P.
256 : *
257 : * @param accountId account id
258 : * @param changeId change number
259 : * @param psId patch set number
260 : * @return reference for this change edit
261 : */
262 : public static String refsEdit(Account.Id accountId, Change.Id changeId, PatchSet.Id psId) {
263 30 : return refsEditPrefix(accountId, changeId) + psId.get();
264 : }
265 :
266 : /**
267 : * Returns reference prefix for this change edit with sharded user and change number:
268 : * refs/users/UU/UUUU/edit-CCCC/.
269 : *
270 : * @param accountId account id
271 : * @param changeId change number
272 : * @return reference prefix for this change edit
273 : */
274 : public static String refsEditPrefix(Account.Id accountId, Change.Id changeId) {
275 30 : return refsEditPrefix(accountId) + changeId.get() + '/';
276 : }
277 :
278 : public static String refsEditPrefix(Account.Id accountId) {
279 30 : return refsUsers(accountId) + '/' + EDIT_PREFIX;
280 : }
281 :
282 : public static boolean isRefsEdit(String ref) {
283 116 : return ref != null && ref.startsWith(REFS_USERS) && ref.contains(EDIT_PREFIX);
284 : }
285 :
286 : public static boolean isRefsUsers(String ref) {
287 2 : return ref.startsWith(REFS_USERS);
288 : }
289 :
290 : public static boolean isRefsUsersSelf(String ref, boolean isAllUsers) {
291 97 : return isAllUsers && REFS_USERS_SELF.equals(ref);
292 : }
293 :
294 : /**
295 : * Whether the ref is a group branch that stores NoteDb data of a group. Returns {@code true} for
296 : * all refs that start with {@code refs/groups/}.
297 : */
298 : public static boolean isRefsGroups(String ref) {
299 51 : return ref.startsWith(REFS_GROUPS);
300 : }
301 :
302 : /**
303 : * Whether the ref is a group branch that stores NoteDb data of a deleted group. Returns {@code
304 : * true} for all refs that start with {@code refs/deleted-groups/}.
305 : */
306 : public static boolean isRefsDeletedGroups(String ref) {
307 6 : return ref.startsWith(REFS_DELETED_GROUPS);
308 : }
309 :
310 : /** Returns true if the provided ref is for draft comments. */
311 : public static boolean isRefsDraftsComments(String ref) {
312 110 : return ref.startsWith(REFS_DRAFT_COMMENTS);
313 : }
314 :
315 : /** Returns true if the provided ref is for starred changes. */
316 : public static boolean isRefsStarredChanges(String ref) {
317 19 : return ref.startsWith(REFS_STARRED_CHANGES);
318 : }
319 :
320 : /**
321 : * Whether the ref is used for storing group data in NoteDb. Returns {@code true} for all group
322 : * branches, refs/meta/group-names and deleted group branches.
323 : */
324 : public static boolean isGroupRef(String ref) {
325 7 : return isRefsGroups(ref) || isRefsDeletedGroups(ref) || REFS_GROUPNAMES.equals(ref);
326 : }
327 :
328 : /** Whether the ref is the configuration branch, i.e. {@code refs/meta/config}, for a project. */
329 : public static boolean isConfigRef(String ref) {
330 42 : return REFS_CONFIG.equals(ref);
331 : }
332 :
333 : /**
334 : * Whether the ref is managed by Gerrit. Covers all Gerrit-internal refs like refs/cache-automerge
335 : * and refs/meta as well as refs/changes. Does not cover user-created refs like branches or custom
336 : * ref namespaces like refs/my-company.
337 : *
338 : * <p>Any ref for which this method evaluates to true will be served to users who have the {@code
339 : * ACCESS_DATABASE} capability.
340 : *
341 : * <p><b>Caution</b> Any ref not in this list will be served if the user was granted a READ
342 : * permission on it using Gerrit's permission model.
343 : */
344 : public static boolean isGerritRef(String ref) {
345 146 : return GERRIT_REFS.stream().anyMatch(internalRef -> ref.startsWith(internalRef));
346 : }
347 :
348 : @Nullable
349 : static Integer parseShardedRefPart(String name) {
350 152 : if (name == null) {
351 1 : return null;
352 : }
353 :
354 152 : List<String> parts = SPLITTER.splitToList(name);
355 152 : int n = parts.size();
356 152 : if (n < 2) {
357 4 : return null;
358 : }
359 :
360 : // Last 2 digits.
361 : int le;
362 152 : for (le = 0; le < parts.get(0).length(); le++) {
363 152 : if (!Character.isDigit(parts.get(0).charAt(le))) {
364 40 : return null;
365 : }
366 : }
367 152 : if (le != 2) {
368 1 : return null;
369 : }
370 :
371 : // Full ID.
372 : int ie;
373 152 : for (ie = 0; ie < parts.get(1).length(); ie++) {
374 152 : if (!Character.isDigit(parts.get(1).charAt(ie))) {
375 1 : if (ie == 0) {
376 1 : return null;
377 : }
378 : break;
379 : }
380 : }
381 :
382 152 : int shard = Integer.parseInt(parts.get(0));
383 152 : int id = Integer.parseInt(parts.get(1).substring(0, ie));
384 :
385 152 : if (id % 100 != shard) {
386 1 : return null;
387 : }
388 152 : return id;
389 : }
390 :
391 : @UsedAt(UsedAt.Project.PLUGINS_ALL)
392 : @Nullable
393 : public static String parseShardedUuidFromRefPart(String name) {
394 41 : if (name == null) {
395 1 : return null;
396 : }
397 :
398 41 : List<String> parts = SPLITTER.splitToList(name);
399 41 : int n = parts.size();
400 41 : if (n != 2) {
401 2 : return null;
402 : }
403 :
404 : // First 2 chars.
405 41 : if (parts.get(0).length() != 2) {
406 1 : return null;
407 : }
408 :
409 : // Full UUID.
410 41 : String uuid = parts.get(1);
411 41 : if (!uuid.startsWith(parts.get(0))) {
412 1 : return null;
413 : }
414 :
415 41 : return uuid;
416 : }
417 :
418 : /**
419 : * Skips a sharded ref part at the beginning of the name.
420 : *
421 : * <p>E.g.: "01/1" -> "", "01/1/" -> "/", "01/1/2" -> "/2", "01/1-edit" -> "-edit"
422 : *
423 : * @param name ref part name
424 : * @return the rest of the name, {@code null} if the ref name part doesn't start with a valid
425 : * sharded ID
426 : */
427 : @Nullable
428 : static String skipShardedRefPart(String name) {
429 12 : if (name == null) {
430 1 : return null;
431 : }
432 :
433 12 : List<String> parts = SPLITTER.splitToList(name);
434 12 : int n = parts.size();
435 12 : if (n < 2) {
436 1 : return null;
437 : }
438 :
439 : // Last 2 digits.
440 : int le;
441 12 : for (le = 0; le < parts.get(0).length(); le++) {
442 12 : if (!Character.isDigit(parts.get(0).charAt(le))) {
443 1 : return null;
444 : }
445 : }
446 12 : if (le != 2) {
447 1 : return null;
448 : }
449 :
450 : // Full ID.
451 : int ie;
452 12 : for (ie = 0; ie < parts.get(1).length(); ie++) {
453 12 : if (!Character.isDigit(parts.get(1).charAt(ie))) {
454 1 : if (ie == 0) {
455 0 : return null;
456 : }
457 : break;
458 : }
459 : }
460 :
461 12 : int shard = Integer.parseInt(parts.get(0));
462 12 : int id = Integer.parseInt(parts.get(1).substring(0, ie));
463 :
464 12 : if (id % 100 != shard) {
465 1 : return null;
466 : }
467 :
468 12 : return name.substring(2 + 1 + ie); // 2 for the length of the shard, 1 for the '/'
469 : }
470 :
471 : /**
472 : * Parses an ID that follows a sharded ref part at the beginning of the name.
473 : *
474 : * <p>E.g.: "01/1/2" -> 2, "01/1/2/4" -> 2, ""01/1/2-edit" -> 2
475 : *
476 : * @param name ref part name
477 : * @return ID that follows the sharded ref part at the beginning of the name, {@code null} if the
478 : * ref name part doesn't start with a valid sharded ID or if no valid ID follows the sharded
479 : * ref part
480 : */
481 : @Nullable
482 : static Integer parseAfterShardedRefPart(String name) {
483 12 : String rest = skipShardedRefPart(name);
484 12 : if (rest == null || !rest.startsWith("/")) {
485 1 : return null;
486 : }
487 :
488 12 : rest = rest.substring(1);
489 :
490 : int ie;
491 12 : for (ie = 0; ie < rest.length(); ie++) {
492 12 : if (!Character.isDigit(rest.charAt(ie))) {
493 1 : break;
494 : }
495 : }
496 12 : if (ie == 0) {
497 1 : return null;
498 : }
499 12 : return Integer.parseInt(rest.substring(0, ie));
500 : }
501 :
502 : @Nullable
503 : public static Integer parseRefSuffix(String name) {
504 9 : if (name == null) {
505 1 : return null;
506 : }
507 9 : int i = name.length();
508 9 : while (i > 0) {
509 9 : char c = name.charAt(i - 1);
510 9 : if (c == '/') {
511 9 : break;
512 9 : } else if (!Character.isDigit(c)) {
513 1 : return null;
514 : }
515 9 : i--;
516 9 : }
517 9 : if (i == 0) {
518 1 : return null;
519 : }
520 9 : return Integer.valueOf(name.substring(i));
521 : }
522 :
523 : private static StringBuilder newStringBuilder() {
524 : // Many refname types in this file are always are longer than the default of 16 chars, so
525 : // presize StringBuilders larger by default. This hurts readability less than accurate
526 : // calculations would, at a negligible cost to memory overhead.
527 152 : return new StringBuilder(64);
528 : }
529 :
530 : private RefNames() {}
531 : }
|