Combine ChangeNoteUtil and CommentsInNotesUtil
Now that we have to actually inject both of these into ChangeNotes, it was getting a little unwieldy. There was practically nothing in ChangeNoteUtil itself anyway. Merge CommentsInNotesUtil into ChangeNoteUtil so we only have one thing to inject. Change-Id: I41ef88dd2911b9d007f8b3a4cf7d3d7159fa25e6
This commit is contained in:
		@@ -44,7 +44,7 @@ public abstract class AbstractChangeUpdate {
 | 
				
			|||||||
  protected final GitRepositoryManager repoManager;
 | 
					  protected final GitRepositoryManager repoManager;
 | 
				
			||||||
  protected final ChangeControl ctl;
 | 
					  protected final ChangeControl ctl;
 | 
				
			||||||
  protected final String anonymousCowardName;
 | 
					  protected final String anonymousCowardName;
 | 
				
			||||||
  protected final ChangeNoteUtil changeNoteUtil;
 | 
					  protected final ChangeNoteUtil noteUtil;
 | 
				
			||||||
  protected final Date when;
 | 
					  protected final Date when;
 | 
				
			||||||
  private final PersonIdent serverIdent;
 | 
					  private final PersonIdent serverIdent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,14 +56,14 @@ public abstract class AbstractChangeUpdate {
 | 
				
			|||||||
      ChangeControl ctl,
 | 
					      ChangeControl ctl,
 | 
				
			||||||
      PersonIdent serverIdent,
 | 
					      PersonIdent serverIdent,
 | 
				
			||||||
      String anonymousCowardName,
 | 
					      String anonymousCowardName,
 | 
				
			||||||
      ChangeNoteUtil changeNoteUtil,
 | 
					      ChangeNoteUtil noteUtil,
 | 
				
			||||||
      Date when) {
 | 
					      Date when) {
 | 
				
			||||||
    this.migration = migration;
 | 
					    this.migration = migration;
 | 
				
			||||||
    this.repoManager = repoManager;
 | 
					    this.repoManager = repoManager;
 | 
				
			||||||
    this.ctl = ctl;
 | 
					    this.ctl = ctl;
 | 
				
			||||||
    this.serverIdent = serverIdent;
 | 
					    this.serverIdent = serverIdent;
 | 
				
			||||||
    this.anonymousCowardName = anonymousCowardName;
 | 
					    this.anonymousCowardName = anonymousCowardName;
 | 
				
			||||||
    this.changeNoteUtil = changeNoteUtil;
 | 
					    this.noteUtil = noteUtil;
 | 
				
			||||||
    this.when = when;
 | 
					    this.when = when;
 | 
				
			||||||
    checkArgument(
 | 
					    checkArgument(
 | 
				
			||||||
        (ctl.getUser() instanceof IdentifiedUser)
 | 
					        (ctl.getUser() instanceof IdentifiedUser)
 | 
				
			||||||
@@ -99,7 +99,7 @@ public abstract class AbstractChangeUpdate {
 | 
				
			|||||||
  private PersonIdent newAuthorIdent() {
 | 
					  private PersonIdent newAuthorIdent() {
 | 
				
			||||||
    CurrentUser u = getUser();
 | 
					    CurrentUser u = getUser();
 | 
				
			||||||
    if (u instanceof IdentifiedUser) {
 | 
					    if (u instanceof IdentifiedUser) {
 | 
				
			||||||
      return changeNoteUtil.newIdent(u.asIdentifiedUser().getAccount(), when,
 | 
					      return noteUtil.newIdent(u.asIdentifiedUser().getAccount(), when,
 | 
				
			||||||
          serverIdent, anonymousCowardName);
 | 
					          serverIdent, anonymousCowardName);
 | 
				
			||||||
    } else if (u instanceof InternalUser) {
 | 
					    } else if (u instanceof InternalUser) {
 | 
				
			||||||
      return serverIdent;
 | 
					      return serverIdent;
 | 
				
			||||||
@@ -108,8 +108,7 @@ public abstract class AbstractChangeUpdate {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected PersonIdent newIdent(Account author, Date when) {
 | 
					  protected PersonIdent newIdent(Account author, Date when) {
 | 
				
			||||||
    return changeNoteUtil.newIdent(author, when, serverIdent,
 | 
					    return noteUtil.newIdent(author, when, serverIdent, anonymousCowardName);
 | 
				
			||||||
        anonymousCowardName);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Whether no updates have been done. */
 | 
					  /** Whether no updates have been done. */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -78,7 +78,6 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private final AllUsersName draftsProject;
 | 
					  private final AllUsersName draftsProject;
 | 
				
			||||||
  private final Account.Id accountId;
 | 
					  private final Account.Id accountId;
 | 
				
			||||||
  private final CommentsInNotesUtil commentsUtil;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO: can go back to a list?
 | 
					  // TODO: can go back to a list?
 | 
				
			||||||
  private Map<Key, PatchLineComment> put;
 | 
					  private Map<Key, PatchLineComment> put;
 | 
				
			||||||
@@ -91,14 +90,12 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
      GitRepositoryManager repoManager,
 | 
					      GitRepositoryManager repoManager,
 | 
				
			||||||
      NotesMigration migration,
 | 
					      NotesMigration migration,
 | 
				
			||||||
      AllUsersName allUsers,
 | 
					      AllUsersName allUsers,
 | 
				
			||||||
      ChangeNoteUtil changeNoteUtil,
 | 
					      ChangeNoteUtil noteUtil,
 | 
				
			||||||
      CommentsInNotesUtil commentsUtil,
 | 
					 | 
				
			||||||
      @Assisted ChangeControl ctl,
 | 
					      @Assisted ChangeControl ctl,
 | 
				
			||||||
      @Assisted Date when) {
 | 
					      @Assisted Date when) {
 | 
				
			||||||
    super(migration, repoManager, ctl, serverIdent, anonymousCowardName,
 | 
					    super(migration, repoManager, ctl, serverIdent, anonymousCowardName,
 | 
				
			||||||
        changeNoteUtil, when);
 | 
					        noteUtil, when);
 | 
				
			||||||
    this.draftsProject = allUsers;
 | 
					    this.draftsProject = allUsers;
 | 
				
			||||||
    this.commentsUtil = commentsUtil;
 | 
					 | 
				
			||||||
    checkState(ctl.getUser().isIdentifiedUser(),
 | 
					    checkState(ctl.getUser().isIdentifiedUser(),
 | 
				
			||||||
        "Current user must be identified");
 | 
					        "Current user must be identified");
 | 
				
			||||||
    IdentifiedUser user = ctl.getUser().asIdentifiedUser();
 | 
					    IdentifiedUser user = ctl.getUser().asIdentifiedUser();
 | 
				
			||||||
@@ -152,7 +149,7 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
    for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
 | 
					    for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
 | 
				
			||||||
      updatedRevs.add(e.getKey());
 | 
					      updatedRevs.add(e.getKey());
 | 
				
			||||||
      ObjectId id = ObjectId.fromString(e.getKey().get());
 | 
					      ObjectId id = ObjectId.fromString(e.getKey().get());
 | 
				
			||||||
      byte[] data = e.getValue().build(commentsUtil);
 | 
					      byte[] data = e.getValue().build(noteUtil);
 | 
				
			||||||
      if (data.length == 0) {
 | 
					      if (data.length == 0) {
 | 
				
			||||||
        rnm.noteMap.remove(id);
 | 
					        rnm.noteMap.remove(id);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@@ -197,7 +194,7 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
    // Even though reading from changes might not be enabled, we need to
 | 
					    // Even though reading from changes might not be enabled, we need to
 | 
				
			||||||
    // parse any existing revision notes so we can merge them.
 | 
					    // parse any existing revision notes so we can merge them.
 | 
				
			||||||
    return RevisionNoteMap.parse(
 | 
					    return RevisionNoteMap.parse(
 | 
				
			||||||
        commentsUtil, ctl.getId(), rw.getObjectReader(), noteMap, true);
 | 
					        noteUtil, ctl.getId(), rw.getObjectReader(), noteMap, true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,21 +14,49 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package com.google.gerrit.server.notedb;
 | 
					package com.google.gerrit.server.notedb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static com.google.common.base.Preconditions.checkArgument;
 | 
				
			||||||
import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
 | 
					import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
 | 
				
			||||||
 | 
					import static java.nio.charset.StandardCharsets.UTF_8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.google.common.annotations.VisibleForTesting;
 | 
					import com.google.common.annotations.VisibleForTesting;
 | 
				
			||||||
 | 
					import com.google.common.collect.ImmutableList;
 | 
				
			||||||
 | 
					import com.google.common.collect.Lists;
 | 
				
			||||||
import com.google.common.primitives.Ints;
 | 
					import com.google.common.primitives.Ints;
 | 
				
			||||||
import com.google.gerrit.reviewdb.client.Account;
 | 
					import com.google.gerrit.reviewdb.client.Account;
 | 
				
			||||||
import com.google.gerrit.reviewdb.client.Change;
 | 
					import com.google.gerrit.reviewdb.client.Change;
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.client.CommentRange;
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.client.Patch;
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.client.PatchLineComment;
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.client.PatchSet;
 | 
				
			||||||
import com.google.gerrit.reviewdb.client.RefNames;
 | 
					import com.google.gerrit.reviewdb.client.RefNames;
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.client.RevId;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.GerritPersonIdent;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.PatchLineCommentsUtil;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.account.AccountCache;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.config.AnonymousCowardName;
 | 
				
			||||||
import com.google.gerrit.server.config.GerritServerId;
 | 
					import com.google.gerrit.server.config.GerritServerId;
 | 
				
			||||||
import com.google.inject.Inject;
 | 
					import com.google.inject.Inject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
					import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
				
			||||||
import org.eclipse.jgit.lib.PersonIdent;
 | 
					import org.eclipse.jgit.lib.PersonIdent;
 | 
				
			||||||
import org.eclipse.jgit.revwalk.FooterKey;
 | 
					import org.eclipse.jgit.revwalk.FooterKey;
 | 
				
			||||||
 | 
					import org.eclipse.jgit.util.GitDateFormatter;
 | 
				
			||||||
 | 
					import org.eclipse.jgit.util.GitDateFormatter.Format;
 | 
				
			||||||
 | 
					import org.eclipse.jgit.util.GitDateParser;
 | 
				
			||||||
 | 
					import org.eclipse.jgit.util.MutableInteger;
 | 
				
			||||||
 | 
					import org.eclipse.jgit.util.QuotedString;
 | 
				
			||||||
 | 
					import org.eclipse.jgit.util.RawParseUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.ByteArrayOutputStream;
 | 
				
			||||||
 | 
					import java.io.OutputStream;
 | 
				
			||||||
 | 
					import java.io.OutputStreamWriter;
 | 
				
			||||||
 | 
					import java.io.PrintWriter;
 | 
				
			||||||
 | 
					import java.sql.Timestamp;
 | 
				
			||||||
 | 
					import java.text.ParseException;
 | 
				
			||||||
import java.util.Date;
 | 
					import java.util.Date;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Locale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class ChangeNoteUtil {
 | 
					public class ChangeNoteUtil {
 | 
				
			||||||
  static final FooterKey FOOTER_BRANCH = new FooterKey("Branch");
 | 
					  static final FooterKey FOOTER_BRANCH = new FooterKey("Branch");
 | 
				
			||||||
@@ -45,6 +73,16 @@ public class ChangeNoteUtil {
 | 
				
			|||||||
      new FooterKey("Submitted-with");
 | 
					      new FooterKey("Submitted-with");
 | 
				
			||||||
  static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
 | 
					  static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static final String AUTHOR = "Author";
 | 
				
			||||||
 | 
					  private static final String BASE_PATCH_SET = "Base-for-patch-set";
 | 
				
			||||||
 | 
					  private static final String COMMENT_RANGE = "Comment-range";
 | 
				
			||||||
 | 
					  private static final String FILE = "File";
 | 
				
			||||||
 | 
					  private static final String LENGTH = "Bytes";
 | 
				
			||||||
 | 
					  private static final String PARENT = "Parent";
 | 
				
			||||||
 | 
					  private static final String PATCH_SET = "Patch-set";
 | 
				
			||||||
 | 
					  private static final String REVISION = "Revision";
 | 
				
			||||||
 | 
					  private static final String UUID = "UUID";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public static String changeRefName(Change.Id id) {
 | 
					  public static String changeRefName(Change.Id id) {
 | 
				
			||||||
    StringBuilder r = new StringBuilder();
 | 
					    StringBuilder r = new StringBuilder();
 | 
				
			||||||
    r.append(RefNames.REFS_CHANGES);
 | 
					    r.append(RefNames.REFS_CHANGES);
 | 
				
			||||||
@@ -60,10 +98,26 @@ public class ChangeNoteUtil {
 | 
				
			|||||||
    return r.toString();
 | 
					    return r.toString();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public static String formatTime(PersonIdent ident, Timestamp t) {
 | 
				
			||||||
 | 
					    GitDateFormatter dateFormatter = new GitDateFormatter(Format.DEFAULT);
 | 
				
			||||||
 | 
					    // TODO(dborowitz): Use a ThreadLocal or use Joda.
 | 
				
			||||||
 | 
					    PersonIdent newIdent = new PersonIdent(ident, t);
 | 
				
			||||||
 | 
					    return dateFormatter.formatDate(newIdent);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private final AccountCache accountCache;
 | 
				
			||||||
 | 
					  private final PersonIdent serverIdent;
 | 
				
			||||||
 | 
					  private final String anonymousCowardName;
 | 
				
			||||||
  private final String serverId;
 | 
					  private final String serverId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  ChangeNoteUtil(@GerritServerId String serverId) {
 | 
					  public ChangeNoteUtil(AccountCache accountCache,
 | 
				
			||||||
 | 
					      @GerritPersonIdent PersonIdent serverIdent,
 | 
				
			||||||
 | 
					      @AnonymousCowardName String anonymousCowardName,
 | 
				
			||||||
 | 
					      @GerritServerId String serverId) {
 | 
				
			||||||
 | 
					    this.accountCache = accountCache;
 | 
				
			||||||
 | 
					    this.serverIdent = serverIdent;
 | 
				
			||||||
 | 
					    this.anonymousCowardName = anonymousCowardName;
 | 
				
			||||||
    this.serverId = serverId;
 | 
					    this.serverId = serverId;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -92,4 +146,361 @@ public class ChangeNoteUtil {
 | 
				
			|||||||
    throw parseException(changeId, "invalid identity, expected <id>@%s: %s",
 | 
					    throw parseException(changeId, "invalid identity, expected <id>@%s: %s",
 | 
				
			||||||
        serverId, email);
 | 
					        serverId, email);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public List<PatchLineComment> parseNote(byte[] note, MutableInteger p,
 | 
				
			||||||
 | 
					      Change.Id changeId, Status status) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    if (p.value >= note.length) {
 | 
				
			||||||
 | 
					      return ImmutableList.of();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    List<PatchLineComment> result = Lists.newArrayList();
 | 
				
			||||||
 | 
					    int sizeOfNote = note.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    boolean isForBase =
 | 
				
			||||||
 | 
					        (RawParseUtils.match(note, p.value, PATCH_SET.getBytes(UTF_8))) < 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PatchSet.Id psId = parsePsId(note, p, changeId, isForBase ? BASE_PATCH_SET : PATCH_SET);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    RevId revId =
 | 
				
			||||||
 | 
					        new RevId(parseStringField(note, p, changeId, REVISION));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PatchLineComment c = null;
 | 
				
			||||||
 | 
					    while (p.value < sizeOfNote) {
 | 
				
			||||||
 | 
					      String previousFileName = c == null ?
 | 
				
			||||||
 | 
					          null : c.getKey().getParentKey().getFileName();
 | 
				
			||||||
 | 
					      c = parseComment(note, p, previousFileName, psId, revId,
 | 
				
			||||||
 | 
					          isForBase, status);
 | 
				
			||||||
 | 
					      result.add(c);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private PatchLineComment parseComment(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      String currentFileName, PatchSet.Id psId, RevId revId, boolean isForBase,
 | 
				
			||||||
 | 
					      Status status) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    Change.Id changeId = psId.getParentKey();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check if there is a new file.
 | 
				
			||||||
 | 
					    boolean newFile =
 | 
				
			||||||
 | 
					        (RawParseUtils.match(note, curr.value, FILE.getBytes(UTF_8))) != -1;
 | 
				
			||||||
 | 
					    if (newFile) {
 | 
				
			||||||
 | 
					      // If so, parse the new file name.
 | 
				
			||||||
 | 
					      currentFileName = parseFilename(note, curr, changeId);
 | 
				
			||||||
 | 
					    } else if (currentFileName == null) {
 | 
				
			||||||
 | 
					      throw parseException(changeId, "could not parse %s", FILE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CommentRange range = parseCommentRange(note, curr);
 | 
				
			||||||
 | 
					    if (range == null) {
 | 
				
			||||||
 | 
					      throw parseException(changeId, "could not parse %s", COMMENT_RANGE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Timestamp commentTime = parseTimestamp(note, curr, changeId);
 | 
				
			||||||
 | 
					    Account.Id aId = parseAuthor(note, curr, changeId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    boolean hasParent =
 | 
				
			||||||
 | 
					        (RawParseUtils.match(note, curr.value, PARENT.getBytes(UTF_8))) != -1;
 | 
				
			||||||
 | 
					    String parentUUID = null;
 | 
				
			||||||
 | 
					    if (hasParent) {
 | 
				
			||||||
 | 
					      parentUUID = parseStringField(note, curr, changeId, PARENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    String uuid = parseStringField(note, curr, changeId, UUID);
 | 
				
			||||||
 | 
					    int commentLength = parseCommentLength(note, curr, changeId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    String message = RawParseUtils.decode(
 | 
				
			||||||
 | 
					        UTF_8, note, curr.value, curr.value + commentLength);
 | 
				
			||||||
 | 
					    checkResult(message, "message contents", changeId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PatchLineComment plc = new PatchLineComment(
 | 
				
			||||||
 | 
					        new PatchLineComment.Key(new Patch.Key(psId, currentFileName), uuid),
 | 
				
			||||||
 | 
					        range.getEndLine(), aId, parentUUID, commentTime);
 | 
				
			||||||
 | 
					    plc.setMessage(message);
 | 
				
			||||||
 | 
					    plc.setSide((short) (isForBase ? 0 : 1));
 | 
				
			||||||
 | 
					    if (range.getStartCharacter() != -1) {
 | 
				
			||||||
 | 
					      plc.setRange(range);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    plc.setRevId(revId);
 | 
				
			||||||
 | 
					    plc.setStatus(status);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    curr.value = RawParseUtils.nextLF(note, curr.value + commentLength);
 | 
				
			||||||
 | 
					    curr.value = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    return plc;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static String parseStringField(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      Change.Id changeId, String fieldName) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    checkHeaderLineFormat(note, curr, fieldName, changeId);
 | 
				
			||||||
 | 
					    int startOfField = RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
 | 
				
			||||||
 | 
					    curr.value = endOfLine;
 | 
				
			||||||
 | 
					    return RawParseUtils.decode(UTF_8, note, startOfField, endOfLine - 1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @return a comment range. If the comment range line in the note only has
 | 
				
			||||||
 | 
					   *    one number, we return a CommentRange with that one number as the end
 | 
				
			||||||
 | 
					   *    line and the other fields as -1. If the comment range line in the note
 | 
				
			||||||
 | 
					   *    contains a whole comment range, then we return a CommentRange with all
 | 
				
			||||||
 | 
					   *    fields set. If the line is not correctly formatted, return null.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private static CommentRange parseCommentRange(byte[] note, MutableInteger ptr) {
 | 
				
			||||||
 | 
					    CommentRange range = new CommentRange(-1, -1, -1, -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int startLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
				
			||||||
 | 
					    if (startLine == 0) {
 | 
				
			||||||
 | 
					      range.setEndLine(0);
 | 
				
			||||||
 | 
					      ptr.value += 1;
 | 
				
			||||||
 | 
					      return range;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (note[ptr.value] == '\n') {
 | 
				
			||||||
 | 
					      range.setEndLine(startLine);
 | 
				
			||||||
 | 
					      ptr.value += 1;
 | 
				
			||||||
 | 
					      return range;
 | 
				
			||||||
 | 
					    } else if (note[ptr.value] == ':') {
 | 
				
			||||||
 | 
					      range.setStartLine(startLine);
 | 
				
			||||||
 | 
					      ptr.value += 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int startChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
				
			||||||
 | 
					    if (note[ptr.value] == '-') {
 | 
				
			||||||
 | 
					      range.setStartCharacter(startChar);
 | 
				
			||||||
 | 
					      ptr.value += 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int endLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
				
			||||||
 | 
					    if (endLine == 0) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (note[ptr.value] == ':') {
 | 
				
			||||||
 | 
					      range.setEndLine(endLine);
 | 
				
			||||||
 | 
					      ptr.value += 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int endChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
				
			||||||
 | 
					    if (endChar == 0) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (note[ptr.value] == '\n') {
 | 
				
			||||||
 | 
					      range.setEndCharacter(endChar);
 | 
				
			||||||
 | 
					      ptr.value += 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return range;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static PatchSet.Id parsePsId(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      Change.Id changeId, String fieldName) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    checkHeaderLineFormat(note, curr, fieldName, changeId);
 | 
				
			||||||
 | 
					    int startOfPsId =
 | 
				
			||||||
 | 
					        RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
 | 
				
			||||||
 | 
					    MutableInteger i = new MutableInteger();
 | 
				
			||||||
 | 
					    int patchSetId =
 | 
				
			||||||
 | 
					        RawParseUtils.parseBase10(note, startOfPsId, i);
 | 
				
			||||||
 | 
					    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    if (i.value != endOfLine - 1) {
 | 
				
			||||||
 | 
					      throw parseException(changeId, "could not parse %s", fieldName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    checkResult(patchSetId, "patchset id", changeId);
 | 
				
			||||||
 | 
					    curr.value = endOfLine;
 | 
				
			||||||
 | 
					    return new PatchSet.Id(changeId, patchSetId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static String parseFilename(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      Change.Id changeId) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    checkHeaderLineFormat(note, curr, FILE, changeId);
 | 
				
			||||||
 | 
					    int startOfFileName =
 | 
				
			||||||
 | 
					        RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
 | 
				
			||||||
 | 
					    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    curr.value = endOfLine;
 | 
				
			||||||
 | 
					    curr.value = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    return QuotedString.GIT_PATH.dequote(
 | 
				
			||||||
 | 
					        RawParseUtils.decode(UTF_8, note, startOfFileName, endOfLine - 1));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static Timestamp parseTimestamp(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      Change.Id changeId) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    Timestamp commentTime;
 | 
				
			||||||
 | 
					    String dateString =
 | 
				
			||||||
 | 
					        RawParseUtils.decode(UTF_8, note, curr.value, endOfLine - 1);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      commentTime = new Timestamp(
 | 
				
			||||||
 | 
					          GitDateParser.parse(dateString, null, Locale.US).getTime());
 | 
				
			||||||
 | 
					    } catch (ParseException e) {
 | 
				
			||||||
 | 
					      throw new ConfigInvalidException("could not parse comment timestamp", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    curr.value = endOfLine;
 | 
				
			||||||
 | 
					    return checkResult(commentTime, "comment timestamp", changeId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private Account.Id parseAuthor(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      Change.Id changeId) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    checkHeaderLineFormat(note, curr, AUTHOR, changeId);
 | 
				
			||||||
 | 
					    int startOfAccountId =
 | 
				
			||||||
 | 
					        RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
 | 
				
			||||||
 | 
					    PersonIdent ident =
 | 
				
			||||||
 | 
					        RawParseUtils.parsePersonIdent(note, startOfAccountId);
 | 
				
			||||||
 | 
					    Account.Id aId = parseIdent(ident, changeId);
 | 
				
			||||||
 | 
					    curr.value = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    return checkResult(aId, "comment author", changeId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static int parseCommentLength(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      Change.Id changeId) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    checkHeaderLineFormat(note, curr, LENGTH, changeId);
 | 
				
			||||||
 | 
					    int startOfLength =
 | 
				
			||||||
 | 
					        RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
 | 
				
			||||||
 | 
					    MutableInteger i = new MutableInteger();
 | 
				
			||||||
 | 
					    int commentLength =
 | 
				
			||||||
 | 
					        RawParseUtils.parseBase10(note, startOfLength, i);
 | 
				
			||||||
 | 
					    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
				
			||||||
 | 
					    if (i.value != endOfLine-1) {
 | 
				
			||||||
 | 
					      throw parseException(changeId, "could not parse %s", PATCH_SET);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    curr.value = endOfLine;
 | 
				
			||||||
 | 
					    return checkResult(commentLength, "comment length", changeId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static <T> T checkResult(T o, String fieldName,
 | 
				
			||||||
 | 
					      Change.Id changeId) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    if (o == null) {
 | 
				
			||||||
 | 
					      throw parseException(changeId, "could not parse %s", fieldName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return o;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static int checkResult(int i, String fieldName, Change.Id changeId)
 | 
				
			||||||
 | 
					      throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    if (i <= 0) {
 | 
				
			||||||
 | 
					      throw parseException(changeId, "could not parse %s", fieldName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return i;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private void appendHeaderField(PrintWriter writer,
 | 
				
			||||||
 | 
					      String field, String value) {
 | 
				
			||||||
 | 
					    writer.print(field);
 | 
				
			||||||
 | 
					    writer.print(": ");
 | 
				
			||||||
 | 
					    writer.print(value);
 | 
				
			||||||
 | 
					    writer.print('\n');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static void checkHeaderLineFormat(byte[] note, MutableInteger curr,
 | 
				
			||||||
 | 
					      String fieldName, Change.Id changeId) throws ConfigInvalidException {
 | 
				
			||||||
 | 
					    boolean correct =
 | 
				
			||||||
 | 
					        RawParseUtils.match(note, curr.value, fieldName.getBytes(UTF_8)) != -1;
 | 
				
			||||||
 | 
					    int p = curr.value + fieldName.length();
 | 
				
			||||||
 | 
					    correct &= (p < note.length && note[p] == ':');
 | 
				
			||||||
 | 
					    p++;
 | 
				
			||||||
 | 
					    correct &= (p < note.length && note[p] == ' ');
 | 
				
			||||||
 | 
					    if (!correct) {
 | 
				
			||||||
 | 
					      throw parseException(changeId, "could not parse %s", fieldName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public byte[] buildNote(List<PatchLineComment> comments) {
 | 
				
			||||||
 | 
					    ByteArrayOutputStream out = new ByteArrayOutputStream();
 | 
				
			||||||
 | 
					    buildNote(comments, out);
 | 
				
			||||||
 | 
					    return out.toByteArray();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Build a note that contains the metadata for and the contents of all of the
 | 
				
			||||||
 | 
					   * comments in the given list of comments.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param comments A list of the comments to be written to the
 | 
				
			||||||
 | 
					   *            output stream. All of the comments in this list must have the
 | 
				
			||||||
 | 
					   *            same side and must share the same patch set ID.
 | 
				
			||||||
 | 
					   * @param out output stream to write to.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  void buildNote(List<PatchLineComment> comments, OutputStream out) {
 | 
				
			||||||
 | 
					    if (comments.isEmpty()) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    OutputStreamWriter streamWriter = new OutputStreamWriter(out, UTF_8);
 | 
				
			||||||
 | 
					    try (PrintWriter writer = new PrintWriter(streamWriter)) {
 | 
				
			||||||
 | 
					      PatchLineComment first = comments.get(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      short side = first.getSide();
 | 
				
			||||||
 | 
					      PatchSet.Id psId = PatchLineCommentsUtil.getCommentPsId(first);
 | 
				
			||||||
 | 
					      appendHeaderField(writer, side == 0
 | 
				
			||||||
 | 
					          ? BASE_PATCH_SET
 | 
				
			||||||
 | 
					          : PATCH_SET,
 | 
				
			||||||
 | 
					          Integer.toString(psId.get()));
 | 
				
			||||||
 | 
					      appendHeaderField(writer, REVISION, first.getRevId().get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      String currentFilename = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (PatchLineComment c : comments) {
 | 
				
			||||||
 | 
					        PatchSet.Id currentPsId = PatchLineCommentsUtil.getCommentPsId(c);
 | 
				
			||||||
 | 
					        checkArgument(psId.equals(currentPsId),
 | 
				
			||||||
 | 
					            "All comments being added must all have the same PatchSet.Id. The"
 | 
				
			||||||
 | 
					            + "comment below does not have the same PatchSet.Id as the others "
 | 
				
			||||||
 | 
					            + "(%s).\n%s", psId.toString(), c.toString());
 | 
				
			||||||
 | 
					        checkArgument(side == c.getSide(),
 | 
				
			||||||
 | 
					            "All comments being added must all have the same side. The"
 | 
				
			||||||
 | 
					            + "comment below does not have the same side as the others "
 | 
				
			||||||
 | 
					            + "(%s).\n%s", side, c.toString());
 | 
				
			||||||
 | 
					        String commentFilename =
 | 
				
			||||||
 | 
					            QuotedString.GIT_PATH.quote(c.getKey().getParentKey().getFileName());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!commentFilename.equals(currentFilename)) {
 | 
				
			||||||
 | 
					          currentFilename = commentFilename;
 | 
				
			||||||
 | 
					          writer.print("File: ");
 | 
				
			||||||
 | 
					          writer.print(commentFilename);
 | 
				
			||||||
 | 
					          writer.print("\n\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // The CommentRange field for a comment is allowed to be null.
 | 
				
			||||||
 | 
					        // If it is indeed null, then in the first line, we simply use the line
 | 
				
			||||||
 | 
					        // number field for a comment instead. If it isn't null, we write the
 | 
				
			||||||
 | 
					        // comment range itself.
 | 
				
			||||||
 | 
					        CommentRange range = c.getRange();
 | 
				
			||||||
 | 
					        if (range != null) {
 | 
				
			||||||
 | 
					          writer.print(range.getStartLine());
 | 
				
			||||||
 | 
					          writer.print(':');
 | 
				
			||||||
 | 
					          writer.print(range.getStartCharacter());
 | 
				
			||||||
 | 
					          writer.print('-');
 | 
				
			||||||
 | 
					          writer.print(range.getEndLine());
 | 
				
			||||||
 | 
					          writer.print(':');
 | 
				
			||||||
 | 
					          writer.print(range.getEndCharacter());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          writer.print(c.getLine());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        writer.print("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        writer.print(formatTime(serverIdent, c.getWrittenOn()));
 | 
				
			||||||
 | 
					        writer.print("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PersonIdent ident = newIdent(
 | 
				
			||||||
 | 
					            accountCache.get(c.getAuthor()).getAccount(),
 | 
				
			||||||
 | 
					            c.getWrittenOn(), serverIdent, anonymousCowardName);
 | 
				
			||||||
 | 
					        String nameString = ident.getName() + " <" + ident.getEmailAddress()
 | 
				
			||||||
 | 
					            + ">";
 | 
				
			||||||
 | 
					        appendHeaderField(writer, AUTHOR, nameString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String parent = c.getParentUuid();
 | 
				
			||||||
 | 
					        if (parent != null) {
 | 
				
			||||||
 | 
					          appendHeaderField(writer, PARENT, parent);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        appendHeaderField(writer, UUID, c.getKey().get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        byte[] messageBytes = c.getMessage().getBytes(UTF_8);
 | 
				
			||||||
 | 
					        appendHeaderField(writer, LENGTH,
 | 
				
			||||||
 | 
					            Integer.toString(messageBytes.length));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        writer.print(c.getMessage());
 | 
				
			||||||
 | 
					        writer.print("\n\n");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,8 +119,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
    private final AllUsersName allUsers;
 | 
					    private final AllUsersName allUsers;
 | 
				
			||||||
    private final Provider<InternalChangeQuery> queryProvider;
 | 
					    private final Provider<InternalChangeQuery> queryProvider;
 | 
				
			||||||
    private final ProjectCache projectCache;
 | 
					    private final ProjectCache projectCache;
 | 
				
			||||||
    private final ChangeNoteUtil changeNoteUtil;
 | 
					    private final ChangeNoteUtil noteUtil;
 | 
				
			||||||
    private final CommentsInNotesUtil commentsUtil;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @VisibleForTesting
 | 
					    @VisibleForTesting
 | 
				
			||||||
    @Inject
 | 
					    @Inject
 | 
				
			||||||
@@ -129,15 +128,13 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
        AllUsersName allUsers,
 | 
					        AllUsersName allUsers,
 | 
				
			||||||
        Provider<InternalChangeQuery> queryProvider,
 | 
					        Provider<InternalChangeQuery> queryProvider,
 | 
				
			||||||
        ProjectCache projectCache,
 | 
					        ProjectCache projectCache,
 | 
				
			||||||
        ChangeNoteUtil changeNoteUtil,
 | 
					        ChangeNoteUtil noteUtil) {
 | 
				
			||||||
        CommentsInNotesUtil commentsUtil) {
 | 
					 | 
				
			||||||
      this.repoManager = repoManager;
 | 
					      this.repoManager = repoManager;
 | 
				
			||||||
      this.migration = migration;
 | 
					      this.migration = migration;
 | 
				
			||||||
      this.allUsers = allUsers;
 | 
					      this.allUsers = allUsers;
 | 
				
			||||||
      this.queryProvider = queryProvider;
 | 
					      this.queryProvider = queryProvider;
 | 
				
			||||||
      this.projectCache = projectCache;
 | 
					      this.projectCache = projectCache;
 | 
				
			||||||
      this.changeNoteUtil = changeNoteUtil;
 | 
					      this.noteUtil = noteUtil;
 | 
				
			||||||
      this.commentsUtil = commentsUtil;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ChangeNotes createChecked(ReviewDb db, Change c)
 | 
					    public ChangeNotes createChecked(ReviewDb db, Change c)
 | 
				
			||||||
@@ -182,8 +179,8 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
          project, changeId, change.getProject());
 | 
					          project, changeId, change.getProject());
 | 
				
			||||||
      // TODO: Throw NoSuchChangeException when the change is not found in the
 | 
					      // TODO: Throw NoSuchChangeException when the change is not found in the
 | 
				
			||||||
      // database
 | 
					      // database
 | 
				
			||||||
      return new ChangeNotes(repoManager, migration, allUsers, changeNoteUtil,
 | 
					      return new ChangeNotes(repoManager, migration, allUsers, noteUtil,
 | 
				
			||||||
          commentsUtil, project, change).load();
 | 
					          project, change).load();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -195,13 +192,13 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
     * @return change notes
 | 
					     * @return change notes
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public ChangeNotes createFromIndexedChange(Change change) {
 | 
					    public ChangeNotes createFromIndexedChange(Change change) {
 | 
				
			||||||
      return new ChangeNotes(repoManager, migration, allUsers, changeNoteUtil,
 | 
					      return new ChangeNotes(repoManager, migration, allUsers, noteUtil,
 | 
				
			||||||
          commentsUtil, change.getProject(), change);
 | 
					          change.getProject(), change);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ChangeNotes createForNew(Change change) throws OrmException {
 | 
					    public ChangeNotes createForNew(Change change) throws OrmException {
 | 
				
			||||||
      return new ChangeNotes(repoManager, migration, allUsers, changeNoteUtil,
 | 
					      return new ChangeNotes(repoManager, migration, allUsers, noteUtil,
 | 
				
			||||||
          commentsUtil, change.getProject(), change).load();
 | 
					          change.getProject(), change).load();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO(dborowitz): Remove when deleting index schemas <27.
 | 
					    // TODO(dborowitz): Remove when deleting index schemas <27.
 | 
				
			||||||
@@ -210,8 +207,8 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
    checkState(!migration.readChanges(), "do not call"
 | 
					    checkState(!migration.readChanges(), "do not call"
 | 
				
			||||||
        + " createFromIdOnlyWhenNotedbDisabled when notedb is enabled");
 | 
					        + " createFromIdOnlyWhenNotedbDisabled when notedb is enabled");
 | 
				
			||||||
      Change change = unwrap(db).changes().get(changeId);
 | 
					      Change change = unwrap(db).changes().get(changeId);
 | 
				
			||||||
      return new ChangeNotes(repoManager, migration, allUsers, changeNoteUtil,
 | 
					      return new ChangeNotes(repoManager, migration, allUsers, noteUtil,
 | 
				
			||||||
          commentsUtil, change.getProject(), change).load();
 | 
					          change.getProject(), change).load();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO(ekempin): Remove when database backend is deleted
 | 
					    // TODO(ekempin): Remove when database backend is deleted
 | 
				
			||||||
@@ -223,8 +220,8 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
        throws OrmException {
 | 
					        throws OrmException {
 | 
				
			||||||
      checkState(!migration.readChanges(), "do not call"
 | 
					      checkState(!migration.readChanges(), "do not call"
 | 
				
			||||||
          + " createFromChangeWhenNotedbDisabled when notedb is enabled");
 | 
					          + " createFromChangeWhenNotedbDisabled when notedb is enabled");
 | 
				
			||||||
      return new ChangeNotes(repoManager, migration, allUsers, changeNoteUtil,
 | 
					      return new ChangeNotes(repoManager, migration, allUsers, noteUtil,
 | 
				
			||||||
          commentsUtil, change.getProject(), change).load();
 | 
					          change.getProject(), change).load();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public CheckedFuture<ChangeNotes, OrmException> createAsync(
 | 
					    public CheckedFuture<ChangeNotes, OrmException> createAsync(
 | 
				
			||||||
@@ -244,7 +241,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
                              + " but actual project is %s",
 | 
					                              + " but actual project is %s",
 | 
				
			||||||
                          project, changeId, change.getProject());
 | 
					                          project, changeId, change.getProject());
 | 
				
			||||||
                      return new ChangeNotes(repoManager, migration, allUsers,
 | 
					                      return new ChangeNotes(repoManager, migration, allUsers,
 | 
				
			||||||
                          changeNoteUtil, commentsUtil, project, change).load();
 | 
					                          noteUtil, project, change).load();
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  });
 | 
					                  });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -383,8 +380,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private final ChangeNoteUtil changeNoteUtil;
 | 
					  private final ChangeNoteUtil noteUtil;
 | 
				
			||||||
  private final CommentsInNotesUtil commentsUtil;
 | 
					 | 
				
			||||||
  private final Project.NameKey project;
 | 
					  private final Project.NameKey project;
 | 
				
			||||||
  private final Change change;
 | 
					  private final Change change;
 | 
				
			||||||
  private ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets;
 | 
					  private ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets;
 | 
				
			||||||
@@ -406,13 +402,11 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @VisibleForTesting
 | 
					  @VisibleForTesting
 | 
				
			||||||
  public ChangeNotes(GitRepositoryManager repoManager, NotesMigration migration,
 | 
					  public ChangeNotes(GitRepositoryManager repoManager, NotesMigration migration,
 | 
				
			||||||
      AllUsersName allUsers, ChangeNoteUtil changeNoteUtil,
 | 
					      AllUsersName allUsers, ChangeNoteUtil noteUtil,
 | 
				
			||||||
      CommentsInNotesUtil commentsUtil, Project.NameKey project,
 | 
					      Project.NameKey project, Change change) {
 | 
				
			||||||
      Change change) {
 | 
					 | 
				
			||||||
    super(repoManager, migration, change != null ? change.getId() : null);
 | 
					    super(repoManager, migration, change != null ? change.getId() : null);
 | 
				
			||||||
    this.allUsers = allUsers;
 | 
					    this.allUsers = allUsers;
 | 
				
			||||||
    this.changeNoteUtil = changeNoteUtil;
 | 
					    this.noteUtil = noteUtil;
 | 
				
			||||||
    this.commentsUtil = commentsUtil;
 | 
					 | 
				
			||||||
    this.project = project;
 | 
					    this.project = project;
 | 
				
			||||||
    this.change = change != null ? new Change(change) : null;
 | 
					    this.change = change != null ? new Change(change) : null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -510,7 +504,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
    if (draftCommentNotes == null ||
 | 
					    if (draftCommentNotes == null ||
 | 
				
			||||||
        !author.equals(draftCommentNotes.getAuthor())) {
 | 
					        !author.equals(draftCommentNotes.getAuthor())) {
 | 
				
			||||||
      draftCommentNotes = new DraftCommentNotes(repoManager, migration,
 | 
					      draftCommentNotes = new DraftCommentNotes(repoManager, migration,
 | 
				
			||||||
          allUsers, commentsUtil, getChangeId(), author);
 | 
					          allUsers, noteUtil, getChangeId(), author);
 | 
				
			||||||
      draftCommentNotes.load();
 | 
					      draftCommentNotes.load();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -556,9 +550,8 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    try (RevWalk walk = new RevWalk(reader);
 | 
					    try (RevWalk walk = new RevWalk(reader);
 | 
				
			||||||
        ChangeNotesParser parser = new ChangeNotesParser(project,
 | 
					        ChangeNotesParser parser = new ChangeNotesParser(
 | 
				
			||||||
            change.getId(), rev, walk, repoManager, changeNoteUtil,
 | 
					            project, change.getId(), rev, walk, repoManager, noteUtil)) {
 | 
				
			||||||
            commentsUtil)) {
 | 
					 | 
				
			||||||
      parser.parseAll();
 | 
					      parser.parseAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (parser.status != null) {
 | 
					      if (parser.status != null) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -114,8 +114,7 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
  PatchSet.Id currentPatchSetId;
 | 
					  PatchSet.Id currentPatchSetId;
 | 
				
			||||||
  RevisionNoteMap revisionNoteMap;
 | 
					  RevisionNoteMap revisionNoteMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private final ChangeNoteUtil changeNoteUtil;
 | 
					  private final ChangeNoteUtil noteUtil;
 | 
				
			||||||
  private final CommentsInNotesUtil commentsUtil;
 | 
					 | 
				
			||||||
  private final Change.Id id;
 | 
					  private final Change.Id id;
 | 
				
			||||||
  private final ObjectId tip;
 | 
					  private final ObjectId tip;
 | 
				
			||||||
  private final RevWalk walk;
 | 
					  private final RevWalk walk;
 | 
				
			||||||
@@ -127,14 +126,13 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  ChangeNotesParser(Project.NameKey project, Change.Id changeId, ObjectId tip,
 | 
					  ChangeNotesParser(Project.NameKey project, Change.Id changeId, ObjectId tip,
 | 
				
			||||||
      RevWalk walk, GitRepositoryManager repoManager,
 | 
					      RevWalk walk, GitRepositoryManager repoManager,
 | 
				
			||||||
      ChangeNoteUtil changeNoteUtil, CommentsInNotesUtil commentsUtil)
 | 
					      ChangeNoteUtil noteUtil)
 | 
				
			||||||
      throws RepositoryNotFoundException, IOException {
 | 
					      throws RepositoryNotFoundException, IOException {
 | 
				
			||||||
    this.id = changeId;
 | 
					    this.id = changeId;
 | 
				
			||||||
    this.tip = tip;
 | 
					    this.tip = tip;
 | 
				
			||||||
    this.walk = walk;
 | 
					    this.walk = walk;
 | 
				
			||||||
    this.repo = repoManager.openMetadataRepository(project);
 | 
					    this.repo = repoManager.openMetadataRepository(project);
 | 
				
			||||||
    this.changeNoteUtil = changeNoteUtil;
 | 
					    this.noteUtil = noteUtil;
 | 
				
			||||||
    this.commentsUtil = commentsUtil;
 | 
					 | 
				
			||||||
    approvals = Maps.newHashMap();
 | 
					    approvals = Maps.newHashMap();
 | 
				
			||||||
    reviewers = Maps.newLinkedHashMap();
 | 
					    reviewers = Maps.newLinkedHashMap();
 | 
				
			||||||
    allPastReviewers = Lists.newArrayList();
 | 
					    allPastReviewers = Lists.newArrayList();
 | 
				
			||||||
@@ -505,7 +503,7 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
    ObjectReader reader = walk.getObjectReader();
 | 
					    ObjectReader reader = walk.getObjectReader();
 | 
				
			||||||
    RevCommit tipCommit = walk.parseCommit(tip);
 | 
					    RevCommit tipCommit = walk.parseCommit(tip);
 | 
				
			||||||
    revisionNoteMap = RevisionNoteMap.parse(
 | 
					    revisionNoteMap = RevisionNoteMap.parse(
 | 
				
			||||||
        commentsUtil, id, reader, NoteMap.read(reader, tipCommit), false);
 | 
					        noteUtil, id, reader, NoteMap.read(reader, tipCommit), false);
 | 
				
			||||||
    Map<RevId, RevisionNote> rns = revisionNoteMap.revisionNotes;
 | 
					    Map<RevId, RevisionNote> rns = revisionNoteMap.revisionNotes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (Map.Entry<RevId, RevisionNote> e : rns.entrySet()) {
 | 
					    for (Map.Entry<RevId, RevisionNote> e : rns.entrySet()) {
 | 
				
			||||||
@@ -544,7 +542,7 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
      labelVoteStr = line.substring(0, s);
 | 
					      labelVoteStr = line.substring(0, s);
 | 
				
			||||||
      PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
 | 
					      PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
 | 
				
			||||||
      checkFooter(ident != null, FOOTER_LABEL, line);
 | 
					      checkFooter(ident != null, FOOTER_LABEL, line);
 | 
				
			||||||
      accountId = changeNoteUtil.parseIdent(ident, id);
 | 
					      accountId = noteUtil.parseIdent(ident, id);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      labelVoteStr = line;
 | 
					      labelVoteStr = line;
 | 
				
			||||||
      accountId = committerId;
 | 
					      accountId = committerId;
 | 
				
			||||||
@@ -582,7 +580,7 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
      label = line.substring(1, s);
 | 
					      label = line.substring(1, s);
 | 
				
			||||||
      PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
 | 
					      PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(s + 1));
 | 
				
			||||||
      checkFooter(ident != null, FOOTER_LABEL, line);
 | 
					      checkFooter(ident != null, FOOTER_LABEL, line);
 | 
				
			||||||
      accountId = changeNoteUtil.parseIdent(ident, id);
 | 
					      accountId = noteUtil.parseIdent(ident, id);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      label = line.substring(1);
 | 
					      label = line.substring(1);
 | 
				
			||||||
      accountId = committerId;
 | 
					      accountId = committerId;
 | 
				
			||||||
@@ -665,7 +663,7 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
          PersonIdent ident =
 | 
					          PersonIdent ident =
 | 
				
			||||||
              RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
 | 
					              RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
 | 
				
			||||||
          checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
 | 
					          checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
 | 
				
			||||||
          label.appliedBy = changeNoteUtil.parseIdent(ident, id);
 | 
					          label.appliedBy = noteUtil.parseIdent(ident, id);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          label.label = line.substring(c + 2);
 | 
					          label.label = line.substring(c + 2);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -683,7 +681,7 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
        && a.getEmailAddress().equals(c.getEmailAddress())) {
 | 
					        && a.getEmailAddress().equals(c.getEmailAddress())) {
 | 
				
			||||||
      return null;
 | 
					      return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return changeNoteUtil.parseIdent(commit.getAuthorIdent(), id);
 | 
					    return noteUtil.parseIdent(commit.getAuthorIdent(), id);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void parseReviewer(ReviewerStateInternal state, String line)
 | 
					  private void parseReviewer(ReviewerStateInternal state, String line)
 | 
				
			||||||
@@ -692,7 +690,7 @@ class ChangeNotesParser implements AutoCloseable {
 | 
				
			|||||||
    if (ident == null) {
 | 
					    if (ident == null) {
 | 
				
			||||||
      throw invalidFooter(state.getFooterKey(), line);
 | 
					      throw invalidFooter(state.getFooterKey(), line);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    Account.Id accountId = changeNoteUtil.parseIdent(ident, id);
 | 
					    Account.Id accountId = noteUtil.parseIdent(ident, id);
 | 
				
			||||||
    if (!reviewers.containsKey(accountId)) {
 | 
					    if (!reviewers.containsKey(accountId)) {
 | 
				
			||||||
      reviewers.put(accountId, state);
 | 
					      reviewers.put(accountId, state);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -100,7 +100,6 @@ public class ChangeUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private final AccountCache accountCache;
 | 
					  private final AccountCache accountCache;
 | 
				
			||||||
  private final CommentsInNotesUtil commentsUtil;
 | 
					 | 
				
			||||||
  private final ChangeDraftUpdate.Factory draftUpdateFactory;
 | 
					  private final ChangeDraftUpdate.Factory draftUpdateFactory;
 | 
				
			||||||
  private final NoteDbUpdateManager.Factory updateManagerFactory;
 | 
					  private final NoteDbUpdateManager.Factory updateManagerFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -136,11 +135,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
      ChangeDraftUpdate.Factory draftUpdateFactory,
 | 
					      ChangeDraftUpdate.Factory draftUpdateFactory,
 | 
				
			||||||
      ProjectCache projectCache,
 | 
					      ProjectCache projectCache,
 | 
				
			||||||
      @Assisted ChangeControl ctl,
 | 
					      @Assisted ChangeControl ctl,
 | 
				
			||||||
      CommentsInNotesUtil commentsUtil,
 | 
					      ChangeNoteUtil noteUtil) {
 | 
				
			||||||
      ChangeNoteUtil changeNoteUtil) {
 | 
					 | 
				
			||||||
    this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
 | 
					    this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
 | 
				
			||||||
        updateManagerFactory, draftUpdateFactory,
 | 
					        updateManagerFactory, draftUpdateFactory,
 | 
				
			||||||
        projectCache, ctl, serverIdent.getWhen(), commentsUtil, changeNoteUtil);
 | 
					        projectCache, ctl, serverIdent.getWhen(), noteUtil);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @AssistedInject
 | 
					  @AssistedInject
 | 
				
			||||||
@@ -155,13 +153,12 @@ public class ChangeUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
      ProjectCache projectCache,
 | 
					      ProjectCache projectCache,
 | 
				
			||||||
      @Assisted ChangeControl ctl,
 | 
					      @Assisted ChangeControl ctl,
 | 
				
			||||||
      @Assisted Date when,
 | 
					      @Assisted Date when,
 | 
				
			||||||
      CommentsInNotesUtil commentsUtil,
 | 
					      ChangeNoteUtil noteUtil) {
 | 
				
			||||||
      ChangeNoteUtil changeNoteUtil) {
 | 
					 | 
				
			||||||
    this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
 | 
					    this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
 | 
				
			||||||
        updateManagerFactory, draftUpdateFactory, ctl,
 | 
					        updateManagerFactory, draftUpdateFactory, ctl,
 | 
				
			||||||
        when,
 | 
					        when,
 | 
				
			||||||
        projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
 | 
					        projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
 | 
				
			||||||
        commentsUtil, changeNoteUtil);
 | 
					        noteUtil);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static Project.NameKey getProjectName(ChangeControl ctl) {
 | 
					  private static Project.NameKey getProjectName(ChangeControl ctl) {
 | 
				
			||||||
@@ -180,12 +177,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
      @Assisted ChangeControl ctl,
 | 
					      @Assisted ChangeControl ctl,
 | 
				
			||||||
      @Assisted Date when,
 | 
					      @Assisted Date when,
 | 
				
			||||||
      @Assisted Comparator<String> labelNameComparator,
 | 
					      @Assisted Comparator<String> labelNameComparator,
 | 
				
			||||||
      CommentsInNotesUtil commentsUtil,
 | 
					      ChangeNoteUtil noteUtil) {
 | 
				
			||||||
      ChangeNoteUtil changeNoteUtil) {
 | 
					 | 
				
			||||||
    super(migration, repoManager, ctl, serverIdent,
 | 
					    super(migration, repoManager, ctl, serverIdent,
 | 
				
			||||||
        anonymousCowardName, changeNoteUtil, when);
 | 
					        anonymousCowardName, noteUtil, when);
 | 
				
			||||||
    this.accountCache = accountCache;
 | 
					    this.accountCache = accountCache;
 | 
				
			||||||
    this.commentsUtil = commentsUtil;
 | 
					 | 
				
			||||||
    this.draftUpdateFactory = draftUpdateFactory;
 | 
					    this.draftUpdateFactory = draftUpdateFactory;
 | 
				
			||||||
    this.updateManagerFactory = updateManagerFactory;
 | 
					    this.updateManagerFactory = updateManagerFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -378,7 +373,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
 | 
					    for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
 | 
				
			||||||
      ObjectId data = inserter.insert(
 | 
					      ObjectId data = inserter.insert(
 | 
				
			||||||
          OBJ_BLOB, e.getValue().build(commentsUtil));
 | 
					          OBJ_BLOB, e.getValue().build(noteUtil));
 | 
				
			||||||
      rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
 | 
					      rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -404,7 +399,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
 | 
				
			|||||||
    // Even though reading from changes might not be enabled, we need to
 | 
					    // Even though reading from changes might not be enabled, we need to
 | 
				
			||||||
    // parse any existing revision notes so we can merge them.
 | 
					    // parse any existing revision notes so we can merge them.
 | 
				
			||||||
    return RevisionNoteMap.parse(
 | 
					    return RevisionNoteMap.parse(
 | 
				
			||||||
        commentsUtil, ctl.getId(), rw.getObjectReader(), noteMap, false);
 | 
					        noteUtil, ctl.getId(), rw.getObjectReader(), noteMap, false);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void checkComments(Map<RevId, RevisionNote> existingNotes,
 | 
					  private void checkComments(Map<RevId, RevisionNote> existingNotes,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,451 +0,0 @@
 | 
				
			|||||||
// Copyright (C) 2014 The Android Open Source Project
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
					 | 
				
			||||||
// you may not use this file except in compliance with the License.
 | 
					 | 
				
			||||||
// You may obtain a copy of the License at
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Unless required by applicable law or agreed to in writing, software
 | 
					 | 
				
			||||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
					 | 
				
			||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					 | 
				
			||||||
// See the License for the specific language governing permissions and
 | 
					 | 
				
			||||||
// limitations under the License.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package com.google.gerrit.server.notedb;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static com.google.common.base.Preconditions.checkArgument;
 | 
					 | 
				
			||||||
import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
 | 
					 | 
				
			||||||
import static java.nio.charset.StandardCharsets.UTF_8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.google.common.collect.ImmutableList;
 | 
					 | 
				
			||||||
import com.google.common.collect.Lists;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.Account;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.Change;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.CommentRange;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.Patch;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
					 | 
				
			||||||
import com.google.gerrit.reviewdb.client.RevId;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.GerritPersonIdent;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.PatchLineCommentsUtil;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.account.AccountCache;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.config.AnonymousCowardName;
 | 
					 | 
				
			||||||
import com.google.inject.Inject;
 | 
					 | 
				
			||||||
import com.google.inject.Singleton;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.lib.PersonIdent;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.util.GitDateFormatter;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.util.GitDateFormatter.Format;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.util.GitDateParser;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.util.MutableInteger;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.util.QuotedString;
 | 
					 | 
				
			||||||
import org.eclipse.jgit.util.RawParseUtils;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.ByteArrayOutputStream;
 | 
					 | 
				
			||||||
import java.io.OutputStream;
 | 
					 | 
				
			||||||
import java.io.OutputStreamWriter;
 | 
					 | 
				
			||||||
import java.io.PrintWriter;
 | 
					 | 
				
			||||||
import java.sql.Timestamp;
 | 
					 | 
				
			||||||
import java.text.ParseException;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Locale;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Utility functions to parse PatchLineComments out of a note byte array and
 | 
					 | 
				
			||||||
 * store a list of PatchLineComments in the form of a note (in a byte array).
 | 
					 | 
				
			||||||
 **/
 | 
					 | 
				
			||||||
@Singleton
 | 
					 | 
				
			||||||
public class CommentsInNotesUtil {
 | 
					 | 
				
			||||||
  private static final String AUTHOR = "Author";
 | 
					 | 
				
			||||||
  private static final String BASE_PATCH_SET = "Base-for-patch-set";
 | 
					 | 
				
			||||||
  private static final String COMMENT_RANGE = "Comment-range";
 | 
					 | 
				
			||||||
  private static final String FILE = "File";
 | 
					 | 
				
			||||||
  private static final String LENGTH = "Bytes";
 | 
					 | 
				
			||||||
  private static final String PARENT = "Parent";
 | 
					 | 
				
			||||||
  private static final String PATCH_SET = "Patch-set";
 | 
					 | 
				
			||||||
  private static final String REVISION = "Revision";
 | 
					 | 
				
			||||||
  private static final String UUID = "UUID";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public static String formatTime(PersonIdent ident, Timestamp t) {
 | 
					 | 
				
			||||||
    GitDateFormatter dateFormatter = new GitDateFormatter(Format.DEFAULT);
 | 
					 | 
				
			||||||
    // TODO(dborowitz): Use a ThreadLocal or use Joda.
 | 
					 | 
				
			||||||
    PersonIdent newIdent = new PersonIdent(ident, t);
 | 
					 | 
				
			||||||
    return dateFormatter.formatDate(newIdent);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private final AccountCache accountCache;
 | 
					 | 
				
			||||||
  private final PersonIdent serverIdent;
 | 
					 | 
				
			||||||
  private final String anonymousCowardName;
 | 
					 | 
				
			||||||
  private final ChangeNoteUtil changeNoteUtil;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Inject
 | 
					 | 
				
			||||||
  public CommentsInNotesUtil(AccountCache accountCache,
 | 
					 | 
				
			||||||
      @GerritPersonIdent PersonIdent serverIdent,
 | 
					 | 
				
			||||||
      @AnonymousCowardName String anonymousCowardName,
 | 
					 | 
				
			||||||
      ChangeNoteUtil changeNoteUtil) {
 | 
					 | 
				
			||||||
    this.accountCache = accountCache;
 | 
					 | 
				
			||||||
    this.serverIdent = serverIdent;
 | 
					 | 
				
			||||||
    this.anonymousCowardName = anonymousCowardName;
 | 
					 | 
				
			||||||
    this.changeNoteUtil = changeNoteUtil;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public List<PatchLineComment> parseNote(byte[] note, MutableInteger p,
 | 
					 | 
				
			||||||
      Change.Id changeId, Status status) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    if (p.value >= note.length) {
 | 
					 | 
				
			||||||
      return ImmutableList.of();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    List<PatchLineComment> result = Lists.newArrayList();
 | 
					 | 
				
			||||||
    int sizeOfNote = note.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    boolean isForBase =
 | 
					 | 
				
			||||||
        (RawParseUtils.match(note, p.value, PATCH_SET.getBytes(UTF_8))) < 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PatchSet.Id psId = parsePsId(note, p, changeId, isForBase ? BASE_PATCH_SET : PATCH_SET);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    RevId revId =
 | 
					 | 
				
			||||||
        new RevId(parseStringField(note, p, changeId, REVISION));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PatchLineComment c = null;
 | 
					 | 
				
			||||||
    while (p.value < sizeOfNote) {
 | 
					 | 
				
			||||||
      String previousFileName = c == null ?
 | 
					 | 
				
			||||||
          null : c.getKey().getParentKey().getFileName();
 | 
					 | 
				
			||||||
      c = parseComment(note, p, previousFileName, psId, revId,
 | 
					 | 
				
			||||||
          isForBase, status);
 | 
					 | 
				
			||||||
      result.add(c);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return result;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private PatchLineComment parseComment(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      String currentFileName, PatchSet.Id psId, RevId revId, boolean isForBase,
 | 
					 | 
				
			||||||
      Status status) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    Change.Id changeId = psId.getParentKey();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Check if there is a new file.
 | 
					 | 
				
			||||||
    boolean newFile =
 | 
					 | 
				
			||||||
        (RawParseUtils.match(note, curr.value, FILE.getBytes(UTF_8))) != -1;
 | 
					 | 
				
			||||||
    if (newFile) {
 | 
					 | 
				
			||||||
      // If so, parse the new file name.
 | 
					 | 
				
			||||||
      currentFileName = parseFilename(note, curr, changeId);
 | 
					 | 
				
			||||||
    } else if (currentFileName == null) {
 | 
					 | 
				
			||||||
      throw parseException(changeId, "could not parse %s", FILE);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    CommentRange range = parseCommentRange(note, curr);
 | 
					 | 
				
			||||||
    if (range == null) {
 | 
					 | 
				
			||||||
      throw parseException(changeId, "could not parse %s", COMMENT_RANGE);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Timestamp commentTime = parseTimestamp(note, curr, changeId);
 | 
					 | 
				
			||||||
    Account.Id aId = parseAuthor(note, curr, changeId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    boolean hasParent =
 | 
					 | 
				
			||||||
        (RawParseUtils.match(note, curr.value, PARENT.getBytes(UTF_8))) != -1;
 | 
					 | 
				
			||||||
    String parentUUID = null;
 | 
					 | 
				
			||||||
    if (hasParent) {
 | 
					 | 
				
			||||||
      parentUUID = parseStringField(note, curr, changeId, PARENT);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    String uuid = parseStringField(note, curr, changeId, UUID);
 | 
					 | 
				
			||||||
    int commentLength = parseCommentLength(note, curr, changeId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    String message = RawParseUtils.decode(
 | 
					 | 
				
			||||||
        UTF_8, note, curr.value, curr.value + commentLength);
 | 
					 | 
				
			||||||
    checkResult(message, "message contents", changeId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PatchLineComment plc = new PatchLineComment(
 | 
					 | 
				
			||||||
        new PatchLineComment.Key(new Patch.Key(psId, currentFileName), uuid),
 | 
					 | 
				
			||||||
        range.getEndLine(), aId, parentUUID, commentTime);
 | 
					 | 
				
			||||||
    plc.setMessage(message);
 | 
					 | 
				
			||||||
    plc.setSide((short) (isForBase ? 0 : 1));
 | 
					 | 
				
			||||||
    if (range.getStartCharacter() != -1) {
 | 
					 | 
				
			||||||
      plc.setRange(range);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    plc.setRevId(revId);
 | 
					 | 
				
			||||||
    plc.setStatus(status);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    curr.value = RawParseUtils.nextLF(note, curr.value + commentLength);
 | 
					 | 
				
			||||||
    curr.value = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    return plc;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static String parseStringField(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      Change.Id changeId, String fieldName) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    checkHeaderLineFormat(note, curr, fieldName, changeId);
 | 
					 | 
				
			||||||
    int startOfField = RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
 | 
					 | 
				
			||||||
    curr.value = endOfLine;
 | 
					 | 
				
			||||||
    return RawParseUtils.decode(UTF_8, note, startOfField, endOfLine - 1);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @return a comment range. If the comment range line in the note only has
 | 
					 | 
				
			||||||
   *    one number, we return a CommentRange with that one number as the end
 | 
					 | 
				
			||||||
   *    line and the other fields as -1. If the comment range line in the note
 | 
					 | 
				
			||||||
   *    contains a whole comment range, then we return a CommentRange with all
 | 
					 | 
				
			||||||
   *    fields set. If the line is not correctly formatted, return null.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  private static CommentRange parseCommentRange(byte[] note, MutableInteger ptr) {
 | 
					 | 
				
			||||||
    CommentRange range = new CommentRange(-1, -1, -1, -1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int startLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
					 | 
				
			||||||
    if (startLine == 0) {
 | 
					 | 
				
			||||||
      range.setEndLine(0);
 | 
					 | 
				
			||||||
      ptr.value += 1;
 | 
					 | 
				
			||||||
      return range;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (note[ptr.value] == '\n') {
 | 
					 | 
				
			||||||
      range.setEndLine(startLine);
 | 
					 | 
				
			||||||
      ptr.value += 1;
 | 
					 | 
				
			||||||
      return range;
 | 
					 | 
				
			||||||
    } else if (note[ptr.value] == ':') {
 | 
					 | 
				
			||||||
      range.setStartLine(startLine);
 | 
					 | 
				
			||||||
      ptr.value += 1;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int startChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
					 | 
				
			||||||
    if (note[ptr.value] == '-') {
 | 
					 | 
				
			||||||
      range.setStartCharacter(startChar);
 | 
					 | 
				
			||||||
      ptr.value += 1;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int endLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
					 | 
				
			||||||
    if (endLine == 0) {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (note[ptr.value] == ':') {
 | 
					 | 
				
			||||||
      range.setEndLine(endLine);
 | 
					 | 
				
			||||||
      ptr.value += 1;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int endChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
 | 
					 | 
				
			||||||
    if (endChar == 0) {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (note[ptr.value] == '\n') {
 | 
					 | 
				
			||||||
      range.setEndCharacter(endChar);
 | 
					 | 
				
			||||||
      ptr.value += 1;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return range;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static PatchSet.Id parsePsId(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      Change.Id changeId, String fieldName) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    checkHeaderLineFormat(note, curr, fieldName, changeId);
 | 
					 | 
				
			||||||
    int startOfPsId =
 | 
					 | 
				
			||||||
        RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
 | 
					 | 
				
			||||||
    MutableInteger i = new MutableInteger();
 | 
					 | 
				
			||||||
    int patchSetId =
 | 
					 | 
				
			||||||
        RawParseUtils.parseBase10(note, startOfPsId, i);
 | 
					 | 
				
			||||||
    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    if (i.value != endOfLine - 1) {
 | 
					 | 
				
			||||||
      throw parseException(changeId, "could not parse %s", fieldName);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    checkResult(patchSetId, "patchset id", changeId);
 | 
					 | 
				
			||||||
    curr.value = endOfLine;
 | 
					 | 
				
			||||||
    return new PatchSet.Id(changeId, patchSetId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static String parseFilename(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      Change.Id changeId) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    checkHeaderLineFormat(note, curr, FILE, changeId);
 | 
					 | 
				
			||||||
    int startOfFileName =
 | 
					 | 
				
			||||||
        RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
 | 
					 | 
				
			||||||
    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    curr.value = endOfLine;
 | 
					 | 
				
			||||||
    curr.value = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    return QuotedString.GIT_PATH.dequote(
 | 
					 | 
				
			||||||
        RawParseUtils.decode(UTF_8, note, startOfFileName, endOfLine - 1));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static Timestamp parseTimestamp(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      Change.Id changeId) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    Timestamp commentTime;
 | 
					 | 
				
			||||||
    String dateString =
 | 
					 | 
				
			||||||
        RawParseUtils.decode(UTF_8, note, curr.value, endOfLine - 1);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      commentTime = new Timestamp(
 | 
					 | 
				
			||||||
          GitDateParser.parse(dateString, null, Locale.US).getTime());
 | 
					 | 
				
			||||||
    } catch (ParseException e) {
 | 
					 | 
				
			||||||
      throw new ConfigInvalidException("could not parse comment timestamp", e);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    curr.value = endOfLine;
 | 
					 | 
				
			||||||
    return checkResult(commentTime, "comment timestamp", changeId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private Account.Id parseAuthor(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      Change.Id changeId) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    checkHeaderLineFormat(note, curr, AUTHOR, changeId);
 | 
					 | 
				
			||||||
    int startOfAccountId =
 | 
					 | 
				
			||||||
        RawParseUtils.endOfFooterLineKey(note, curr.value) + 2;
 | 
					 | 
				
			||||||
    PersonIdent ident =
 | 
					 | 
				
			||||||
        RawParseUtils.parsePersonIdent(note, startOfAccountId);
 | 
					 | 
				
			||||||
    Account.Id aId = changeNoteUtil.parseIdent(ident, changeId);
 | 
					 | 
				
			||||||
    curr.value = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    return checkResult(aId, "comment author", changeId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static int parseCommentLength(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      Change.Id changeId) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    checkHeaderLineFormat(note, curr, LENGTH, changeId);
 | 
					 | 
				
			||||||
    int startOfLength =
 | 
					 | 
				
			||||||
        RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
 | 
					 | 
				
			||||||
    MutableInteger i = new MutableInteger();
 | 
					 | 
				
			||||||
    int commentLength =
 | 
					 | 
				
			||||||
        RawParseUtils.parseBase10(note, startOfLength, i);
 | 
					 | 
				
			||||||
    int endOfLine = RawParseUtils.nextLF(note, curr.value);
 | 
					 | 
				
			||||||
    if (i.value != endOfLine-1) {
 | 
					 | 
				
			||||||
      throw parseException(changeId, "could not parse %s", PATCH_SET);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    curr.value = endOfLine;
 | 
					 | 
				
			||||||
    return checkResult(commentLength, "comment length", changeId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static <T> T checkResult(T o, String fieldName,
 | 
					 | 
				
			||||||
      Change.Id changeId) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    if (o == null) {
 | 
					 | 
				
			||||||
      throw parseException(changeId, "could not parse %s", fieldName);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return o;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static int checkResult(int i, String fieldName, Change.Id changeId)
 | 
					 | 
				
			||||||
      throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    if (i <= 0) {
 | 
					 | 
				
			||||||
      throw parseException(changeId, "could not parse %s", fieldName);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return i;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private void appendHeaderField(PrintWriter writer,
 | 
					 | 
				
			||||||
      String field, String value) {
 | 
					 | 
				
			||||||
    writer.print(field);
 | 
					 | 
				
			||||||
    writer.print(": ");
 | 
					 | 
				
			||||||
    writer.print(value);
 | 
					 | 
				
			||||||
    writer.print('\n');
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static void checkHeaderLineFormat(byte[] note, MutableInteger curr,
 | 
					 | 
				
			||||||
      String fieldName, Change.Id changeId) throws ConfigInvalidException {
 | 
					 | 
				
			||||||
    boolean correct =
 | 
					 | 
				
			||||||
        RawParseUtils.match(note, curr.value, fieldName.getBytes(UTF_8)) != -1;
 | 
					 | 
				
			||||||
    int p = curr.value + fieldName.length();
 | 
					 | 
				
			||||||
    correct &= (p < note.length && note[p] == ':');
 | 
					 | 
				
			||||||
    p++;
 | 
					 | 
				
			||||||
    correct &= (p < note.length && note[p] == ' ');
 | 
					 | 
				
			||||||
    if (!correct) {
 | 
					 | 
				
			||||||
      throw parseException(changeId, "could not parse %s", fieldName);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public byte[] buildNote(List<PatchLineComment> comments) {
 | 
					 | 
				
			||||||
    ByteArrayOutputStream out = new ByteArrayOutputStream();
 | 
					 | 
				
			||||||
    buildNote(comments, out);
 | 
					 | 
				
			||||||
    return out.toByteArray();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Build a note that contains the metadata for and the contents of all of the
 | 
					 | 
				
			||||||
   * comments in the given list of comments.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @param comments A list of the comments to be written to the
 | 
					 | 
				
			||||||
   *            output stream. All of the comments in this list must have the
 | 
					 | 
				
			||||||
   *            same side and must share the same patch set ID.
 | 
					 | 
				
			||||||
   * @param out output stream to write to.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  void buildNote(List<PatchLineComment> comments, OutputStream out) {
 | 
					 | 
				
			||||||
    if (comments.isEmpty()) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    OutputStreamWriter streamWriter = new OutputStreamWriter(out, UTF_8);
 | 
					 | 
				
			||||||
    try (PrintWriter writer = new PrintWriter(streamWriter)) {
 | 
					 | 
				
			||||||
      PatchLineComment first = comments.get(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      short side = first.getSide();
 | 
					 | 
				
			||||||
      PatchSet.Id psId = PatchLineCommentsUtil.getCommentPsId(first);
 | 
					 | 
				
			||||||
      appendHeaderField(writer, side == 0
 | 
					 | 
				
			||||||
          ? BASE_PATCH_SET
 | 
					 | 
				
			||||||
          : PATCH_SET,
 | 
					 | 
				
			||||||
          Integer.toString(psId.get()));
 | 
					 | 
				
			||||||
      appendHeaderField(writer, REVISION, first.getRevId().get());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      String currentFilename = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      for (PatchLineComment c : comments) {
 | 
					 | 
				
			||||||
        PatchSet.Id currentPsId = PatchLineCommentsUtil.getCommentPsId(c);
 | 
					 | 
				
			||||||
        checkArgument(psId.equals(currentPsId),
 | 
					 | 
				
			||||||
            "All comments being added must all have the same PatchSet.Id. The"
 | 
					 | 
				
			||||||
            + "comment below does not have the same PatchSet.Id as the others "
 | 
					 | 
				
			||||||
            + "(%s).\n%s", psId.toString(), c.toString());
 | 
					 | 
				
			||||||
        checkArgument(side == c.getSide(),
 | 
					 | 
				
			||||||
            "All comments being added must all have the same side. The"
 | 
					 | 
				
			||||||
            + "comment below does not have the same side as the others "
 | 
					 | 
				
			||||||
            + "(%s).\n%s", side, c.toString());
 | 
					 | 
				
			||||||
        String commentFilename =
 | 
					 | 
				
			||||||
            QuotedString.GIT_PATH.quote(c.getKey().getParentKey().getFileName());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!commentFilename.equals(currentFilename)) {
 | 
					 | 
				
			||||||
          currentFilename = commentFilename;
 | 
					 | 
				
			||||||
          writer.print("File: ");
 | 
					 | 
				
			||||||
          writer.print(commentFilename);
 | 
					 | 
				
			||||||
          writer.print("\n\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // The CommentRange field for a comment is allowed to be null.
 | 
					 | 
				
			||||||
        // If it is indeed null, then in the first line, we simply use the line
 | 
					 | 
				
			||||||
        // number field for a comment instead. If it isn't null, we write the
 | 
					 | 
				
			||||||
        // comment range itself.
 | 
					 | 
				
			||||||
        CommentRange range = c.getRange();
 | 
					 | 
				
			||||||
        if (range != null) {
 | 
					 | 
				
			||||||
          writer.print(range.getStartLine());
 | 
					 | 
				
			||||||
          writer.print(':');
 | 
					 | 
				
			||||||
          writer.print(range.getStartCharacter());
 | 
					 | 
				
			||||||
          writer.print('-');
 | 
					 | 
				
			||||||
          writer.print(range.getEndLine());
 | 
					 | 
				
			||||||
          writer.print(':');
 | 
					 | 
				
			||||||
          writer.print(range.getEndCharacter());
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          writer.print(c.getLine());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        writer.print("\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        writer.print(formatTime(serverIdent, c.getWrittenOn()));
 | 
					 | 
				
			||||||
        writer.print("\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        PersonIdent ident =
 | 
					 | 
				
			||||||
            changeNoteUtil.newIdent(accountCache.get(c.getAuthor()).getAccount(),
 | 
					 | 
				
			||||||
                c.getWrittenOn(), serverIdent, anonymousCowardName);
 | 
					 | 
				
			||||||
        String nameString = ident.getName() + " <" + ident.getEmailAddress()
 | 
					 | 
				
			||||||
            + ">";
 | 
					 | 
				
			||||||
        appendHeaderField(writer, AUTHOR, nameString);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        String parent = c.getParentUuid();
 | 
					 | 
				
			||||||
        if (parent != null) {
 | 
					 | 
				
			||||||
          appendHeaderField(writer, PARENT, parent);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        appendHeaderField(writer, UUID, c.getKey().get());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        byte[] messageBytes = c.getMessage().getBytes(UTF_8);
 | 
					 | 
				
			||||||
        appendHeaderField(writer, LENGTH,
 | 
					 | 
				
			||||||
            Integer.toString(messageBytes.length));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        writer.print(c.getMessage());
 | 
					 | 
				
			||||||
        writer.print("\n\n");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -48,40 +48,40 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
 | 
				
			|||||||
    private final GitRepositoryManager repoManager;
 | 
					    private final GitRepositoryManager repoManager;
 | 
				
			||||||
    private final NotesMigration migration;
 | 
					    private final NotesMigration migration;
 | 
				
			||||||
    private final AllUsersName draftsProject;
 | 
					    private final AllUsersName draftsProject;
 | 
				
			||||||
    private final CommentsInNotesUtil commentsUtil;
 | 
					    private final ChangeNoteUtil noteUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @VisibleForTesting
 | 
					    @VisibleForTesting
 | 
				
			||||||
    @Inject
 | 
					    @Inject
 | 
				
			||||||
    public Factory(GitRepositoryManager repoManager,
 | 
					    public Factory(GitRepositoryManager repoManager,
 | 
				
			||||||
        NotesMigration migration,
 | 
					        NotesMigration migration,
 | 
				
			||||||
        AllUsersName allUsers,
 | 
					        AllUsersName allUsers,
 | 
				
			||||||
        CommentsInNotesUtil commentsUtil) {
 | 
					        ChangeNoteUtil noteUtil) {
 | 
				
			||||||
      this.repoManager = repoManager;
 | 
					      this.repoManager = repoManager;
 | 
				
			||||||
      this.migration = migration;
 | 
					      this.migration = migration;
 | 
				
			||||||
      this.draftsProject = allUsers;
 | 
					      this.draftsProject = allUsers;
 | 
				
			||||||
      this.commentsUtil = commentsUtil;
 | 
					      this.noteUtil = noteUtil;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public DraftCommentNotes create(Change.Id changeId, Account.Id accountId) {
 | 
					    public DraftCommentNotes create(Change.Id changeId, Account.Id accountId) {
 | 
				
			||||||
      return new DraftCommentNotes(repoManager, migration, draftsProject,
 | 
					      return new DraftCommentNotes(repoManager, migration, draftsProject,
 | 
				
			||||||
          commentsUtil, changeId, accountId);
 | 
					          noteUtil, changeId, accountId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private final AllUsersName draftsProject;
 | 
					  private final AllUsersName draftsProject;
 | 
				
			||||||
  private final CommentsInNotesUtil commentsUtil;
 | 
					  private final ChangeNoteUtil noteUtil;
 | 
				
			||||||
  private final Account.Id author;
 | 
					  private final Account.Id author;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private ImmutableListMultimap<RevId, PatchLineComment> comments;
 | 
					  private ImmutableListMultimap<RevId, PatchLineComment> comments;
 | 
				
			||||||
  private RevisionNoteMap revisionNoteMap;
 | 
					  private RevisionNoteMap revisionNoteMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  DraftCommentNotes(GitRepositoryManager repoManager, NotesMigration migration,
 | 
					  DraftCommentNotes(GitRepositoryManager repoManager, NotesMigration migration,
 | 
				
			||||||
      AllUsersName draftsProject, CommentsInNotesUtil commentsUtil,
 | 
					      AllUsersName draftsProject, ChangeNoteUtil noteUtil, Change.Id changeId,
 | 
				
			||||||
      Change.Id changeId, Account.Id author) {
 | 
					      Account.Id author) {
 | 
				
			||||||
    super(repoManager, migration, changeId);
 | 
					    super(repoManager, migration, changeId);
 | 
				
			||||||
    this.draftsProject = draftsProject;
 | 
					    this.draftsProject = draftsProject;
 | 
				
			||||||
    this.author = author;
 | 
					    this.author = author;
 | 
				
			||||||
    this.commentsUtil = commentsUtil;
 | 
					    this.noteUtil = noteUtil;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  RevisionNoteMap getRevisionNoteMap() {
 | 
					  RevisionNoteMap getRevisionNoteMap() {
 | 
				
			||||||
@@ -122,7 +122,7 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
 | 
				
			|||||||
    try (RevWalk walk = new RevWalk(reader)) {
 | 
					    try (RevWalk walk = new RevWalk(reader)) {
 | 
				
			||||||
      RevCommit tipCommit = walk.parseCommit(rev);
 | 
					      RevCommit tipCommit = walk.parseCommit(rev);
 | 
				
			||||||
      revisionNoteMap = RevisionNoteMap.parse(
 | 
					      revisionNoteMap = RevisionNoteMap.parse(
 | 
				
			||||||
          commentsUtil, getChangeId(), reader, NoteMap.read(reader, tipCommit),
 | 
					          noteUtil, getChangeId(), reader, NoteMap.read(reader, tipCommit),
 | 
				
			||||||
          true);
 | 
					          true);
 | 
				
			||||||
      Multimap<RevId, PatchLineComment> cs = ArrayListMultimap.create();
 | 
					      Multimap<RevId, PatchLineComment> cs = ArrayListMultimap.create();
 | 
				
			||||||
      for (RevisionNote rn : revisionNoteMap.revisionNotes.values()) {
 | 
					      for (RevisionNote rn : revisionNoteMap.revisionNotes.values()) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,7 +63,7 @@ class RevisionNote {
 | 
				
			|||||||
  final ImmutableList<PatchLineComment> comments;
 | 
					  final ImmutableList<PatchLineComment> comments;
 | 
				
			||||||
  final String pushCert;
 | 
					  final String pushCert;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  RevisionNote(CommentsInNotesUtil commentsUtil, Change.Id changeId,
 | 
					  RevisionNote(ChangeNoteUtil noteUtil, Change.Id changeId,
 | 
				
			||||||
      ObjectReader reader, ObjectId noteId, boolean draftsOnly)
 | 
					      ObjectReader reader, ObjectId noteId, boolean draftsOnly)
 | 
				
			||||||
      throws ConfigInvalidException, IOException {
 | 
					      throws ConfigInvalidException, IOException {
 | 
				
			||||||
    byte[] bytes = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
 | 
					    byte[] bytes = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
 | 
				
			||||||
@@ -79,6 +79,6 @@ class RevisionNote {
 | 
				
			|||||||
        ? PatchLineComment.Status.DRAFT
 | 
					        ? PatchLineComment.Status.DRAFT
 | 
				
			||||||
        : PatchLineComment.Status.PUBLISHED;
 | 
					        : PatchLineComment.Status.PUBLISHED;
 | 
				
			||||||
    comments = ImmutableList.copyOf(
 | 
					    comments = ImmutableList.copyOf(
 | 
				
			||||||
        commentsUtil.parseNote(bytes, p, changeId, status));
 | 
					        noteUtil.parseNote(bytes, p, changeId, status));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,7 +91,7 @@ class RevisionNoteBuilder {
 | 
				
			|||||||
    this.pushCert = pushCert;
 | 
					    this.pushCert = pushCert;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  byte[] build(CommentsInNotesUtil commentsUtil) {
 | 
					  byte[] build(ChangeNoteUtil noteUtil) {
 | 
				
			||||||
    ByteArrayOutputStream out = new ByteArrayOutputStream();
 | 
					    ByteArrayOutputStream out = new ByteArrayOutputStream();
 | 
				
			||||||
    if (pushCert != null) {
 | 
					    if (pushCert != null) {
 | 
				
			||||||
      byte[] certBytes = pushCert.getBytes(UTF_8);
 | 
					      byte[] certBytes = pushCert.getBytes(UTF_8);
 | 
				
			||||||
@@ -107,7 +107,7 @@ class RevisionNoteBuilder {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    Collections.sort(all, PLC_ORDER);
 | 
					    Collections.sort(all, PLC_ORDER);
 | 
				
			||||||
    commentsUtil.buildNote(all, out);
 | 
					    noteUtil.buildNote(all, out);
 | 
				
			||||||
    return out.toByteArray();
 | 
					    return out.toByteArray();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,13 +31,13 @@ class RevisionNoteMap {
 | 
				
			|||||||
  final NoteMap noteMap;
 | 
					  final NoteMap noteMap;
 | 
				
			||||||
  final ImmutableMap<RevId, RevisionNote> revisionNotes;
 | 
					  final ImmutableMap<RevId, RevisionNote> revisionNotes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static RevisionNoteMap parse(CommentsInNotesUtil commentsUtil,
 | 
					  static RevisionNoteMap parse(ChangeNoteUtil noteUtil,
 | 
				
			||||||
      Change.Id changeId, ObjectReader reader, NoteMap noteMap,
 | 
					      Change.Id changeId, ObjectReader reader, NoteMap noteMap,
 | 
				
			||||||
      boolean draftsOnly) throws ConfigInvalidException, IOException {
 | 
					      boolean draftsOnly) throws ConfigInvalidException, IOException {
 | 
				
			||||||
    Map<RevId, RevisionNote> result = new HashMap<>();
 | 
					    Map<RevId, RevisionNote> result = new HashMap<>();
 | 
				
			||||||
    for (Note note : noteMap) {
 | 
					    for (Note note : noteMap) {
 | 
				
			||||||
      RevisionNote rn = new RevisionNote(
 | 
					      RevisionNote rn = new RevisionNote(
 | 
				
			||||||
          commentsUtil, changeId, reader, note.getData(), draftsOnly);
 | 
					          noteUtil, changeId, reader, note.getData(), draftsOnly);
 | 
				
			||||||
      result.put(new RevId(note.name()), rn);
 | 
					      result.put(new RevId(note.name()), rn);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return new RevisionNoteMap(noteMap, ImmutableMap.copyOf(result));
 | 
					    return new RevisionNoteMap(noteMap, ImmutableMap.copyOf(result));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -108,10 +108,7 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
 | 
				
			|||||||
  protected AllUsersName allUsers;
 | 
					  protected AllUsersName allUsers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  protected ChangeNoteUtil changeNoteUtil;
 | 
					  protected ChangeNoteUtil noteUtil;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Inject
 | 
					 | 
				
			||||||
  protected CommentsInNotesUtil commentsUtil;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private Injector injector;
 | 
					  private Injector injector;
 | 
				
			||||||
  private String systemTimeZone;
 | 
					  private String systemTimeZone;
 | 
				
			||||||
@@ -207,8 +204,8 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected ChangeNotes newNotes(Change c) throws OrmException {
 | 
					  protected ChangeNotes newNotes(Change c) throws OrmException {
 | 
				
			||||||
    return new ChangeNotes(repoManager, MIGRATION, allUsers, changeNoteUtil,
 | 
					    return new ChangeNotes(repoManager, MIGRATION, allUsers, noteUtil,
 | 
				
			||||||
        commentsUtil, c.getProject(), c).load();
 | 
					        c.getProject(), c).load();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected static SubmitRecord submitRecord(String status,
 | 
					  protected static SubmitRecord submitRecord(String status,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -415,7 +415,7 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private RevCommit writeCommit(String body) throws Exception {
 | 
					  private RevCommit writeCommit(String body) throws Exception {
 | 
				
			||||||
    return writeCommit(body, changeNoteUtil.newIdent(
 | 
					    return writeCommit(body, noteUtil.newIdent(
 | 
				
			||||||
        changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent,
 | 
					        changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent,
 | 
				
			||||||
        "Anonymous Coward"));
 | 
					        "Anonymous Coward"));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -462,6 +462,6 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
  private ChangeNotesParser newParser(ObjectId tip) throws Exception {
 | 
					  private ChangeNotesParser newParser(ObjectId tip) throws Exception {
 | 
				
			||||||
    Change c = newChange();
 | 
					    Change c = newChange();
 | 
				
			||||||
    return new ChangeNotesParser(c.getProject(), c.getId(), tip, walk,
 | 
					    return new ChangeNotesParser(c.getProject(), c.getId(), tip, walk,
 | 
				
			||||||
        repoManager, changeNoteUtil, commentsUtil);
 | 
					        repoManager, noteUtil);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -825,7 +825,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
        + "File: a.txt\n"
 | 
					        + "File: a.txt\n"
 | 
				
			||||||
        + "\n"
 | 
					        + "\n"
 | 
				
			||||||
        + "1:2-3:4\n"
 | 
					        + "1:2-3:4\n"
 | 
				
			||||||
        + CommentsInNotesUtil.formatTime(serverIdent, ts) + "\n"
 | 
					        + ChangeNoteUtil.formatTime(serverIdent, ts) + "\n"
 | 
				
			||||||
        + "Author: Change Owner <1@gerrit>\n"
 | 
					        + "Author: Change Owner <1@gerrit>\n"
 | 
				
			||||||
        + "UUID: uuid1\n"
 | 
					        + "UUID: uuid1\n"
 | 
				
			||||||
        + "Bytes: 7\n"
 | 
					        + "Bytes: 7\n"
 | 
				
			||||||
@@ -910,8 +910,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try (RevWalk rw = new RevWalk(repo)) {
 | 
					    try (RevWalk rw = new RevWalk(repo)) {
 | 
				
			||||||
      try (ChangeNotesParser notesWithComments = new ChangeNotesParser(project,
 | 
					      try (ChangeNotesParser notesWithComments = new ChangeNotesParser(project,
 | 
				
			||||||
          c.getId(), commitWithComments.copy(), rw, repoManager, changeNoteUtil,
 | 
					          c.getId(), commitWithComments.copy(), rw, repoManager, noteUtil)) {
 | 
				
			||||||
          commentsUtil)) {
 | 
					 | 
				
			||||||
        notesWithComments.parseAll();
 | 
					        notesWithComments.parseAll();
 | 
				
			||||||
        ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals1 =
 | 
					        ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals1 =
 | 
				
			||||||
            notesWithComments.buildApprovals();
 | 
					            notesWithComments.buildApprovals();
 | 
				
			||||||
@@ -923,7 +922,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
    try (RevWalk rw = new RevWalk(repo)) {
 | 
					    try (RevWalk rw = new RevWalk(repo)) {
 | 
				
			||||||
      try (ChangeNotesParser notesWithApprovals = new ChangeNotesParser(project,
 | 
					      try (ChangeNotesParser notesWithApprovals = new ChangeNotesParser(project,
 | 
				
			||||||
          c.getId(), commitWithApprovals.copy(), rw, repoManager,
 | 
					          c.getId(), commitWithApprovals.copy(), rw, repoManager,
 | 
				
			||||||
          changeNoteUtil, commentsUtil)) {
 | 
					          noteUtil)) {
 | 
				
			||||||
        notesWithApprovals.parseAll();
 | 
					        notesWithApprovals.parseAll();
 | 
				
			||||||
        ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals2 =
 | 
					        ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals2 =
 | 
				
			||||||
            notesWithApprovals.buildApprovals();
 | 
					            notesWithApprovals.buildApprovals();
 | 
				
			||||||
@@ -1168,14 +1167,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
          + "File: file1\n"
 | 
					          + "File: file1\n"
 | 
				
			||||||
          + "\n"
 | 
					          + "\n"
 | 
				
			||||||
          + "1:1-2:1\n"
 | 
					          + "1:1-2:1\n"
 | 
				
			||||||
          + CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
 | 
					          + ChangeNoteUtil.formatTime(serverIdent, time1) + "\n"
 | 
				
			||||||
          + "Author: Other Account <2@gerrit>\n"
 | 
					          + "Author: Other Account <2@gerrit>\n"
 | 
				
			||||||
          + "UUID: uuid1\n"
 | 
					          + "UUID: uuid1\n"
 | 
				
			||||||
          + "Bytes: 9\n"
 | 
					          + "Bytes: 9\n"
 | 
				
			||||||
          + "comment 1\n"
 | 
					          + "comment 1\n"
 | 
				
			||||||
          + "\n"
 | 
					          + "\n"
 | 
				
			||||||
          + "2:1-3:1\n"
 | 
					          + "2:1-3:1\n"
 | 
				
			||||||
          + CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
 | 
					          + ChangeNoteUtil.formatTime(serverIdent, time2) + "\n"
 | 
				
			||||||
          + "Author: Other Account <2@gerrit>\n"
 | 
					          + "Author: Other Account <2@gerrit>\n"
 | 
				
			||||||
          + "UUID: uuid2\n"
 | 
					          + "UUID: uuid2\n"
 | 
				
			||||||
          + "Bytes: 9\n"
 | 
					          + "Bytes: 9\n"
 | 
				
			||||||
@@ -1184,7 +1183,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
          + "File: file2\n"
 | 
					          + "File: file2\n"
 | 
				
			||||||
          + "\n"
 | 
					          + "\n"
 | 
				
			||||||
          + "3:0-4:1\n"
 | 
					          + "3:0-4:1\n"
 | 
				
			||||||
          + CommentsInNotesUtil.formatTime(serverIdent, time3) + "\n"
 | 
					          + ChangeNoteUtil.formatTime(serverIdent, time3) + "\n"
 | 
				
			||||||
          + "Author: Other Account <2@gerrit>\n"
 | 
					          + "Author: Other Account <2@gerrit>\n"
 | 
				
			||||||
          + "UUID: uuid3\n"
 | 
					          + "UUID: uuid3\n"
 | 
				
			||||||
          + "Bytes: 9\n"
 | 
					          + "Bytes: 9\n"
 | 
				
			||||||
@@ -1238,14 +1237,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
 | 
				
			|||||||
          + "File: file1\n"
 | 
					          + "File: file1\n"
 | 
				
			||||||
          + "\n"
 | 
					          + "\n"
 | 
				
			||||||
          + "1:1-2:1\n"
 | 
					          + "1:1-2:1\n"
 | 
				
			||||||
          + CommentsInNotesUtil.formatTime(serverIdent, time1) + "\n"
 | 
					          + ChangeNoteUtil.formatTime(serverIdent, time1) + "\n"
 | 
				
			||||||
          + "Author: Other Account <2@gerrit>\n"
 | 
					          + "Author: Other Account <2@gerrit>\n"
 | 
				
			||||||
          + "UUID: uuid1\n"
 | 
					          + "UUID: uuid1\n"
 | 
				
			||||||
          + "Bytes: 9\n"
 | 
					          + "Bytes: 9\n"
 | 
				
			||||||
          + "comment 1\n"
 | 
					          + "comment 1\n"
 | 
				
			||||||
          + "\n"
 | 
					          + "\n"
 | 
				
			||||||
          + "2:1-3:1\n"
 | 
					          + "2:1-3:1\n"
 | 
				
			||||||
          + CommentsInNotesUtil.formatTime(serverIdent, time2) + "\n"
 | 
					          + ChangeNoteUtil.formatTime(serverIdent, time2) + "\n"
 | 
				
			||||||
          + "Author: Other Account <2@gerrit>\n"
 | 
					          + "Author: Other Account <2@gerrit>\n"
 | 
				
			||||||
          + "UUID: uuid2\n"
 | 
					          + "UUID: uuid2\n"
 | 
				
			||||||
          + "Bytes: 9\n"
 | 
					          + "Bytes: 9\n"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,6 @@ import com.google.gerrit.server.notedb.ChangeDraftUpdate;
 | 
				
			|||||||
import com.google.gerrit.server.notedb.ChangeNoteUtil;
 | 
					import com.google.gerrit.server.notedb.ChangeNoteUtil;
 | 
				
			||||||
import com.google.gerrit.server.notedb.ChangeNotes;
 | 
					import com.google.gerrit.server.notedb.ChangeNotes;
 | 
				
			||||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
					import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
				
			||||||
import com.google.gerrit.server.notedb.CommentsInNotesUtil;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.notedb.NotesMigration;
 | 
					import com.google.gerrit.server.notedb.NotesMigration;
 | 
				
			||||||
import com.google.gerrit.server.project.ChangeControl;
 | 
					import com.google.gerrit.server.project.ChangeControl;
 | 
				
			||||||
import com.google.gwtorm.server.OrmException;
 | 
					import com.google.gwtorm.server.OrmException;
 | 
				
			||||||
@@ -105,7 +104,6 @@ public class TestChanges {
 | 
				
			|||||||
            stubChangeControl(
 | 
					            stubChangeControl(
 | 
				
			||||||
                repoManager, migration, c, allUsers,
 | 
					                repoManager, migration, c, allUsers,
 | 
				
			||||||
                injector.getInstance(ChangeNoteUtil.class),
 | 
					                injector.getInstance(ChangeNoteUtil.class),
 | 
				
			||||||
                injector.getInstance(CommentsInNotesUtil.class),
 | 
					 | 
				
			||||||
                user),
 | 
					                user),
 | 
				
			||||||
            TimeUtil.nowTs(), Ordering.<String> natural());
 | 
					            TimeUtil.nowTs(), Ordering.<String> natural());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -139,15 +137,15 @@ public class TestChanges {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private static ChangeControl stubChangeControl(
 | 
					  private static ChangeControl stubChangeControl(
 | 
				
			||||||
      GitRepositoryManager repoManager, NotesMigration migration,
 | 
					      GitRepositoryManager repoManager, NotesMigration migration,
 | 
				
			||||||
      Change c, AllUsersName allUsers, ChangeNoteUtil changeNoteUtil,
 | 
					      Change c, AllUsersName allUsers, ChangeNoteUtil noteUtil,
 | 
				
			||||||
      CommentsInNotesUtil commentsUtil, CurrentUser user) throws OrmException {
 | 
					      CurrentUser user) throws OrmException {
 | 
				
			||||||
    ChangeControl ctl = EasyMock.createMock(ChangeControl.class);
 | 
					    ChangeControl ctl = EasyMock.createMock(ChangeControl.class);
 | 
				
			||||||
    expect(ctl.getChange()).andStubReturn(c);
 | 
					    expect(ctl.getChange()).andStubReturn(c);
 | 
				
			||||||
    expect(ctl.getProject()).andStubReturn(new Project(c.getProject()));
 | 
					    expect(ctl.getProject()).andStubReturn(new Project(c.getProject()));
 | 
				
			||||||
    expect(ctl.getUser()).andStubReturn(user);
 | 
					    expect(ctl.getUser()).andStubReturn(user);
 | 
				
			||||||
    ChangeNotes notes =
 | 
					    ChangeNotes notes =
 | 
				
			||||||
        new ChangeNotes(repoManager, migration, allUsers, changeNoteUtil,
 | 
					        new ChangeNotes(repoManager, migration, allUsers, noteUtil,
 | 
				
			||||||
            commentsUtil, c.getProject(), c).load();
 | 
					            c.getProject(), c).load();
 | 
				
			||||||
    expect(ctl.getNotes()).andStubReturn(notes);
 | 
					    expect(ctl.getNotes()).andStubReturn(notes);
 | 
				
			||||||
    expect(ctl.getId()).andStubReturn(c.getId());
 | 
					    expect(ctl.getId()).andStubReturn(c.getId());
 | 
				
			||||||
    EasyMock.replay(ctl);
 | 
					    EasyMock.replay(ctl);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user