LCOV - code coverage report
Current view: top level - server - ChangeUtil.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 45 47 95.7 %
Date: 2022-11-19 15:00:39 Functions: 10 10 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2009 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;
      16             : 
      17             : import static java.util.Comparator.comparingInt;
      18             : import static java.util.stream.Collectors.toSet;
      19             : 
      20             : import com.google.common.collect.Ordering;
      21             : import com.google.common.io.BaseEncoding;
      22             : import com.google.gerrit.common.FooterConstants;
      23             : import com.google.gerrit.entities.Change;
      24             : import com.google.gerrit.entities.PatchSet;
      25             : import com.google.gerrit.extensions.restapi.BadRequestException;
      26             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      27             : import com.google.gerrit.server.config.UrlFormatter;
      28             : import com.google.gerrit.server.util.CommitMessageUtil;
      29             : import com.google.inject.Singleton;
      30             : import java.io.IOException;
      31             : import java.security.SecureRandom;
      32             : import java.util.Collection;
      33             : import java.util.List;
      34             : import java.util.Optional;
      35             : import java.util.Random;
      36             : import java.util.Set;
      37             : import java.util.regex.Matcher;
      38             : import java.util.regex.Pattern;
      39             : import java.util.stream.Stream;
      40             : import org.eclipse.jgit.lib.Constants;
      41             : import org.eclipse.jgit.lib.ObjectId;
      42             : import org.eclipse.jgit.lib.Ref;
      43             : import org.eclipse.jgit.lib.Repository;
      44             : import org.eclipse.jgit.revwalk.RevCommit;
      45             : 
      46             : @Singleton
      47             : public class ChangeUtil {
      48             :   public static final int TOPIC_MAX_LENGTH = 2048;
      49             : 
      50         108 :   private static final Random UUID_RANDOM = new SecureRandom();
      51         108 :   private static final BaseEncoding UUID_ENCODING = BaseEncoding.base16().lowerCase();
      52             : 
      53         108 :   public static final Ordering<PatchSet> PS_ID_ORDER =
      54         108 :       Ordering.from(comparingInt(PatchSet::number));
      55             : 
      56             :   /** Returns a new unique identifier for change message entities. */
      57             :   public static String messageUuid() {
      58          29 :     byte[] buf = new byte[8];
      59          29 :     UUID_RANDOM.nextBytes(buf);
      60          29 :     return UUID_ENCODING.encode(buf, 0, 4) + '_' + UUID_ENCODING.encode(buf, 4, 4);
      61             :   }
      62             : 
      63             :   /**
      64             :    * Get the next patch set ID from a previously-read map of refs below the change prefix.
      65             :    *
      66             :    * @param changeRefNames existing full change ref names with the same change ID as {@code id}.
      67             :    * @param id previous patch set ID.
      68             :    * @return next unused patch set ID for the same change, skipping any IDs whose corresponding ref
      69             :    *     names appear in the {@code changeRefs} map.
      70             :    */
      71             :   public static PatchSet.Id nextPatchSetIdFromChangeRefs(
      72             :       Collection<String> changeRefNames, PatchSet.Id id) {
      73          17 :     return nextPatchSetIdFromChangeRefs(changeRefNames.stream(), id);
      74             :   }
      75             : 
      76             :   private static PatchSet.Id nextPatchSetIdFromChangeRefs(
      77             :       Stream<String> changeRefNames, PatchSet.Id id) {
      78          34 :     Set<PatchSet.Id> existing =
      79             :         changeRefNames
      80          34 :             .map(PatchSet.Id::fromRef)
      81          34 :             .filter(psId -> psId != null && psId.changeId().equals(id.changeId()))
      82          34 :             .collect(toSet());
      83          34 :     PatchSet.Id next = nextPatchSetId(id);
      84          34 :     while (existing.contains(next)) {
      85           1 :       next = nextPatchSetId(next);
      86             :     }
      87          34 :     return next;
      88             :   }
      89             : 
      90             :   /**
      91             :    * Get the next patch set ID just looking at a single previous patch set ID.
      92             :    *
      93             :    * <p>This patch set ID may or may not be available in the database.
      94             :    *
      95             :    * @param id previous patch set ID.
      96             :    * @return next patch set ID for the same change, incrementing by 1.
      97             :    */
      98             :   public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
      99          52 :     return PatchSet.id(id.changeId(), id.get() + 1);
     100             :   }
     101             : 
     102             :   /**
     103             :    * Get the next patch set ID from scanning refs in the repo.
     104             :    *
     105             :    * @param git repository to scan for patch set refs.
     106             :    * @param id previous patch set ID.
     107             :    * @return next unused patch set ID for the same change, skipping any IDs whose corresponding ref
     108             :    *     names appear in the repository.
     109             :    */
     110             :   public static PatchSet.Id nextPatchSetId(Repository git, PatchSet.Id id) throws IOException {
     111          27 :     return nextPatchSetIdFromChangeRefs(
     112          27 :         git.getRefDatabase().getRefsByPrefix(id.changeId().toRefPrefix()).stream()
     113          27 :             .map(Ref::getName),
     114             :         id);
     115             :   }
     116             : 
     117             :   /**
     118             :    * Make sure that the change commit message has a correct footer.
     119             :    *
     120             :    * @param requireChangeId true if Change-Id is a mandatory footer for the project
     121             :    * @param currentChangeId current Change-Id value before the commit message is updated
     122             :    * @param newCommitMessage new commit message for the change
     123             :    * @throws ResourceConflictException if the new commit message has a missing or invalid Change-Id
     124             :    * @throws BadRequestException if the new commit message is null or empty
     125             :    */
     126             :   public static void ensureChangeIdIsCorrect(
     127             :       boolean requireChangeId, String currentChangeId, String newCommitMessage)
     128             :       throws ResourceConflictException, BadRequestException {
     129             :     RevCommit revCommit =
     130          10 :         RevCommit.parse(
     131          10 :             Constants.encode("tree " + ObjectId.zeroId().name() + "\n\n" + newCommitMessage));
     132             : 
     133             :     // Check that the commit message without footers is not empty
     134          10 :     CommitMessageUtil.checkAndSanitizeCommitMessage(revCommit.getShortMessage());
     135             : 
     136          10 :     List<String> changeIdFooters = revCommit.getFooterLines(FooterConstants.CHANGE_ID);
     137          10 :     if (requireChangeId && changeIdFooters.isEmpty()) {
     138           1 :       throw new ResourceConflictException("missing Change-Id footer");
     139             :     }
     140          10 :     if (!changeIdFooters.isEmpty() && !changeIdFooters.get(0).equals(currentChangeId)) {
     141           2 :       throw new ResourceConflictException("wrong Change-Id footer");
     142             :     }
     143          10 :     if (changeIdFooters.size() > 1) {
     144           0 :       throw new ResourceConflictException("multiple Change-Id footers");
     145             :     }
     146          10 :   }
     147             : 
     148             :   public static String status(Change c) {
     149         103 :     return c != null ? c.getStatus().name().toLowerCase() : "deleted";
     150             :   }
     151             : 
     152         108 :   private static final Pattern LINK_CHANGE_ID_PATTERN = Pattern.compile("I[0-9a-f]{40}");
     153             : 
     154             :   public static List<String> getChangeIdsFromFooter(RevCommit c, UrlFormatter urlFormatter) {
     155         108 :     List<String> changeIds = c.getFooterLines(FooterConstants.CHANGE_ID);
     156         108 :     Optional<String> webUrl = urlFormatter.getWebUrl();
     157         108 :     if (!webUrl.isPresent()) {
     158           0 :       return changeIds;
     159             :     }
     160             : 
     161         108 :     String prefix = webUrl.get() + "id/";
     162         108 :     for (String link : c.getFooterLines(FooterConstants.LINK)) {
     163           3 :       if (!link.startsWith(prefix)) {
     164           3 :         continue;
     165             :       }
     166           3 :       String changeId = link.substring(prefix.length());
     167           3 :       Matcher m = LINK_CHANGE_ID_PATTERN.matcher(changeId);
     168           3 :       if (m.matches()) {
     169           3 :         changeIds.add(changeId);
     170             :       }
     171           3 :     }
     172             : 
     173         108 :     return changeIds;
     174             :   }
     175             : 
     176             :   private ChangeUtil() {}
     177             : }

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