ChangeNotesParser: Extract class for parsing patch set fields
A small subset of fields on PatchSet are mutable, meaning they can be encountered during parsing before the initial PatchSet data is populated. Previously, ChangeNotesParser handled this case by recording a PatchSet with a sentinel invalid RevId. This was a little ugly, and prevents us from either enforcing that RevId has the correct format or migrating entirely to ObjectId. Instead, define a separate class encapsulating just the pending mutable fields. When encountering the initial patch set definition commit, copy the mutable fields into the PatchSet. Change-Id: Ia86390ff604c3d5ab1e0d2f7f53e1ce2b0570ac9
This commit is contained in:
		@@ -101,10 +101,6 @@ import org.eclipse.jgit.util.RawParseUtils;
 | 
			
		||||
class ChangeNotesParser {
 | 
			
		||||
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
			
		||||
 | 
			
		||||
  // Sentinel RevId indicating a mutable field on a patch set was parsed, but
 | 
			
		||||
  // the parser does not yet know its commit SHA-1.
 | 
			
		||||
  private static final RevId PARTIAL_PATCH_SET = new RevId("INVALID PARTIAL PATCH SET");
 | 
			
		||||
 | 
			
		||||
  @AutoValue
 | 
			
		||||
  abstract static class ApprovalKey {
 | 
			
		||||
    abstract PatchSet.Id psId();
 | 
			
