LCOV - code coverage report
Current view: top level - server/restapi/change - SubmittedTogether.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 54 70 77.1 %
Date: 2022-11-19 15:00:39 Functions: 8 10 80.0 %

          Line data    Source code
       1             : // Copyright (C) 2015 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.change;
      16             : 
      17             : import static com.google.common.collect.ImmutableList.toImmutableList;
      18             : import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
      19             : import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.TOPIC_CLOSURE;
      20             : import static java.util.Collections.reverseOrder;
      21             : 
      22             : import com.google.common.collect.ImmutableList;
      23             : import com.google.common.flogger.FluentLogger;
      24             : import com.google.gerrit.entities.Change;
      25             : import com.google.gerrit.exceptions.StorageException;
      26             : import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
      27             : import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
      28             : import com.google.gerrit.extensions.client.ListChangesOption;
      29             : import com.google.gerrit.extensions.restapi.AuthException;
      30             : import com.google.gerrit.extensions.restapi.BadRequestException;
      31             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      32             : import com.google.gerrit.extensions.restapi.Response;
      33             : import com.google.gerrit.extensions.restapi.RestReadView;
      34             : import com.google.gerrit.server.change.ChangeJson;
      35             : import com.google.gerrit.server.change.ChangeResource;
      36             : import com.google.gerrit.server.change.WalkSorter;
      37             : import com.google.gerrit.server.change.WalkSorter.PatchSetData;
      38             : import com.google.gerrit.server.permissions.PermissionBackendException;
      39             : import com.google.gerrit.server.query.change.ChangeData;
      40             : import com.google.gerrit.server.query.change.InternalChangeQuery;
      41             : import com.google.gerrit.server.submit.ChangeSet;
      42             : import com.google.gerrit.server.submit.MergeSuperSet;
      43             : import com.google.inject.Inject;
      44             : import com.google.inject.Provider;
      45             : import java.io.IOException;
      46             : import java.util.Collections;
      47             : import java.util.Comparator;
      48             : import java.util.EnumSet;
      49             : import java.util.List;
      50             : import java.util.Set;
      51             : import org.kohsuke.args4j.Option;
      52             : 
      53             : public class SubmittedTogether implements RestReadView<ChangeResource> {
      54          57 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      55             : 
      56          57 :   private final EnumSet<SubmittedTogetherOption> options =
      57          57 :       EnumSet.noneOf(SubmittedTogetherOption.class);
      58             : 
      59          57 :   private final EnumSet<ListChangesOption> jsonOpt =
      60          57 :       EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.SUBMITTABLE);
      61             : 
      62          57 :   private static final Comparator<ChangeData> COMPARATOR =
      63          57 :       Comparator.comparing(ChangeData::project)
      64          57 :           .thenComparing(cd -> cd.getId().get(), reverseOrder());
      65             : 
      66             :   private final ChangeJson.Factory json;
      67             :   private final Provider<InternalChangeQuery> queryProvider;
      68             :   private final Provider<MergeSuperSet> mergeSuperSet;
      69             :   private final Provider<WalkSorter> sorter;
      70             : 
      71             :   @Option(name = "-o", usage = "Output options")
      72             :   void addOption(String option) {
      73           0 :     for (ListChangesOption o : ListChangesOption.values()) {
      74           0 :       if (o.name().equalsIgnoreCase(option)) {
      75           0 :         jsonOpt.add(o);
      76           0 :         return;
      77             :       }
      78             :     }
      79             : 
      80           0 :     for (SubmittedTogetherOption o : SubmittedTogetherOption.values()) {
      81           0 :       if (o.name().equalsIgnoreCase(option)) {
      82           0 :         options.add(o);
      83           0 :         return;
      84             :       }
      85             :     }
      86             : 
      87           0 :     throw new IllegalArgumentException("option not recognized: " + option);
      88             :   }
      89             : 
      90             :   @Inject
      91             :   SubmittedTogether(
      92             :       ChangeJson.Factory json,
      93             :       Provider<InternalChangeQuery> queryProvider,
      94             :       Provider<MergeSuperSet> mergeSuperSet,
      95          57 :       Provider<WalkSorter> sorter) {
      96          57 :     this.json = json;
      97          57 :     this.queryProvider = queryProvider;
      98          57 :     this.mergeSuperSet = mergeSuperSet;
      99          57 :     this.sorter = sorter;
     100          57 :   }
     101             : 
     102             :   public SubmittedTogether addListChangesOption(Set<ListChangesOption> o) {
     103          10 :     jsonOpt.addAll(o);
     104          10 :     return this;
     105             :   }
     106             : 
     107             :   public SubmittedTogether addSubmittedTogetherOption(Set<SubmittedTogetherOption> o) {
     108          10 :     options.addAll(o);
     109          10 :     return this;
     110             :   }
     111             : 
     112             :   @Override
     113             :   public Response<Object> apply(ChangeResource resource)
     114             :       throws AuthException, BadRequestException, ResourceConflictException, IOException,
     115             :           PermissionBackendException {
     116           1 :     SubmittedTogetherInfo info = applyInfo(resource);
     117           1 :     if (options.isEmpty()) {
     118           1 :       return Response.ok(info.changes);
     119             :     }
     120           0 :     return Response.ok(info);
     121             :   }
     122             : 
     123             :   public SubmittedTogetherInfo applyInfo(ChangeResource resource)
     124             :       throws AuthException, IOException, PermissionBackendException {
     125          11 :     Change c = resource.getChange();
     126             :     try {
     127             :       List<ChangeData> cds;
     128             :       int hidden;
     129             : 
     130          11 :       if (c.isNew()) {
     131          10 :         ChangeSet cs =
     132             :             mergeSuperSet
     133          10 :                 .get()
     134          10 :                 .completeChangeSet(c, resource.getUser(), options.contains(TOPIC_CLOSURE));
     135          10 :         cds = ensureRequiredDataIsLoaded(cs.changes().asList());
     136          10 :         hidden = cs.nonVisibleChanges().size();
     137          11 :       } else if (c.isMerged()) {
     138           8 :         cds = queryProvider.get().bySubmissionId(c.getSubmissionId());
     139           8 :         hidden = 0;
     140             :       } else {
     141           0 :         cds = Collections.emptyList();
     142           0 :         hidden = 0;
     143             :       }
     144             : 
     145          11 :       if (hidden != 0 && !options.contains(NON_VISIBLE_CHANGES)) {
     146           0 :         throw new AuthException("change would be submitted with a change that you cannot see");
     147             :       }
     148             : 
     149          11 :       cds = sort(cds, hidden);
     150          11 :       SubmittedTogetherInfo info = new SubmittedTogetherInfo();
     151          11 :       info.changes = json.create(jsonOpt).format(cds);
     152          11 :       info.nonVisibleChanges = hidden;
     153          11 :       return info;
     154           0 :     } catch (StorageException | IOException e) {
     155           0 :       logger.atSevere().withCause(e).log("Error on getting a ChangeSet");
     156           0 :       throw e;
     157             :     }
     158             :   }
     159             : 
     160             :   private ImmutableList<ChangeData> sort(List<ChangeData> cds, int hidden) throws IOException {
     161          11 :     if (cds.size() <= 1 && hidden == 0) {
     162             :       // Skip sorting for singleton lists, to avoid WalkSorter opening the
     163             :       // repo just to fill out the commit field in PatchSetData.
     164           8 :       return ImmutableList.of();
     165             :     }
     166             : 
     167          10 :     long numProjectsDistinct = cds.stream().map(ChangeData::project).distinct().count();
     168          10 :     long numProjects = cds.stream().map(ChangeData::project).count();
     169             : 
     170          10 :     if (numProjects == numProjectsDistinct || numProjectsDistinct > 5) {
     171             :       // We either have only a single change per project which means that WalkSorter won't make a
     172             :       // difference compared to our index-backed sort, or we are looking at more than 5 projects
     173             :       // which would make WalkSorter too expensive for this call.
     174           1 :       return cds.stream().sorted(COMPARATOR).collect(toImmutableList());
     175             :     }
     176             : 
     177             :     // Perform more expensive walk-sort.
     178          10 :     ImmutableList.Builder<ChangeData> sorted = ImmutableList.builderWithExpectedSize(cds.size());
     179          10 :     for (PatchSetData psd : sorter.get().sort(cds)) {
     180          10 :       sorted.add(psd.data());
     181          10 :     }
     182          10 :     return sorted.build();
     183             :   }
     184             : 
     185             :   private static List<ChangeData> ensureRequiredDataIsLoaded(List<ChangeData> cds) {
     186             :     // TODO(hiesel): Instead of calling these manually, either implement a helper that brings a
     187             :     // database-backed change on-par with an index-backed change in terms of the populated fields in
     188             :     // ChangeData or check if any of the ChangeDatas was loaded from the database and allow
     189             :     // lazyloading if so.
     190          10 :     for (ChangeData cd : cds) {
     191          10 :       cd.submitRecords(ChangeJson.SUBMIT_RULE_OPTIONS_LENIENT);
     192          10 :       cd.submitRecords(ChangeJson.SUBMIT_RULE_OPTIONS_STRICT);
     193          10 :       cd.currentPatchSet();
     194          10 :     }
     195          10 :     return cds;
     196             :   }
     197             : }

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