LCOV - code coverage report
Current view: top level - server/events - EventFactory.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 245 295 83.1 %
Date: 2022-11-19 15:00:39 Functions: 35 39 89.7 %

          Line data    Source code
       1             : // Copyright (C) 2010 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.events;
      16             : 
      17             : import static java.util.Comparator.comparing;
      18             : import static java.util.Objects.requireNonNull;
      19             : 
      20             : import com.google.common.collect.ListMultimap;
      21             : import com.google.common.collect.Lists;
      22             : import com.google.common.flogger.FluentLogger;
      23             : import com.google.gerrit.common.Nullable;
      24             : import com.google.gerrit.entities.Account;
      25             : import com.google.gerrit.entities.BranchNameKey;
      26             : import com.google.gerrit.entities.Change;
      27             : import com.google.gerrit.entities.ChangeMessage;
      28             : import com.google.gerrit.entities.HumanComment;
      29             : import com.google.gerrit.entities.LabelType;
      30             : import com.google.gerrit.entities.LabelTypes;
      31             : import com.google.gerrit.entities.LegacySubmitRequirement;
      32             : import com.google.gerrit.entities.PatchSet;
      33             : import com.google.gerrit.entities.PatchSetApproval;
      34             : import com.google.gerrit.entities.SubmitRecord;
      35             : import com.google.gerrit.entities.UserIdentity;
      36             : import com.google.gerrit.exceptions.StorageException;
      37             : import com.google.gerrit.extensions.registration.DynamicItem;
      38             : import com.google.gerrit.index.IndexConfig;
      39             : import com.google.gerrit.server.GerritPersonIdent;
      40             : import com.google.gerrit.server.account.AccountAttributeLoader;
      41             : import com.google.gerrit.server.account.AccountCache;
      42             : import com.google.gerrit.server.account.AccountState;
      43             : import com.google.gerrit.server.account.Emails;
      44             : import com.google.gerrit.server.approval.ApprovalsUtil;
      45             : import com.google.gerrit.server.change.ChangeKindCache;
      46             : import com.google.gerrit.server.config.UrlFormatter;
      47             : import com.google.gerrit.server.data.AccountAttribute;
      48             : import com.google.gerrit.server.data.ApprovalAttribute;
      49             : import com.google.gerrit.server.data.ChangeAttribute;
      50             : import com.google.gerrit.server.data.DependencyAttribute;
      51             : import com.google.gerrit.server.data.MessageAttribute;
      52             : import com.google.gerrit.server.data.PatchAttribute;
      53             : import com.google.gerrit.server.data.PatchSetAttribute;
      54             : import com.google.gerrit.server.data.PatchSetCommentAttribute;
      55             : import com.google.gerrit.server.data.RefUpdateAttribute;
      56             : import com.google.gerrit.server.data.SubmitLabelAttribute;
      57             : import com.google.gerrit.server.data.SubmitRecordAttribute;
      58             : import com.google.gerrit.server.data.SubmitRequirementAttribute;
      59             : import com.google.gerrit.server.data.TrackingIdAttribute;
      60             : import com.google.gerrit.server.notedb.ChangeNotes;
      61             : import com.google.gerrit.server.patch.DiffNotAvailableException;
      62             : import com.google.gerrit.server.patch.DiffOperations;
      63             : import com.google.gerrit.server.patch.DiffOptions;
      64             : import com.google.gerrit.server.patch.FilePathAdapter;
      65             : import com.google.gerrit.server.patch.filediff.FileDiffOutput;
      66             : import com.google.gerrit.server.query.change.ChangeData;
      67             : import com.google.gerrit.server.query.change.InternalChangeQuery;
      68             : import com.google.gerrit.server.util.AccountTemplateUtil;
      69             : import com.google.inject.Inject;
      70             : import com.google.inject.Provider;
      71             : import com.google.inject.Singleton;
      72             : import java.io.IOException;
      73             : import java.util.ArrayList;
      74             : import java.util.Collection;
      75             : import java.util.List;
      76             : import java.util.Map;
      77             : import java.util.Optional;
      78             : import java.util.Set;
      79             : import org.eclipse.jgit.lib.ObjectId;
      80             : import org.eclipse.jgit.lib.PersonIdent;
      81             : import org.eclipse.jgit.revwalk.RevCommit;
      82             : import org.eclipse.jgit.revwalk.RevWalk;
      83             : 
      84             : @Singleton
      85             : public class EventFactory {
      86         138 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      87             : 
      88             :   private final AccountCache accountCache;
      89             :   private final DynamicItem<UrlFormatter> urlFormatter;
      90             :   private final DiffOperations diffOperations;
      91             :   private final Emails emails;
      92             :   private final Provider<PersonIdent> myIdent;
      93             :   private final ChangeData.Factory changeDataFactory;
      94             :   private final ApprovalsUtil approvalsUtil;
      95             :   private final ChangeKindCache changeKindCache;
      96             :   private final Provider<InternalChangeQuery> queryProvider;
      97             :   private final IndexConfig indexConfig;
      98             :   private final AccountTemplateUtil accountTemplateUtil;
      99             : 
     100             :   @Inject
     101             :   EventFactory(
     102             :       AccountCache accountCache,
     103             :       Emails emails,
     104             :       DynamicItem<UrlFormatter> urlFormatter,
     105             :       DiffOperations diffOperations,
     106             :       @GerritPersonIdent Provider<PersonIdent> myIdent,
     107             :       ChangeData.Factory changeDataFactory,
     108             :       ApprovalsUtil approvalsUtil,
     109             :       ChangeKindCache changeKindCache,
     110             :       Provider<InternalChangeQuery> queryProvider,
     111             :       IndexConfig indexConfig,
     112         138 :       AccountTemplateUtil accountTemplateUtil) {
     113         138 :     this.accountCache = accountCache;
     114         138 :     this.urlFormatter = urlFormatter;
     115         138 :     this.emails = emails;
     116         138 :     this.diffOperations = diffOperations;
     117         138 :     this.myIdent = myIdent;
     118         138 :     this.changeDataFactory = changeDataFactory;
     119         138 :     this.approvalsUtil = approvalsUtil;
     120         138 :     this.changeKindCache = changeKindCache;
     121         138 :     this.queryProvider = queryProvider;
     122         138 :     this.indexConfig = indexConfig;
     123         138 :     this.accountTemplateUtil = accountTemplateUtil;
     124         138 :   }
     125             : 
     126             :   public ChangeAttribute asChangeAttribute(Change change, AccountAttributeLoader accountLoader) {
     127          10 :     ChangeAttribute a = new ChangeAttribute();
     128          10 :     a.project = change.getProject().get();
     129          10 :     a.branch = change.getDest().shortName();
     130          10 :     a.topic = change.getTopic();
     131          10 :     a.id = change.getKey().get();
     132          10 :     a.number = change.getId().get();
     133          10 :     a.subject = change.getSubject();
     134          10 :     a.url = getChangeUrl(change);
     135          10 :     a.owner = asAccountAttribute(change.getOwner(), accountLoader);
     136          10 :     a.assignee = asAccountAttribute(change.getAssignee(), accountLoader);
     137          10 :     a.status = change.getStatus();
     138          10 :     a.createdOn = change.getCreatedOn().getEpochSecond();
     139          10 :     a.wip = change.isWorkInProgress() ? true : null;
     140          10 :     a.isPrivate = change.isPrivate() ? true : null;
     141          10 :     a.cherryPickOfChange =
     142          10 :         change.getCherryPickOf() != null ? change.getCherryPickOf().changeId().get() : null;
     143          10 :     a.cherryPickOfPatchSet =
     144          10 :         change.getCherryPickOf() != null ? change.getCherryPickOf().get() : null;
     145          10 :     return a;
     146             :   }
     147             : 
     148             :   /** Create a {@link ChangeAttribute} instance from the specified change. */
     149             :   public ChangeAttribute asChangeAttribute(Change change, ChangeNotes notes) {
     150           8 :     ChangeAttribute a = asChangeAttribute(change, (AccountAttributeLoader) null);
     151           8 :     addHashTags(a, notes);
     152           8 :     addCommitMessage(a, notes);
     153           8 :     return a;
     154             :   }
     155             :   /**
     156             :    * Create a {@link RefUpdateAttribute} for the given old ObjectId, new ObjectId, and branch that
     157             :    * is suitable for serialization to JSON.
     158             :    */
     159             :   public RefUpdateAttribute asRefUpdateAttribute(
     160             :       ObjectId oldId, ObjectId newId, BranchNameKey refName) {
     161         115 :     RefUpdateAttribute ru = new RefUpdateAttribute();
     162         115 :     ru.newRev = newId != null ? newId.getName() : ObjectId.zeroId().getName();
     163         115 :     ru.oldRev = oldId != null ? oldId.getName() : ObjectId.zeroId().getName();
     164         115 :     ru.project = refName.project().get();
     165         115 :     ru.refName = refName.branch();
     166         115 :     return ru;
     167             :   }
     168             : 
     169             :   /** Extend the existing {@link ChangeAttribute} with additional fields. */
     170             :   public void extend(ChangeAttribute a, Change change) {
     171           3 :     a.lastUpdated = change.getLastUpdatedOn().getEpochSecond();
     172           3 :     a.open = change.isNew();
     173           3 :   }
     174             : 
     175             :   /** Add allReviewers to an existing {@link ChangeAttribute}. */
     176             :   public void addAllReviewers(
     177             :       ChangeAttribute a, ChangeNotes notes, AccountAttributeLoader accountLoader) {
     178           1 :     Collection<Account.Id> reviewers = approvalsUtil.getReviewers(notes).all();
     179           1 :     if (!reviewers.isEmpty()) {
     180           1 :       a.allReviewers = Lists.newArrayListWithCapacity(reviewers.size());
     181           1 :       for (Account.Id id : reviewers) {
     182           1 :         a.allReviewers.add(asAccountAttribute(id, accountLoader));
     183           1 :       }
     184             :     }
     185           1 :   }
     186             : 
     187             :   /** Add submitRecords to an existing {@link ChangeAttribute}. */
     188             :   public void addSubmitRecords(
     189             :       ChangeAttribute ca, List<SubmitRecord> submitRecords, AccountAttributeLoader accountLoader) {
     190           1 :     ca.submitRecords = new ArrayList<>();
     191             : 
     192           1 :     for (SubmitRecord submitRecord : submitRecords) {
     193           1 :       SubmitRecordAttribute sa = new SubmitRecordAttribute();
     194           1 :       sa.status = submitRecord.status.name();
     195           1 :       if (submitRecord.status != SubmitRecord.Status.RULE_ERROR) {
     196           1 :         addSubmitRecordLabels(submitRecord, sa, accountLoader);
     197           1 :         addSubmitRecordRequirements(submitRecord, sa);
     198             :       }
     199           1 :       ca.submitRecords.add(sa);
     200           1 :     }
     201             :     // Remove empty lists so a confusing label won't be displayed in the output.
     202           1 :     if (ca.submitRecords.isEmpty()) {
     203           0 :       ca.submitRecords = null;
     204             :     }
     205           1 :   }
     206             : 
     207             :   private void addSubmitRecordLabels(
     208             :       SubmitRecord submitRecord, SubmitRecordAttribute sa, AccountAttributeLoader accountLoader) {
     209           1 :     if (submitRecord.labels != null && !submitRecord.labels.isEmpty()) {
     210           1 :       sa.labels = new ArrayList<>();
     211           1 :       for (SubmitRecord.Label lbl : submitRecord.labels) {
     212           1 :         SubmitLabelAttribute la = new SubmitLabelAttribute();
     213           1 :         la.label = lbl.label;
     214           1 :         la.status = lbl.status.name();
     215           1 :         if (lbl.appliedBy != null) {
     216           0 :           la.by = asAccountAttribute(lbl.appliedBy, accountLoader);
     217             :         }
     218           1 :         sa.labels.add(la);
     219           1 :       }
     220             :     }
     221           1 :   }
     222             : 
     223             :   private void addSubmitRecordRequirements(SubmitRecord submitRecord, SubmitRecordAttribute sa) {
     224           1 :     if (submitRecord.requirements != null && !submitRecord.requirements.isEmpty()) {
     225           0 :       sa.requirements = new ArrayList<>();
     226           0 :       for (LegacySubmitRequirement req : submitRecord.requirements) {
     227           0 :         SubmitRequirementAttribute re = new SubmitRequirementAttribute();
     228           0 :         re.fallbackText = req.fallbackText();
     229           0 :         re.type = req.type();
     230           0 :         sa.requirements.add(re);
     231           0 :       }
     232             :     }
     233           1 :   }
     234             : 
     235             :   public void addDependencies(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs) {
     236           1 :     if (change == null || currentPs == null) {
     237           0 :       return;
     238             :     }
     239           1 :     ca.dependsOn = new ArrayList<>();
     240           1 :     ca.neededBy = new ArrayList<>();
     241             :     try {
     242           1 :       addDependsOn(rw, ca, change, currentPs);
     243           1 :       addNeededBy(rw, ca, change, currentPs);
     244           0 :     } catch (StorageException | IOException e) {
     245             :       // Squash DB exceptions and leave dependency lists partially filled.
     246           1 :     }
     247             :     // Remove empty lists so a confusing label won't be displayed in the output.
     248           1 :     if (ca.dependsOn.isEmpty()) {
     249           1 :       ca.dependsOn = null;
     250             :     }
     251           1 :     if (ca.neededBy.isEmpty()) {
     252           1 :       ca.neededBy = null;
     253             :     }
     254           1 :   }
     255             : 
     256             :   private void addDependsOn(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs)
     257             :       throws IOException {
     258           1 :     RevCommit commit = rw.parseCommit(currentPs.commitId());
     259           1 :     final List<String> parentNames = new ArrayList<>(commit.getParentCount());
     260           1 :     for (RevCommit p : commit.getParents()) {
     261           1 :       parentNames.add(p.name());
     262             :     }
     263             : 
     264             :     // Find changes in this project having a patch set matching any parent of
     265             :     // this patch set's revision.
     266           1 :     for (ChangeData cd : queryProvider.get().byProjectCommits(change.getProject(), parentNames)) {
     267           1 :       for (PatchSet ps : cd.patchSets()) {
     268           1 :         for (String p : parentNames) {
     269           1 :           if (!ps.commitId().name().equals(p)) {
     270           0 :             continue;
     271             :           }
     272           1 :           ca.dependsOn.add(newDependsOn(requireNonNull(cd.change()), ps));
     273           1 :         }
     274           1 :       }
     275           1 :     }
     276             :     // Sort by original parent order.
     277           1 :     ca.dependsOn.sort(
     278           1 :         comparing(
     279             :             d -> {
     280           0 :               for (int i = 0; i < parentNames.size(); i++) {
     281           0 :                 if (parentNames.get(i).equals(d.revision)) {
     282           0 :                   return i;
     283             :                 }
     284             :               }
     285           0 :               return parentNames.size() + 1;
     286             :             }));
     287           1 :   }
     288             : 
     289             :   private void addNeededBy(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs)
     290             :       throws IOException {
     291           1 :     if (currentPs.groups().isEmpty()) {
     292           0 :       return;
     293             :     }
     294           1 :     String rev = currentPs.commitId().name();
     295             :     // Find changes in the same related group as this patch set, having a patch
     296             :     // set whose parent matches this patch set's revision.
     297             :     for (ChangeData cd :
     298           1 :         InternalChangeQuery.byProjectGroups(
     299           1 :             queryProvider, indexConfig, change.getProject(), currentPs.groups())) {
     300             :       PATCH_SETS:
     301           1 :       for (PatchSet ps : cd.patchSets()) {
     302           1 :         RevCommit commit = rw.parseCommit(ps.commitId());
     303           1 :         for (RevCommit p : commit.getParents()) {
     304           1 :           if (!p.name().equals(rev)) {
     305           1 :             continue;
     306             :           }
     307           1 :           ca.neededBy.add(newNeededBy(requireNonNull(cd.change()), ps));
     308           1 :           continue PATCH_SETS;
     309             :         }
     310           1 :       }
     311           1 :     }
     312           1 :   }
     313             : 
     314             :   private DependencyAttribute newDependsOn(Change c, PatchSet ps) {
     315           1 :     DependencyAttribute d = newDependencyAttribute(c, ps);
     316           1 :     d.isCurrentPatchSet = ps.id().equals(c.currentPatchSetId());
     317           1 :     return d;
     318             :   }
     319             : 
     320             :   private DependencyAttribute newNeededBy(Change c, PatchSet ps) {
     321           1 :     return newDependencyAttribute(c, ps);
     322             :   }
     323             : 
     324             :   private DependencyAttribute newDependencyAttribute(Change c, PatchSet ps) {
     325           1 :     DependencyAttribute d = new DependencyAttribute();
     326           1 :     d.number = c.getId().get();
     327           1 :     d.id = c.getKey().toString();
     328           1 :     d.revision = ps.commitId().name();
     329           1 :     d.ref = ps.refName();
     330           1 :     return d;
     331             :   }
     332             : 
     333             :   public void addTrackingIds(ChangeAttribute a, ListMultimap<String, String> set) {
     334           0 :     if (!set.isEmpty()) {
     335           0 :       a.trackingIds = new ArrayList<>(set.size());
     336           0 :       for (Map.Entry<String, Collection<String>> e : set.asMap().entrySet()) {
     337           0 :         for (String id : e.getValue()) {
     338           0 :           TrackingIdAttribute t = new TrackingIdAttribute();
     339           0 :           t.system = e.getKey();
     340           0 :           t.id = id;
     341           0 :           a.trackingIds.add(t);
     342           0 :         }
     343           0 :       }
     344             :     }
     345           0 :   }
     346             : 
     347             :   public void addCommitMessage(ChangeAttribute a, String commitMessage) {
     348           8 :     a.commitMessage = commitMessage;
     349           8 :   }
     350             : 
     351             :   private void addCommitMessage(ChangeAttribute changeAttribute, ChangeNotes notes) {
     352             :     try {
     353           8 :       addCommitMessage(changeAttribute, changeDataFactory.create(notes).commitMessage());
     354           0 :     } catch (Exception e) {
     355           0 :       logger.atSevere().withCause(e).log(
     356             :           "Error while getting full commit message for change %d", changeAttribute.number);
     357           8 :     }
     358           8 :   }
     359             : 
     360             :   public void addPatchSets(
     361             :       RevWalk revWalk,
     362             :       ChangeAttribute ca,
     363             :       Collection<PatchSet> ps,
     364             :       Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
     365             :       LabelTypes labelTypes,
     366             :       AccountAttributeLoader accountLoader) {
     367           0 :     addPatchSets(revWalk, ca, ps, approvals, false, null, labelTypes, accountLoader);
     368           0 :   }
     369             : 
     370             :   public void addPatchSets(
     371             :       RevWalk revWalk,
     372             :       ChangeAttribute ca,
     373             :       Collection<PatchSet> ps,
     374             :       Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
     375             :       boolean includeFiles,
     376             :       Change change,
     377             :       LabelTypes labelTypes,
     378             :       AccountAttributeLoader accountLoader) {
     379           1 :     if (!ps.isEmpty()) {
     380           1 :       ca.patchSets = new ArrayList<>(ps.size());
     381           1 :       for (PatchSet p : ps) {
     382           1 :         PatchSetAttribute psa = asPatchSetAttribute(revWalk, change, p, accountLoader);
     383           1 :         if (approvals != null) {
     384           1 :           addApprovals(psa, p.id(), approvals, labelTypes, accountLoader);
     385             :         }
     386           1 :         ca.patchSets.add(psa);
     387           1 :         if (includeFiles) {
     388           1 :           addPatchSetFileNames(psa, change, p);
     389             :         }
     390           1 :       }
     391             :     }
     392           1 :   }
     393             : 
     394             :   public void addPatchSetComments(
     395             :       PatchSetAttribute patchSetAttribute,
     396             :       Collection<HumanComment> comments,
     397             :       AccountAttributeLoader accountLoader) {
     398           1 :     for (HumanComment comment : comments) {
     399           1 :       if (comment.key.patchSetId == patchSetAttribute.number) {
     400           1 :         if (patchSetAttribute.comments == null) {
     401           1 :           patchSetAttribute.comments = new ArrayList<>();
     402             :         }
     403           1 :         patchSetAttribute.comments.add(asPatchSetLineAttribute(comment, accountLoader));
     404             :       }
     405           1 :     }
     406           1 :   }
     407             : 
     408             :   public void addPatchSetFileNames(
     409             :       PatchSetAttribute patchSetAttribute, Change change, PatchSet patchSet) {
     410             :     try {
     411           1 :       Map<String, FileDiffOutput> modifiedFiles =
     412           1 :           diffOperations.listModifiedFilesAgainstParent(
     413           1 :               change.getProject(), patchSet.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
     414             : 
     415           1 :       for (FileDiffOutput diff : modifiedFiles.values()) {
     416           1 :         if (patchSetAttribute.files == null) {
     417           1 :           patchSetAttribute.files = new ArrayList<>();
     418             :         }
     419             : 
     420           1 :         PatchAttribute p = new PatchAttribute();
     421           1 :         p.file = FilePathAdapter.getNewPath(diff.oldPath(), diff.newPath(), diff.changeType());
     422           1 :         p.fileOld = FilePathAdapter.getOldPath(diff.oldPath(), diff.changeType());
     423           1 :         p.type = diff.changeType();
     424           1 :         p.deletions -= diff.deletions();
     425           1 :         p.insertions = diff.insertions();
     426           1 :         patchSetAttribute.files.add(p);
     427           1 :       }
     428           0 :     } catch (DiffNotAvailableException e) {
     429           0 :       logger.atSevere().withCause(e).log("Cannot get patch list");
     430           1 :     }
     431           1 :   }
     432             : 
     433             :   public void addComments(
     434             :       ChangeAttribute ca,
     435             :       Collection<ChangeMessage> messages,
     436             :       AccountAttributeLoader accountLoader) {
     437           1 :     if (!messages.isEmpty()) {
     438           1 :       ca.comments = new ArrayList<>();
     439           1 :       for (ChangeMessage message : messages) {
     440           1 :         ca.comments.add(asMessageAttribute(message, accountLoader));
     441           1 :       }
     442             :     }
     443           1 :   }
     444             : 
     445             :   public PatchSetAttribute asPatchSetAttribute(RevWalk revWalk, Change change, PatchSet patchSet) {
     446           1 :     return asPatchSetAttribute(revWalk, change, patchSet, null);
     447             :   }
     448             : 
     449             :   /** Create a PatchSetAttribute for the given patchset suitable for serialization to JSON. */
     450             :   public PatchSetAttribute asPatchSetAttribute(
     451             :       RevWalk revWalk, Change change, PatchSet patchSet, AccountAttributeLoader accountLoader) {
     452           1 :     PatchSetAttribute p = new PatchSetAttribute();
     453           1 :     p.revision = patchSet.commitId().name();
     454           1 :     p.number = patchSet.number();
     455           1 :     p.ref = patchSet.refName();
     456           1 :     p.uploader = asAccountAttribute(patchSet.uploader(), accountLoader);
     457           1 :     p.createdOn = patchSet.createdOn().getEpochSecond();
     458           1 :     PatchSet.Id pId = patchSet.id();
     459             :     try {
     460           1 :       p.parents = new ArrayList<>();
     461           1 :       RevCommit c = revWalk.parseCommit(ObjectId.fromString(p.revision));
     462           1 :       for (RevCommit parent : c.getParents()) {
     463           1 :         p.parents.add(parent.name());
     464             :       }
     465             : 
     466           1 :       UserIdentity author = emails.toUserIdentity(c.getAuthorIdent());
     467           1 :       if (author.getAccount() == null) {
     468           0 :         p.author = new AccountAttribute();
     469           0 :         p.author.email = author.getEmail();
     470           0 :         p.author.name = author.getName();
     471           0 :         p.author.username = "";
     472             :       } else {
     473           1 :         p.author = asAccountAttribute(author.getAccount(), accountLoader);
     474             :       }
     475             : 
     476           1 :       Map<String, FileDiffOutput> modifiedFiles =
     477           1 :           diffOperations.listModifiedFilesAgainstParent(
     478           1 :               change.getProject(), patchSet.commitId(), /* parentNum= */ 0, DiffOptions.DEFAULTS);
     479           1 :       for (FileDiffOutput fileDiff : modifiedFiles.values()) {
     480           1 :         p.sizeDeletions += fileDiff.deletions();
     481           1 :         p.sizeInsertions += fileDiff.insertions();
     482           1 :       }
     483           1 :       p.kind = changeKindCache.getChangeKind(change, patchSet);
     484           0 :     } catch (IOException | StorageException e) {
     485           0 :       logger.atSevere().withCause(e).log("Cannot load patch set data for %s", patchSet.id());
     486           0 :     } catch (DiffNotAvailableException e) {
     487           0 :       logger.atSevere().withCause(e).log("Cannot get size information for %s.", pId);
     488           1 :     }
     489           1 :     return p;
     490             :   }
     491             : 
     492             :   public void addApprovals(
     493             :       PatchSetAttribute p,
     494             :       PatchSet.Id id,
     495             :       Map<PatchSet.Id, Collection<PatchSetApproval>> all,
     496             :       LabelTypes labelTypes,
     497             :       AccountAttributeLoader accountLoader) {
     498           1 :     Collection<PatchSetApproval> list = all.get(id);
     499           1 :     if (list != null) {
     500           1 :       addApprovals(p, list, labelTypes, accountLoader);
     501             :     }
     502           1 :   }
     503             : 
     504             :   public void addApprovals(
     505             :       PatchSetAttribute p,
     506             :       Collection<PatchSetApproval> list,
     507             :       LabelTypes labelTypes,
     508             :       AccountAttributeLoader accountLoader) {
     509           1 :     if (!list.isEmpty()) {
     510           1 :       p.approvals = new ArrayList<>(list.size());
     511           1 :       for (PatchSetApproval a : list) {
     512           1 :         if (a.value() != 0) {
     513           1 :           p.approvals.add(asApprovalAttribute(a, labelTypes, accountLoader));
     514             :         }
     515           1 :       }
     516           1 :       if (p.approvals.isEmpty()) {
     517           0 :         p.approvals = null;
     518             :       }
     519             :     }
     520           1 :   }
     521             : 
     522             :   public AccountAttribute asAccountAttribute(Account.Id id, AccountAttributeLoader accountLoader) {
     523          10 :     return accountLoader != null ? accountLoader.get(id) : asAccountAttribute(id);
     524             :   }
     525             : 
     526             :   /** Create an AuthorAttribute for the given account suitable for serialization to JSON. */
     527             :   @Nullable
     528             :   public AccountAttribute asAccountAttribute(Account.Id id) {
     529           8 :     if (id == null) {
     530           8 :       return null;
     531             :     }
     532           8 :     return accountCache.get(id).map(this::asAccountAttribute).orElse(null);
     533             :   }
     534             : 
     535             :   /** Create an AuthorAttribute for the given account suitable for serialization to JSON. */
     536             :   public AccountAttribute asAccountAttribute(AccountState accountState) {
     537           8 :     AccountAttribute who = new AccountAttribute();
     538           8 :     who.name = accountState.account().fullName();
     539           8 :     who.email = accountState.account().preferredEmail();
     540           8 :     who.username = accountState.userName().orElse(null);
     541           8 :     return who;
     542             :   }
     543             : 
     544             :   /** Create an AuthorAttribute for the given person ident suitable for serialization to JSON. */
     545             :   public AccountAttribute asAccountAttribute(PersonIdent ident) {
     546           0 :     AccountAttribute who = new AccountAttribute();
     547           0 :     who.name = ident.getName();
     548           0 :     who.email = ident.getEmailAddress();
     549           0 :     return who;
     550             :   }
     551             : 
     552             :   /**
     553             :    * Create an ApprovalAttribute for the given approval suitable for serialization to JSON.
     554             :    *
     555             :    * @param labelTypes label types for the containing project
     556             :    * @return object suitable for serialization to JSON
     557             :    */
     558             :   public ApprovalAttribute asApprovalAttribute(
     559             :       PatchSetApproval approval, LabelTypes labelTypes, AccountAttributeLoader accountLoader) {
     560           1 :     ApprovalAttribute a = new ApprovalAttribute();
     561           1 :     a.type = approval.labelId().get();
     562           1 :     a.value = Short.toString(approval.value());
     563           1 :     a.by = asAccountAttribute(approval.accountId(), accountLoader);
     564           1 :     a.grantedOn = approval.granted().getEpochSecond();
     565           1 :     a.oldValue = null;
     566             : 
     567           1 :     Optional<LabelType> lt = labelTypes.byLabel(approval.labelId());
     568           1 :     lt.ifPresent(l -> a.description = l.getName());
     569           1 :     return a;
     570             :   }
     571             : 
     572             :   public MessageAttribute asMessageAttribute(
     573             :       ChangeMessage message, AccountAttributeLoader accountLoader) {
     574           1 :     MessageAttribute a = new MessageAttribute();
     575           1 :     a.timestamp = message.getWrittenOn().getEpochSecond();
     576           1 :     a.reviewer =
     577           1 :         message.getAuthor() != null
     578           1 :             ? asAccountAttribute(message.getAuthor(), accountLoader)
     579           1 :             : asAccountAttribute(myIdent.get());
     580           1 :     a.message = accountTemplateUtil.replaceTemplates(message.getMessage());
     581           1 :     return a;
     582             :   }
     583             : 
     584             :   public PatchSetCommentAttribute asPatchSetLineAttribute(
     585             :       HumanComment c, AccountAttributeLoader accountLoader) {
     586           1 :     PatchSetCommentAttribute a = new PatchSetCommentAttribute();
     587           1 :     a.reviewer = asAccountAttribute(c.author.getId(), accountLoader);
     588           1 :     a.file = c.key.filename;
     589           1 :     a.line = c.lineNbr;
     590           1 :     a.message = c.message;
     591           1 :     return a;
     592             :   }
     593             : 
     594             :   /** Get a link to the change; null if the server doesn't know its own address. */
     595             :   @Nullable
     596             :   private String getChangeUrl(Change change) {
     597          10 :     if (change != null) {
     598          10 :       return urlFormatter.get().getChangeViewUrl(change.getProject(), change.getId()).orElse(null);
     599             :     }
     600           0 :     return null;
     601             :   }
     602             : 
     603             :   private void addHashTags(ChangeAttribute changeAttribute, ChangeNotes notes) {
     604           8 :     Set<String> hashtags = notes.load().getHashtags();
     605           8 :     if (!hashtags.isEmpty()) {
     606           0 :       changeAttribute.hashtags = new ArrayList<>(hashtags.size());
     607           0 :       changeAttribute.hashtags.addAll(hashtags);
     608             :     }
     609           8 :   }
     610             : }

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