LCOV - code coverage report
Current view: top level - server/project - ProjectState.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 202 222 91.0 %
Date: 2022-11-19 15:00:39 Functions: 39 41 95.1 %

          Line data    Source code
       1             : // Copyright (C) 2008 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.project;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkState;
      18             : import static com.google.gerrit.entities.PermissionRule.Action.ALLOW;
      19             : 
      20             : import com.google.common.annotations.VisibleForTesting;
      21             : import com.google.common.collect.FluentIterable;
      22             : import com.google.common.collect.ImmutableList;
      23             : import com.google.common.collect.ImmutableMap;
      24             : import com.google.common.collect.Lists;
      25             : import com.google.common.flogger.FluentLogger;
      26             : import com.google.gerrit.entities.AccessSection;
      27             : import com.google.gerrit.entities.AccountGroup;
      28             : import com.google.gerrit.entities.BooleanProjectConfig;
      29             : import com.google.gerrit.entities.BranchNameKey;
      30             : import com.google.gerrit.entities.BranchOrderSection;
      31             : import com.google.gerrit.entities.CachedProjectConfig;
      32             : import com.google.gerrit.entities.GroupReference;
      33             : import com.google.gerrit.entities.LabelType;
      34             : import com.google.gerrit.entities.LabelTypes;
      35             : import com.google.gerrit.entities.Permission;
      36             : import com.google.gerrit.entities.PermissionRule;
      37             : import com.google.gerrit.entities.Project;
      38             : import com.google.gerrit.entities.StoredCommentLinkInfo;
      39             : import com.google.gerrit.entities.SubmitRequirement;
      40             : import com.google.gerrit.entities.SubscribeSection;
      41             : import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
      42             : import com.google.gerrit.extensions.client.SubmitType;
      43             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      44             : import com.google.gerrit.index.project.ProjectData;
      45             : import com.google.gerrit.server.account.CapabilityCollection;
      46             : import com.google.gerrit.server.config.AllProjectsName;
      47             : import com.google.gerrit.server.config.AllUsersName;
      48             : import com.google.gerrit.server.config.PluginConfig;
      49             : import com.google.gerrit.server.git.TransferConfig;
      50             : import com.google.gerrit.server.notedb.ChangeNotes;
      51             : import com.google.inject.Inject;
      52             : import com.google.inject.assistedinject.Assisted;
      53             : import java.util.ArrayList;
      54             : import java.util.Collection;
      55             : import java.util.Collections;
      56             : import java.util.HashSet;
      57             : import java.util.LinkedHashMap;
      58             : import java.util.List;
      59             : import java.util.Map;
      60             : import java.util.Optional;
      61             : import java.util.Set;
      62             : import org.eclipse.jgit.errors.ConfigInvalidException;
      63             : import org.eclipse.jgit.lib.Config;
      64             : 
      65             : /**
      66             :  * State of a project, aggregated from the project and its parents. This is obtained from the {@link
      67             :  * ProjectCache}. It should not be persisted across requests
      68             :  */
      69             : public class ProjectState {
      70         153 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      71             : 
      72             :   public interface Factory {
      73             :     ProjectState create(CachedProjectConfig config);
      74             :   }
      75             : 
      76             :   private final boolean isAllProjects;
      77             :   private final boolean isAllUsers;
      78             :   private final AllProjectsName allProjectsName;
      79             :   private final ProjectCache projectCache;
      80             :   private final List<CommentLinkInfo> commentLinks;
      81             : 
      82             :   private final CachedProjectConfig cachedConfig;
      83             :   private final Set<AccountGroup.UUID> localOwners;
      84             :   private final long globalMaxObjectSizeLimit;
      85             :   private final boolean inheritProjectMaxObjectSizeLimit;
      86             : 
      87             :   /** Local access sections, wrapped in SectionMatchers for faster evaluation. */
      88             :   private volatile List<SectionMatcher> localAccessSections;
      89             : 
      90             :   /** If this is all projects, the capabilities used by the server. */
      91             :   private final CapabilityCollection capabilities;
      92             : 
      93             :   @Inject
      94             :   public ProjectState(
      95             :       ProjectCache projectCache,
      96             :       AllProjectsName allProjectsName,
      97             :       AllUsersName allUsersName,
      98             :       List<CommentLinkInfo> commentLinks,
      99             :       CapabilityCollection.Factory limitsFactory,
     100             :       TransferConfig transferConfig,
     101         151 :       @Assisted CachedProjectConfig cachedProjectConfig) {
     102         151 :     this.projectCache = projectCache;
     103         151 :     this.isAllProjects = cachedProjectConfig.getProject().getNameKey().equals(allProjectsName);
     104         151 :     this.isAllUsers = cachedProjectConfig.getProject().getNameKey().equals(allUsersName);
     105         151 :     this.allProjectsName = allProjectsName;
     106         151 :     this.commentLinks = commentLinks;
     107         151 :     this.cachedConfig = cachedProjectConfig;
     108         151 :     this.capabilities =
     109         151 :         isAllProjects
     110         151 :             ? limitsFactory.create(
     111             :                 cachedProjectConfig
     112         151 :                     .getAccessSection(AccessSection.GLOBAL_CAPABILITIES)
     113         151 :                     .orElse(null))
     114         151 :             : null;
     115         151 :     this.globalMaxObjectSizeLimit = transferConfig.getMaxObjectSizeLimit();
     116         151 :     this.inheritProjectMaxObjectSizeLimit = transferConfig.inheritProjectMaxObjectSizeLimit();
     117             : 
     118         151 :     if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
     119         151 :       localOwners = Collections.emptySet();
     120             :     } else {
     121         148 :       HashSet<AccountGroup.UUID> groups = new HashSet<>();
     122         148 :       Optional<AccessSection> all = cachedProjectConfig.getAccessSection(AccessSection.ALL);
     123         148 :       if (all.isPresent()) {
     124          61 :         Permission owner = all.get().getPermission(Permission.OWNER);
     125          61 :         if (owner != null) {
     126          18 :           for (PermissionRule rule : owner.getRules()) {
     127          18 :             GroupReference ref = rule.getGroup();
     128          18 :             if (rule.getAction() == ALLOW && ref.getUUID() != null) {
     129          18 :               groups.add(ref.getUUID());
     130             :             }
     131          18 :           }
     132             :         }
     133             :       }
     134         148 :       localOwners = Collections.unmodifiableSet(groups);
     135             :     }
     136         151 :   }
     137             : 
     138             :   /**
     139             :    * Returns cached computation of all global capabilities. This should only be invoked on the state
     140             :    * from {@link ProjectCache#getAllProjects()}. Null on any other project.
     141             :    */
     142             :   public CapabilityCollection getCapabilityCollection() {
     143         150 :     return capabilities;
     144             :   }
     145             : 
     146             :   /**
     147             :    * Returns true if the Prolog engine is expected to run for this project, that is if this project
     148             :    * or a parent possesses a rules.pl file.
     149             :    */
     150             :   public boolean hasPrologRules() {
     151             :     // We check if this project has a rules.pl file
     152         103 :     if (getConfig().getRulesId().isPresent()) {
     153           5 :       return true;
     154             :     }
     155             : 
     156             :     // If not, we check the parents.
     157         103 :     return parents().stream()
     158         103 :         .map(ProjectState::getConfig)
     159         103 :         .map(CachedProjectConfig::getRulesId)
     160         103 :         .anyMatch(Optional::isPresent);
     161             :   }
     162             : 
     163             :   public Project getProject() {
     164         148 :     return cachedConfig.getProject();
     165             :   }
     166             : 
     167             :   public Project.NameKey getNameKey() {
     168         148 :     return getProject().getNameKey();
     169             :   }
     170             : 
     171             :   public String getName() {
     172          82 :     return getNameKey().get();
     173             :   }
     174             : 
     175             :   public CachedProjectConfig getConfig() {
     176         151 :     return cachedConfig;
     177             :   }
     178             : 
     179             :   public ProjectLevelConfig getConfig(String fileName) {
     180           1 :     checkState(fileName.endsWith(".config"), "file name must end in .config. is: " + fileName);
     181           1 :     return new ProjectLevelConfig(
     182           1 :         fileName, this, cachedConfig.getParsedProjectLevelConfigs().get(fileName));
     183             :   }
     184             : 
     185             :   public long getMaxObjectSizeLimit() {
     186           0 :     return cachedConfig.getMaxObjectSizeLimit();
     187             :   }
     188             : 
     189             :   public boolean statePermitsRead() {
     190         145 :     return getProject().getState().permitsRead();
     191             :   }
     192             : 
     193             :   public void checkStatePermitsRead() throws ResourceConflictException {
     194         105 :     if (!statePermitsRead()) {
     195           0 :       throw new ResourceConflictException(
     196           0 :           "project state " + getProject().getState().name() + " does not permit read");
     197             :     }
     198         105 :   }
     199             : 
     200             :   public boolean statePermitsWrite() {
     201         119 :     return getProject().getState().permitsWrite();
     202             :   }
     203             : 
     204             :   public void checkStatePermitsWrite() throws ResourceConflictException {
     205          86 :     if (!statePermitsWrite()) {
     206           1 :       throw new ResourceConflictException(
     207           1 :           "project state " + getProject().getState().name() + " does not permit write");
     208             :     }
     209          86 :   }
     210             : 
     211         105 :   public static class EffectiveMaxObjectSizeLimit {
     212             :     public long value;
     213             :     public String summary;
     214             :   }
     215             : 
     216             :   private static final String MAY_NOT_SET = "This project may not set a higher limit.";
     217             : 
     218             :   @VisibleForTesting
     219             :   public static final String INHERITED_FROM_PARENT = "Inherited from parent project '%s'.";
     220             : 
     221             :   @VisibleForTesting
     222             :   public static final String OVERRIDDEN_BY_PARENT =
     223             :       "Overridden by parent project '%s'. " + MAY_NOT_SET;
     224             : 
     225             :   @VisibleForTesting
     226             :   public static final String INHERITED_FROM_GLOBAL = "Inherited from the global config.";
     227             : 
     228             :   @VisibleForTesting
     229             :   public static final String OVERRIDDEN_BY_GLOBAL =
     230             :       "Overridden by the global config. " + MAY_NOT_SET;
     231             : 
     232             :   public EffectiveMaxObjectSizeLimit getEffectiveMaxObjectSizeLimit() {
     233         105 :     EffectiveMaxObjectSizeLimit result = new EffectiveMaxObjectSizeLimit();
     234             : 
     235         105 :     result.value = cachedConfig.getMaxObjectSizeLimit();
     236             : 
     237         105 :     if (inheritProjectMaxObjectSizeLimit) {
     238           1 :       for (ProjectState parent : parents()) {
     239           1 :         long parentValue = parent.cachedConfig.getMaxObjectSizeLimit();
     240           1 :         if (parentValue > 0 && result.value > 0) {
     241           1 :           if (parentValue < result.value) {
     242           1 :             result.value = parentValue;
     243           1 :             result.summary =
     244           1 :                 String.format(OVERRIDDEN_BY_PARENT, parent.cachedConfig.getProject().getNameKey());
     245             :           }
     246           1 :         } else if (parentValue > 0) {
     247           1 :           result.value = parentValue;
     248           1 :           result.summary =
     249           1 :               String.format(INHERITED_FROM_PARENT, parent.cachedConfig.getProject().getNameKey());
     250             :         }
     251           1 :       }
     252             :     }
     253             : 
     254         105 :     if (globalMaxObjectSizeLimit > 0 && result.value > 0) {
     255           1 :       if (globalMaxObjectSizeLimit < result.value) {
     256           1 :         result.value = globalMaxObjectSizeLimit;
     257           1 :         result.summary = OVERRIDDEN_BY_GLOBAL;
     258             :       }
     259         105 :     } else if (globalMaxObjectSizeLimit > result.value) {
     260             :       // zero means "no limit", in this case the max is more limiting
     261           1 :       result.value = globalMaxObjectSizeLimit;
     262           1 :       result.summary = INHERITED_FROM_GLOBAL;
     263             :     }
     264         105 :     return result;
     265             :   }
     266             : 
     267             :   /** Get the sections that pertain only to this project. */
     268             :   List<SectionMatcher> getLocalAccessSections() {
     269         145 :     if (localAccessSections != null) {
     270           0 :       return localAccessSections;
     271             :     }
     272         145 :     List<SectionMatcher> sm = new ArrayList<>(cachedConfig.getAccessSections().values().size());
     273         145 :     for (AccessSection section : cachedConfig.getAccessSections().values()) {
     274         145 :       SectionMatcher matcher = SectionMatcher.wrap(getNameKey(), section);
     275         145 :       if (matcher != null) {
     276         145 :         sm.add(matcher);
     277             :       }
     278         145 :     }
     279         145 :     localAccessSections = sm;
     280         145 :     return localAccessSections;
     281             :   }
     282             : 
     283             :   /**
     284             :    * Obtain all local and inherited sections. This collection is looked up dynamically and is not
     285             :    * cached. Callers should try to cache this result per-request as much as possible.
     286             :    */
     287             :   public List<SectionMatcher> getAllSections() {
     288         145 :     if (isAllProjects) {
     289         103 :       return getLocalAccessSections();
     290             :     }
     291             : 
     292         145 :     List<SectionMatcher> all = new ArrayList<>();
     293         145 :     for (ProjectState s : tree()) {
     294         145 :       all.addAll(s.getLocalAccessSections());
     295         145 :     }
     296         145 :     return all;
     297             :   }
     298             : 
     299             :   /**
     300             :    * Returns all {@link AccountGroup}'s to which the owner privilege for 'refs/*' is assigned for
     301             :    * this project (the local owners), if there are no local owners the local owners of the nearest
     302             :    * parent project that has local owners are returned
     303             :    */
     304             :   public Set<AccountGroup.UUID> getOwners() {
     305           1 :     for (ProjectState p : tree()) {
     306           1 :       if (!p.localOwners.isEmpty()) {
     307           1 :         return p.localOwners;
     308             :       }
     309           0 :     }
     310           0 :     return Collections.emptySet();
     311             :   }
     312             : 
     313             :   /**
     314             :    * Returns all {@link AccountGroup}'s that are allowed to administrate the complete project. This
     315             :    * includes all groups to which the owner privilege for 'refs/*' is assigned for this project (the
     316             :    * local owners) and all groups to which the owner privilege for 'refs/*' is assigned for one of
     317             :    * the parent projects (the inherited owners).
     318             :    */
     319             :   public Set<AccountGroup.UUID> getAllOwners() {
     320         145 :     Set<AccountGroup.UUID> result = new HashSet<>();
     321             : 
     322         145 :     for (ProjectState p : tree()) {
     323         145 :       result.addAll(p.localOwners);
     324         145 :     }
     325             : 
     326         145 :     return result;
     327             :   }
     328             : 
     329             :   /**
     330             :    * Returns an iterable that walks through this project and then the parents of this project.
     331             :    * Starts from this project and progresses up the hierarchy to All-Projects.
     332             :    */
     333             :   public Iterable<ProjectState> tree() {
     334         148 :     return () -> new ProjectHierarchyIterator(projectCache, allProjectsName, ProjectState.this);
     335             :   }
     336             : 
     337             :   /**
     338             :    * Returns an iterable that walks in-order from All-Projects through the project hierarchy to this
     339             :    * project.
     340             :    */
     341             :   public Iterable<ProjectState> treeInOrder() {
     342         148 :     List<ProjectState> projects = Lists.newArrayList(tree());
     343         148 :     Collections.reverse(projects);
     344         148 :     return projects;
     345             :   }
     346             : 
     347             :   /**
     348             :    * Returns an iterable that walks through the parents of this project. Starts from the immediate
     349             :    * parent of this project and progresses up the hierarchy to All-Projects.
     350             :    */
     351             :   public FluentIterable<ProjectState> parents() {
     352         110 :     return FluentIterable.from(tree()).skip(1);
     353             :   }
     354             : 
     355             :   public boolean isAllProjects() {
     356          23 :     return isAllProjects;
     357             :   }
     358             : 
     359             :   public boolean isAllUsers() {
     360         135 :     return isAllUsers;
     361             :   }
     362             : 
     363             :   public boolean is(BooleanProjectConfig config) {
     364         115 :     for (ProjectState s : tree()) {
     365         115 :       switch (s.getProject().getBooleanConfig(config)) {
     366             :         case TRUE:
     367         107 :           return true;
     368             :         case FALSE:
     369         115 :           return false;
     370             :         case INHERIT:
     371             :         default:
     372         115 :           continue;
     373             :       }
     374             :     }
     375         114 :     return false;
     376             :   }
     377             : 
     378             :   /** Get all submit requirements for a project, including those from parent projects. */
     379             :   public Map<String, SubmitRequirement> getSubmitRequirements() {
     380         103 :     Map<String, SubmitRequirement> requirements = new LinkedHashMap<>();
     381         103 :     for (ProjectState s : treeInOrder()) {
     382         103 :       for (SubmitRequirement requirement : s.getConfig().getSubmitRequirementSections().values()) {
     383           2 :         String lowerName = requirement.name().toLowerCase();
     384           2 :         SubmitRequirement old = requirements.get(lowerName);
     385           2 :         if (old == null || old.allowOverrideInChildProjects()) {
     386           2 :           requirements.put(lowerName, requirement);
     387             :         }
     388           2 :       }
     389         103 :     }
     390         103 :     return ImmutableMap.copyOf(requirements);
     391             :   }
     392             : 
     393             :   /** All available label types. */
     394             :   public LabelTypes getLabelTypes() {
     395         145 :     Map<String, LabelType> types = new LinkedHashMap<>();
     396         145 :     for (ProjectState s : treeInOrder()) {
     397         145 :       for (LabelType type : s.getConfig().getLabelSections().values()) {
     398         145 :         String lower = type.getName().toLowerCase();
     399         145 :         LabelType old = types.get(lower);
     400         145 :         if (old == null || old.isCanOverride()) {
     401         145 :           types.put(lower, type);
     402             :         }
     403         145 :       }
     404         145 :     }
     405         145 :     List<LabelType> all = Lists.newArrayListWithCapacity(types.size());
     406         145 :     for (LabelType type : types.values()) {
     407         145 :       if (!type.getValues().isEmpty()) {
     408         145 :         all.add(type);
     409             :       }
     410         145 :     }
     411         145 :     return new LabelTypes(Collections.unmodifiableList(all));
     412             :   }
     413             : 
     414             :   /** All available label types for this change. */
     415             :   public LabelTypes getLabelTypes(ChangeNotes notes) {
     416         103 :     return getLabelTypes(notes.getChange().getDest());
     417             :   }
     418             : 
     419             :   /** All available label types for this branch. */
     420             :   public LabelTypes getLabelTypes(BranchNameKey destination) {
     421         103 :     List<LabelType> all = getLabelTypes().getLabelTypes();
     422             : 
     423         103 :     List<LabelType> r = Lists.newArrayListWithCapacity(all.size());
     424         103 :     for (LabelType l : all) {
     425         103 :       List<String> refs = l.getRefPatterns();
     426         103 :       if (refs == null) {
     427         103 :         r.add(l);
     428             :       } else {
     429           1 :         for (String refPattern : refs) {
     430           1 :           if (refPattern.contains("${")) {
     431           0 :             logger.atWarning().log(
     432             :                 "Ref pattern for label %s in project %s contains illegal expanded parameters: %s."
     433             :                     + " Ref pattern will be ignored.",
     434           0 :                 l, getName(), refPattern);
     435           0 :             continue;
     436             :           }
     437             : 
     438           1 :           if (AccessSection.isValidRefSectionName(refPattern) && match(destination, refPattern)) {
     439           1 :             r.add(l);
     440           1 :             break;
     441             :           }
     442           1 :         }
     443             :       }
     444         103 :     }
     445             : 
     446         103 :     return new LabelTypes(r);
     447             :   }
     448             : 
     449             :   public List<CommentLinkInfo> getCommentLinks() {
     450          22 :     Map<String, CommentLinkInfo> cls = new LinkedHashMap<>();
     451          22 :     for (CommentLinkInfo cl : commentLinks) {
     452           1 :       cls.put(cl.name.toLowerCase(), cl);
     453           1 :     }
     454          22 :     for (ProjectState s : treeInOrder()) {
     455          22 :       for (StoredCommentLinkInfo cl : s.getConfig().getCommentLinkSections().values()) {
     456           1 :         String name = cl.getName().toLowerCase();
     457           1 :         if (cl.getOverrideOnly()) {
     458           0 :           CommentLinkInfo parent = cls.get(name);
     459           0 :           if (parent == null) {
     460           0 :             continue; // Ignore invalid overrides.
     461             :           }
     462           0 :           cls.put(name, StoredCommentLinkInfo.fromInfo(parent, cl.getEnabled()).toInfo());
     463           0 :         } else {
     464           1 :           cls.put(name, cl.toInfo());
     465             :         }
     466           1 :       }
     467          22 :     }
     468          22 :     return ImmutableList.copyOf(cls.values());
     469             :   }
     470             : 
     471             :   /**
     472             :    * Returns the {@link PluginConfig} that got parsed from the {@code plugins} section of {@code
     473             :    * project.config}. The returned instance is a defensive copy of the cached value. Returns an
     474             :    * empty config in case we find no config for the given plugin name. This is useful when calling
     475             :    * {@code PluginConfig#withInheritance(ProjectState.Factory)}
     476             :    */
     477             :   public PluginConfig getPluginConfig(String pluginName) {
     478           2 :     if (getConfig().getPluginConfigs().containsKey(pluginName)) {
     479           2 :       Config config = new Config();
     480             :       try {
     481           2 :         config.fromText(getConfig().getPluginConfigs().get(pluginName));
     482           0 :       } catch (ConfigInvalidException e) {
     483             :         // This is OK to propagate as IllegalStateException because it's a programmer error.
     484             :         // The config was converted to a String using Config#toText. So #fromText must not
     485             :         // throw a ConfigInvalidException
     486           0 :         throw new IllegalStateException("invalid plugin config for " + pluginName, e);
     487           2 :       }
     488           2 :       return PluginConfig.create(pluginName, config, getConfig());
     489             :     }
     490           2 :     return PluginConfig.create(pluginName, new Config(), getConfig());
     491             :   }
     492             : 
     493             :   public Optional<BranchOrderSection> getBranchOrderSection() {
     494           1 :     for (ProjectState s : tree()) {
     495           1 :       Optional<BranchOrderSection> section = s.getConfig().getBranchOrderSection();
     496           1 :       if (section.isPresent()) {
     497           1 :         return section;
     498             :       }
     499           0 :     }
     500           0 :     return Optional.empty();
     501             :   }
     502             : 
     503             :   public Collection<SubscribeSection> getSubscribeSections(BranchNameKey branch) {
     504          65 :     Collection<SubscribeSection> ret = new ArrayList<>();
     505          65 :     for (ProjectState s : tree()) {
     506          65 :       ret.addAll(s.getConfig().getSubscribeSections(branch));
     507          65 :     }
     508          65 :     return ret;
     509             :   }
     510             : 
     511             :   public Set<GroupReference> getAllGroups() {
     512           0 :     return getGroups(getAllSections());
     513             :   }
     514             : 
     515             :   public Set<GroupReference> getLocalGroups() {
     516           1 :     return getGroups(getLocalAccessSections());
     517             :   }
     518             : 
     519             :   public SubmitType getSubmitType() {
     520         107 :     for (ProjectState s : tree()) {
     521         107 :       SubmitType t = s.getProject().getSubmitType();
     522         107 :       if (t != SubmitType.INHERIT) {
     523         107 :         return t;
     524             :       }
     525          95 :     }
     526           0 :     return Project.DEFAULT_ALL_PROJECTS_SUBMIT_TYPE;
     527             :   }
     528             : 
     529             :   private static Set<GroupReference> getGroups(List<SectionMatcher> sectionMatcherList) {
     530           1 :     final Set<GroupReference> all = new HashSet<>();
     531           1 :     for (SectionMatcher matcher : sectionMatcherList) {
     532           1 :       final AccessSection section = matcher.getSection();
     533           1 :       for (Permission permission : section.getPermissions()) {
     534           1 :         for (PermissionRule rule : permission.getRules()) {
     535           1 :           all.add(rule.getGroup());
     536           1 :         }
     537           1 :       }
     538           1 :     }
     539           1 :     return all;
     540             :   }
     541             : 
     542             :   public ProjectData toProjectData() {
     543         147 :     ProjectData project = null;
     544         147 :     for (ProjectState state : treeInOrder()) {
     545         147 :       project = new ProjectData(state.getProject(), Optional.ofNullable(project));
     546         147 :     }
     547         147 :     return project;
     548             :   }
     549             : 
     550             :   private boolean match(BranchNameKey destination, String refPattern) {
     551           1 :     return RefPatternMatcher.getMatcher(refPattern).match(destination.branch(), null);
     552             :   }
     553             : }

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