		||||
@@ -118,6 +114,18 @@ class ChangeNotesParser {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Subset of field on patch sets that are mutable after patch set creation, and therefore may be
 | 
			
		||||
   * parsed before the rest of the patch set is available.
 | 
			
		||||
   *
 | 
			
		||||
   * <p>In the future, if {@code PatchSet} becomes an immutable type with a builder, this class can
 | 
			
		||||
   * be replaced with the builder. For now, keep it as simple as possible.
 | 
			
		||||
   */
 | 
			
		||||
  static class PendingPatchSetFields {
 | 
			
		||||
    List<String> groups = Collections.emptyList();
 | 
			
		||||
    String description;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Private final members initialized in the constructor.
 | 
			
		||||
  private final ChangeNoteJson changeNoteJson;
 | 
			
		||||
  private final LegacyChangeNoteRead legacyChangeNoteRead;
 | 
			
		||||
@@ -136,6 +144,7 @@ class ChangeNotesParser {
 | 
			
		||||
  private final List<SubmitRecord> submitRecords;
 | 
			
		||||
  private final ListMultimap<ObjectId, Comment> comments;
 | 
			
		||||
  private final Map<PatchSet.Id, PatchSet> patchSets;
 | 
			
		||||
  private final Map<PatchSet.Id, PendingPatchSetFields> pendingPatchSets;
 | 
			
		||||
  private final Set<PatchSet.Id> deletedPatchSets;
 | 
			
		||||
  private final Map<PatchSet.Id, PatchSetState> patchSetStates;
 | 
			
		||||
  private final List<PatchSet.Id> currentPatchSets;
 | 
			
		||||
@@ -193,6 +202,7 @@ class ChangeNotesParser {
 | 
			
		||||
    allChangeMessages = new ArrayList<>();
 | 
			
		||||
    comments = MultimapBuilder.hashKeys().arrayListValues().build();
 | 
			
		||||
    patchSets = new HashMap<>();
 | 
			
		||||
    pendingPatchSets = new HashMap<>();
 | 
			
		||||
    deletedPatchSets = new HashSet<>();
 | 
			
		||||
    patchSetStates = new HashMap<>();
 | 
			
		||||
    currentPatchSets = new ArrayList<>();
 | 
			
		||||
@@ -364,11 +374,14 @@ class ChangeNotesParser {
 | 
			
		||||
      submissionId = parseSubmissionId(commit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Parse mutable patch set fields first so they can be recorded in the PendingPatchSetFields.
 | 
			
		||||
    parseDescription(psId, commit);
 | 
			
		||||
    parseGroups(psId, commit);
 | 
			
		||||
 | 
			
		||||
    ObjectId currRev = parseRevision(commit);
 | 
			
		||||
    if (currRev != null) {
 | 
			
		||||
      parsePatchSet(psId, currRev, accountId, ts);
 | 
			
		||||
    }
 | 
			
		||||
    parseGroups(psId, commit);
 | 
			
		||||
    parseCurrentPatchSet(psId, commit);
 | 
			
		||||
 | 
			
		||||
    if (submitRecords.isEmpty()) {
 | 
			
		||||
@@ -412,8 +425,6 @@ class ChangeNotesParser {
 | 
			
		||||
    if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
 | 
			
		||||
      lastUpdatedOn = ts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parseDescription(psId, commit);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private String parseSubmissionId(ChangeNotesCommit commit) throws ConfigInvalidException {
 | 
			
		||||
@@ -486,14 +497,9 @@ class ChangeNotesParser {
 | 
			
		||||
    if (accountId == null) {
 | 
			
		||||
      throw parseException("patch set %s requires an identified user as uploader", psId.get());
 | 
			
		||||
    }
 | 
			
		||||
    PatchSet ps = patchSets.get(psId);
 | 
			
		||||
    if (ps == null) {
 | 
			
		||||
      ps = new PatchSet(psId);
 | 
			
		||||
      patchSets.put(psId, ps);
 | 
			
		||||
    } else if (!ps.getRevision().equals(PARTIAL_PATCH_SET)) {
 | 
			
		||||
    if (patchSets.containsKey(psId)) {
 | 
			
		||||
      if (deletedPatchSets.contains(psId)) {
 | 
			
		||||
        // Do not update PS details as PS was deleted and this meta data is of
 | 
			
		||||
        // no relevance
 | 
			
		||||
        // Do not update PS details as PS was deleted and this meta data is of no relevance.
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      throw new ConfigInvalidException(
 | 
			
		||||
@@ -501,9 +507,16 @@ class ChangeNotesParser {
 | 
			
		||||
              "Multiple revisions parsed for patch set %s: %s and %s",
 | 
			
		||||
              psId.get(), patchSets.get(psId).getRevision(), rev.name()));
 | 
			
		||||
    }
 | 
			
		||||
    PatchSet ps = new PatchSet(psId);
 | 
			
		||||
    patchSets.put(psId, ps);
 | 
			
		||||
    ps.setRevision(new RevId(rev.name()));
 | 
			
		||||
    ps.setUploader(accountId);
 | 
			
		||||
    ps.setCreatedOn(ts);
 | 
			
		||||
    PendingPatchSetFields pending = pendingPatchSets.remove(psId);
 | 
			
		||||
    if (pending != null) {
 | 
			
		||||
      ps.setGroups(pending.groups);
 | 
			
		||||
      ps.setDescription(pending.description);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void parseGroups(PatchSet.Id psId, ChangeNotesCommit commit)
 | 
			
		||||
@@ -512,15 +525,14 @@ class ChangeNotesParser {
 | 
			
		||||
    if (groupsStr == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    PatchSet ps = patchSets.get(psId);
 | 
			
		||||
    if (ps == null) {
 | 
			
		||||
      ps = new PatchSet(psId);
 | 
			
		||||
      ps.setRevision(PARTIAL_PATCH_SET);
 | 
			
		||||
      patchSets.put(psId, ps);
 | 
			
		||||
    } else if (!ps.getGroups().isEmpty()) {
 | 
			
		||||
      return;
 | 
			
		||||
    if (patchSets.containsKey(psId)) {
 | 
			
		||||
      throw patchSetFieldBeforeDefinition(psId, FOOTER_GROUPS);
 | 
			
		||||
    }
 | 
			
		||||
    PendingPatchSetFields pending =
 | 
			
		||||
        pendingPatchSets.computeIfAbsent(psId, p -> new PendingPatchSetFields());
 | 
			
		||||
    if (pending.groups.isEmpty()) {
 | 
			
		||||
      pending.groups = PatchSet.splitGroups(groupsStr);
 | 
			
		||||
    }
 | 
			
		||||
    ps.setGroups(PatchSet.splitGroups(groupsStr));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void parseCurrentPatchSet(PatchSet.Id psId, ChangeNotesCommit commit)
 | 
			
		||||
@@ -661,16 +673,17 @@ class ChangeNotesParser {
 | 
			
		||||
    List<String> descLines = commit.getFooterLineValues(FOOTER_PATCH_SET_DESCRIPTION);
 | 
			
		||||
    if (descLines.isEmpty()) {
 | 
			
		||||
      return;
 | 
			
		||||
    } else if (descLines.size() == 1) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (patchSets.containsKey(psId)) {
 | 
			
		||||
      throw patchSetFieldBeforeDefinition(psId, FOOTER_PATCH_SET_DESCRIPTION);
 | 
			
		||||
    }
 | 
			
		||||
    if (descLines.size() == 1) {
 | 
			
		||||
      String desc = descLines.get(0).trim();
 | 
			
		||||
      PatchSet ps = patchSets.get(psId);
 | 
			
		||||
      if (ps == null) {
 | 
			
		||||
        ps = new PatchSet(psId);
 | 
			
		||||
        ps.setRevision(PARTIAL_PATCH_SET);
 | 
			
		||||
        patchSets.put(psId, ps);
 | 
			
		||||
      }
 | 
			
		||||
      if (ps.getDescription() == null) {
 | 
			
		||||
        ps.setDescription(desc);
 | 
			
		||||
      PendingPatchSetFields pending =
 | 
			
		||||
          pendingPatchSets.computeIfAbsent(psId, p -> new PendingPatchSetFields());
 | 
			
		||||
      if (pending.description == null) {
 | 
			
		||||
        pending.description = desc;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      throw expectedOneFooter(FOOTER_PATCH_SET_DESCRIPTION, descLines);
 | 
			
		||||
@@ -1003,13 +1016,7 @@ class ChangeNotesParser {
 | 
			
		||||
 | 
			
		||||
  private void updatePatchSetStates() {
 | 
			
		||||
    Set<PatchSet.Id> missing = new TreeSet<>(comparing(PatchSet.Id::get));
 | 
			
		||||
    for (Iterator<PatchSet> it = patchSets.values().iterator(); it.hasNext(); ) {
 | 
			
		||||
      PatchSet ps = it.next();
 | 
			
		||||
      if (ps.getRevision().equals(PARTIAL_PATCH_SET)) {
 | 
			
		||||
        missing.add(ps.getId());
 | 
			
		||||
        it.remove();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    missing.addAll(pendingPatchSets.keySet());
 | 
			
		||||
    for (Map.Entry<PatchSet.Id, PatchSetState> e : patchSetStates.entrySet()) {
 | 
			
		||||
      switch (e.getValue()) {
 | 
			
		||||
        case PUBLISHED:
 | 
			
		||||
@@ -1089,6 +1096,13 @@ class ChangeNotesParser {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private ConfigInvalidException patchSetFieldBeforeDefinition(
 | 
			
		||||
      PatchSet.Id psId, FooterKey footerPatchSetDescription) {
 | 
			
		||||
    return parseException(
 | 
			
		||||
        "%s field found for patch set %s before it was defined",
 | 
			
		||||
        footerPatchSetDescription.getName(), psId.get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private ConfigInvalidException parseException(String fmt, Object... args) {
 | 
			
		||||
    return ChangeNotes.parseException(id, fmt, args);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user