LCOV - code coverage report
Current view: top level - server/restapi/project - ListBranches.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 97 114 85.1 %
Date: 2022-11-19 15:00:39 Functions: 14 14 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.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             : }

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