LCOV - code coverage report
Current view: top level - entities - RefNames.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 136 138 98.6 %
Date: 2022-11-19 15:00:39 Functions: 41 41 100.0 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750