LCOV - code coverage report
Current view: top level - server/notedb - DeleteCommentRewriter.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 76 79 96.2 %
Date: 2022-11-19 15:00:39 Functions: 11 11 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2017 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.notedb;
      16             : 
      17             : import static com.google.common.base.Preconditions.checkArgument;
      18             : import static java.util.stream.Collectors.toList;
      19             : import static java.util.stream.Collectors.toMap;
      20             : import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
      21             : 
      22             : import com.google.common.annotations.VisibleForTesting;
      23             : import com.google.gerrit.entities.Change;
      24             : import com.google.gerrit.entities.HumanComment;
      25             : import com.google.gerrit.entities.RefNames;
      26             : import com.google.inject.Inject;
      27             : import com.google.inject.assistedinject.Assisted;
      28             : import java.io.IOException;
      29             : import java.util.ArrayList;
      30             : import java.util.List;
      31             : import java.util.Map;
      32             : import java.util.function.Function;
      33             : import org.eclipse.jgit.errors.ConfigInvalidException;
      34             : import org.eclipse.jgit.lib.CommitBuilder;
      35             : import org.eclipse.jgit.lib.ObjectId;
      36             : import org.eclipse.jgit.lib.ObjectInserter;
      37             : import org.eclipse.jgit.lib.ObjectReader;
      38             : import org.eclipse.jgit.notes.NoteMap;
      39             : import org.eclipse.jgit.revwalk.RevCommit;
      40             : import org.eclipse.jgit.revwalk.RevSort;
      41             : import org.eclipse.jgit.revwalk.RevWalk;
      42             : 
      43             : /**
      44             :  * Deletes a published comment from NoteDb by rewriting the commit history. Instead of deleting the
      45             :  * whole comment, it just replaces the comment's message with a new message.
      46             :  */
      47             : public class DeleteCommentRewriter implements NoteDbRewriter {
      48             : 
      49             :   public interface Factory {
      50             :     /**
      51             :      * Creates a DeleteCommentRewriter instance.
      52             :      *
      53             :      * @param id the id of the change which contains the target comment.
      54             :      * @param uuid the uuid of the target comment.
      55             :      * @param newMessage the message used to replace the old message of the target comment.
      56             :      * @return the DeleteCommentRewriter instance
      57             :      */
      58             :     DeleteCommentRewriter create(
      59             :         Change.Id id, @Assisted("uuid") String uuid, @Assisted("newMessage") String newMessage);
      60             :   }
      61             : 
      62             :   private final ChangeNoteUtil noteUtil;
      63             :   private final Change.Id changeId;
      64             :   private final String uuid;
      65             :   private final String newMessage;
      66             : 
      67             :   @Inject
      68             :   DeleteCommentRewriter(
      69             :       ChangeNoteUtil noteUtil,
      70             :       @Assisted Change.Id changeId,
      71             :       @Assisted("uuid") String uuid,
      72           3 :       @Assisted("newMessage") String newMessage) {
      73           3 :     this.noteUtil = noteUtil;
      74           3 :     this.changeId = changeId;
      75           3 :     this.uuid = uuid;
      76           3 :     this.newMessage = newMessage;
      77           3 :   }
      78             : 
      79             :   @Override
      80             :   public String getRefName() {
      81           3 :     return RefNames.changeMetaRef(changeId);
      82             :   }
      83             : 
      84             :   @Override
      85             :   public ObjectId rewriteCommitHistory(RevWalk revWalk, ObjectInserter inserter, ObjectId currTip)
      86             :       throws IOException, ConfigInvalidException {
      87           3 :     checkArgument(!currTip.equals(ObjectId.zeroId()));
      88             : 
      89             :     // Walk from the first commit of the branch.
      90           3 :     revWalk.reset();
      91           3 :     revWalk.markStart(revWalk.parseCommit(currTip));
      92           3 :     revWalk.sort(RevSort.REVERSE);
      93             : 
      94           3 :     ObjectReader reader = revWalk.getObjectReader();
      95           3 :     RevCommit newTipCommit = revWalk.next(); // The first commit will not be rewritten.
      96           3 :     Map<String, HumanComment> parentComments =
      97           3 :         getPublishedComments(noteUtil, reader, NoteMap.read(reader, newTipCommit));
      98             : 
      99           3 :     boolean rewrite = false;
     100             :     RevCommit originalCommit;
     101           3 :     while ((originalCommit = revWalk.next()) != null) {
     102           3 :       NoteMap noteMap = NoteMap.read(reader, originalCommit);
     103           3 :       Map<String, HumanComment> currComments = getPublishedComments(noteUtil, reader, noteMap);
     104             : 
     105           3 :       if (!rewrite && currComments.containsKey(uuid)) {
     106           3 :         rewrite = true;
     107             :       }
     108             : 
     109           3 :       if (!rewrite) {
     110           3 :         parentComments = currComments;
     111           3 :         newTipCommit = originalCommit;
     112           3 :         continue;
     113             :       }
     114             : 
     115           3 :       List<HumanComment> putInComments = getPutInComments(parentComments, currComments);
     116           3 :       List<HumanComment> deletedComments = getDeletedComments(parentComments, currComments);
     117           3 :       newTipCommit =
     118           3 :           revWalk.parseCommit(
     119           3 :               rewriteCommit(
     120             :                   originalCommit, newTipCommit, inserter, reader, putInComments, deletedComments));
     121           3 :       parentComments = currComments;
     122           3 :     }
     123             : 
     124           3 :     return newTipCommit;
     125             :   }
     126             : 
     127             :   /**
     128             :    * Gets all the comments which are presented at a commit. Note they include the comments put in by
     129             :    * the previous commits.
     130             :    */
     131             :   @VisibleForTesting
     132             :   public static Map<String, HumanComment> getPublishedComments(
     133             :       ChangeNoteJson changeNoteJson, ObjectReader reader, NoteMap noteMap)
     134             :       throws IOException, ConfigInvalidException {
     135           3 :     return RevisionNoteMap.parse(changeNoteJson, reader, noteMap, HumanComment.Status.PUBLISHED)
     136           3 :         .revisionNotes.values().stream()
     137           3 :         .flatMap(n -> n.getEntities().stream())
     138           3 :         .collect(toMap(c -> c.key.uuid, Function.identity()));
     139             :   }
     140             : 
     141             :   public static Map<String, HumanComment> getPublishedComments(
     142             :       ChangeNoteUtil noteUtil, ObjectReader reader, NoteMap noteMap)
     143             :       throws IOException, ConfigInvalidException {
     144           3 :     return getPublishedComments(noteUtil.getChangeNoteJson(), reader, noteMap);
     145             :   }
     146             :   /**
     147             :    * Gets the comments put in by the current commit. The message of the target comment will be
     148             :    * replaced by the new message.
     149             :    *
     150             :    * @param parMap the comment map of the parent commit.
     151             :    * @param curMap the comment map of the current commit.
     152             :    * @return The comments put in by the current commit.
     153             :    */
     154             :   private List<HumanComment> getPutInComments(
     155             :       Map<String, HumanComment> parMap, Map<String, HumanComment> curMap) {
     156           3 :     List<HumanComment> comments = new ArrayList<>();
     157           3 :     for (String key : curMap.keySet()) {
     158           3 :       if (!parMap.containsKey(key)) {
     159           3 :         HumanComment comment = curMap.get(key);
     160           3 :         if (key.equals(uuid)) {
     161           3 :           comment.message = newMessage;
     162           3 :           comment.unresolved = false;
     163             :         }
     164           3 :         comments.add(comment);
     165             :       }
     166           3 :     }
     167           3 :     return comments;
     168             :   }
     169             : 
     170             :   /**
     171             :    * Gets the comments deleted by the current commit.
     172             :    *
     173             :    * @param parMap the comment map of the parent commit.
     174             :    * @param curMap the comment map of the current commit.
     175             :    * @return The comments deleted by the current commit.
     176             :    */
     177             :   private List<HumanComment> getDeletedComments(
     178             :       Map<String, HumanComment> parMap, Map<String, HumanComment> curMap) {
     179           3 :     return parMap.entrySet().stream()
     180           3 :         .filter(c -> !curMap.containsKey(c.getKey()))
     181           3 :         .map(Map.Entry::getValue)
     182           3 :         .collect(toList());
     183             :   }
     184             : 
     185             :   /**
     186             :    * Rewrites one commit.
     187             :    *
     188             :    * @param originalCommit the original commit to be rewritten.
     189             :    * @param parentCommit the parent of the new commit.
     190             :    * @param inserter the {@code ObjectInserter} for the rewrite process.
     191             :    * @param reader the {@code ObjectReader} for the rewrite process.
     192             :    * @param putInComments the comments put in by this commit.
     193             :    * @param deletedComments the comments deleted by this commit.
     194             :    * @return the {@code objectId} of the new commit.
     195             :    */
     196             :   private ObjectId rewriteCommit(
     197             :       RevCommit originalCommit,
     198             :       RevCommit parentCommit,
     199             :       ObjectInserter inserter,
     200             :       ObjectReader reader,
     201             :       List<HumanComment> putInComments,
     202             :       List<HumanComment> deletedComments)
     203             :       throws IOException, ConfigInvalidException {
     204           3 :     RevisionNoteMap<ChangeRevisionNote> revNotesMap =
     205           3 :         RevisionNoteMap.parse(
     206           3 :             noteUtil.getChangeNoteJson(),
     207             :             reader,
     208           3 :             NoteMap.read(reader, parentCommit),
     209             :             HumanComment.Status.PUBLISHED);
     210           3 :     RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(revNotesMap);
     211             : 
     212           3 :     for (HumanComment c : putInComments) {
     213           3 :       cache.get(c.getCommitId()).putComment(c);
     214           3 :     }
     215             : 
     216           3 :     for (HumanComment c : deletedComments) {
     217           0 :       cache.get(c.getCommitId()).deleteComment(c.key);
     218           0 :     }
     219             : 
     220           3 :     Map<ObjectId, RevisionNoteBuilder> builders = cache.getBuilders();
     221           3 :     for (Map.Entry<ObjectId, RevisionNoteBuilder> entry : builders.entrySet()) {
     222           3 :       ObjectId objectId = entry.getKey();
     223           3 :       byte[] data = entry.getValue().build(noteUtil.getChangeNoteJson());
     224           3 :       if (data.length == 0) {
     225           0 :         revNotesMap.noteMap.remove(objectId);
     226             :       } else {
     227           3 :         revNotesMap.noteMap.set(objectId, inserter.insert(OBJ_BLOB, data));
     228             :       }
     229           3 :     }
     230             : 
     231           3 :     CommitBuilder cb = new CommitBuilder();
     232           3 :     cb.setParentId(parentCommit);
     233           3 :     cb.setTreeId(revNotesMap.noteMap.writeTree(inserter));
     234           3 :     cb.setMessage(originalCommit.getFullMessage());
     235           3 :     cb.setCommitter(originalCommit.getCommitterIdent());
     236           3 :     cb.setAuthor(originalCommit.getAuthorIdent());
     237           3 :     cb.setEncoding(originalCommit.getEncoding());
     238             : 
     239           3 :     return inserter.insert(cb);
     240             :   }
     241             : }

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