Split ChangeNoteUtil into JSON/Read/Write parts
The write part needs the AccountCache, which is problematic in the schema migration. Change-Id: I2b8a6a0d946c765588107fb10abd19b96aef274c
This commit is contained in:
@@ -48,7 +48,8 @@ public abstract class AbstractChangeNotes<T> {
|
|||||||
final GitRepositoryManager repoManager;
|
final GitRepositoryManager repoManager;
|
||||||
final NotesMigration migration;
|
final NotesMigration migration;
|
||||||
final AllUsersName allUsers;
|
final AllUsersName allUsers;
|
||||||
final ChangeNoteUtil noteUtil;
|
final ChangeNoteJson changeNoteJson;
|
||||||
|
final LegacyChangeNoteRead legacyChangeNoteRead;
|
||||||
final NoteDbMetrics metrics;
|
final NoteDbMetrics metrics;
|
||||||
final Provider<ReviewDb> db;
|
final Provider<ReviewDb> db;
|
||||||
|
|
||||||
@@ -65,7 +66,8 @@ public abstract class AbstractChangeNotes<T> {
|
|||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
AllUsersName allUsers,
|
AllUsersName allUsers,
|
||||||
ChangeNoteUtil noteUtil,
|
ChangeNoteJson changeNoteJson,
|
||||||
|
LegacyChangeNoteRead legacyChangeNoteRead,
|
||||||
NoteDbMetrics metrics,
|
NoteDbMetrics metrics,
|
||||||
Provider<ReviewDb> db,
|
Provider<ReviewDb> db,
|
||||||
Provider<ChangeRebuilder> rebuilder,
|
Provider<ChangeRebuilder> rebuilder,
|
||||||
@@ -73,7 +75,8 @@ public abstract class AbstractChangeNotes<T> {
|
|||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.migration = migration;
|
this.migration = migration;
|
||||||
this.allUsers = allUsers;
|
this.allUsers = allUsers;
|
||||||
this.noteUtil = noteUtil;
|
this.legacyChangeNoteRead = legacyChangeNoteRead;
|
||||||
|
this.changeNoteJson = changeNoteJson;
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.rebuilder = rebuilder;
|
this.rebuilder = rebuilder;
|
||||||
|
@@ -120,7 +120,9 @@ public abstract class AbstractChangeUpdate {
|
|||||||
ChangeNoteUtil noteUtil, PersonIdent serverIdent, CurrentUser u, Date when) {
|
ChangeNoteUtil noteUtil, PersonIdent serverIdent, CurrentUser u, Date when) {
|
||||||
checkUserType(u);
|
checkUserType(u);
|
||||||
if (u instanceof IdentifiedUser) {
|
if (u instanceof IdentifiedUser) {
|
||||||
return noteUtil.newIdent(u.asIdentifiedUser().getAccount(), when, serverIdent);
|
return noteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(u.asIdentifiedUser().getAccount(), when, serverIdent);
|
||||||
} else if (u instanceof InternalUser) {
|
} else if (u instanceof InternalUser) {
|
||||||
return serverIdent;
|
return serverIdent;
|
||||||
}
|
}
|
||||||
@@ -175,7 +177,7 @@ public abstract class AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected PersonIdent newIdent(Account.Id authorId, Date when) {
|
protected PersonIdent newIdent(Account.Id authorId, Date when) {
|
||||||
return noteUtil.newIdent(authorId, when, serverIdent);
|
return noteUtil.getLegacyChangeNoteWrite().newIdent(authorId, when, serverIdent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether no updates have been done. */
|
/** Whether no updates have been done. */
|
||||||
|
@@ -505,11 +505,11 @@ public class ChangeBundle {
|
|||||||
if (rn >= 0) {
|
if (rn >= 0) {
|
||||||
s = s.substring(0, rn);
|
s = s.substring(0, rn);
|
||||||
}
|
}
|
||||||
return ChangeNoteUtil.sanitizeFooter(s);
|
return NoteDbUtil.sanitizeFooter(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String cleanNoteDbSubject(String s) {
|
private static String cleanNoteDbSubject(String s) {
|
||||||
return ChangeNoteUtil.sanitizeFooter(s);
|
return NoteDbUtil.sanitizeFooter(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -178,7 +178,12 @@ 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(noteUtil, noteUtil.getWriteJson());
|
byte[] data =
|
||||||
|
e.getValue()
|
||||||
|
.build(
|
||||||
|
noteUtil.getChangeNoteJson(),
|
||||||
|
noteUtil.getLegacyChangeNoteWrite(),
|
||||||
|
noteUtil.getChangeNoteJson().getWriteJson());
|
||||||
if (!Arrays.equals(data, e.getValue().baseRaw)) {
|
if (!Arrays.equals(data, e.getValue().baseRaw)) {
|
||||||
touchedAnyRevs = true;
|
touchedAnyRevs = true;
|
||||||
}
|
}
|
||||||
@@ -236,7 +241,12 @@ 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(
|
||||||
noteUtil, getId(), rw.getObjectReader(), noteMap, PatchLineComment.Status.DRAFT);
|
noteUtil.getChangeNoteJson(),
|
||||||
|
noteUtil.getLegacyChangeNoteRead(),
|
||||||
|
getId(),
|
||||||
|
rw.getObjectReader(),
|
||||||
|
noteMap,
|
||||||
|
PatchLineComment.Status.DRAFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
49
java/com/google/gerrit/server/notedb/ChangeNoteJson.java
Normal file
49
java/com/google/gerrit/server/notedb/ChangeNoteJson.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (C) 2018 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 com.google.gerrit.server.config.GerritServerConfig;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ChangeNoteJson {
|
||||||
|
private final Gson gson = newGson();
|
||||||
|
private final boolean writeJson;
|
||||||
|
|
||||||
|
static Gson newGson() {
|
||||||
|
return new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Timestamp.class, new CommentTimestampAdapter().nullSafe())
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Gson getGson() {
|
||||||
|
return gson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getWriteJson() {
|
||||||
|
return writeJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ChangeNoteJson(@GerritServerConfig Config config) {
|
||||||
|
this.writeJson = config.getBoolean("notedb", "writeJson", true);
|
||||||
|
}
|
||||||
|
}
|
@@ -14,52 +14,8 @@
|
|||||||
|
|
||||||
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.CommentsUtil.COMMENT_ORDER;
|
|
||||||
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.base.CharMatcher;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ListMultimap;
|
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
|
||||||
import com.google.gerrit.reviewdb.client.Comment;
|
|
||||||
import com.google.gerrit.reviewdb.client.CommentRange;
|
|
||||||
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.account.AccountCache;
|
|
||||||
import com.google.gerrit.server.account.AccountState;
|
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
|
||||||
import com.google.gerrit.server.config.GerritServerId;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.sql.Timestamp;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
|
||||||
import org.eclipse.jgit.lib.Config;
|
|
||||||
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;
|
|
||||||
|
|
||||||
public class ChangeNoteUtil {
|
public class ChangeNoteUtil {
|
||||||
public static final FooterKey FOOTER_ASSIGNEE = new FooterKey("Assignee");
|
public static final FooterKey FOOTER_ASSIGNEE = new FooterKey("Assignee");
|
||||||
@@ -85,560 +41,43 @@ public class ChangeNoteUtil {
|
|||||||
public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
|
public static final FooterKey FOOTER_WORK_IN_PROGRESS = new FooterKey("Work-in-progress");
|
||||||
public static final FooterKey FOOTER_REVERT_OF = new FooterKey("Revert-of");
|
public static final FooterKey FOOTER_REVERT_OF = new FooterKey("Revert-of");
|
||||||
|
|
||||||
private static final String AUTHOR = "Author";
|
static final String AUTHOR = "Author";
|
||||||
private static final String BASE_PATCH_SET = "Base-for-patch-set";
|
static final String BASE_PATCH_SET = "Base-for-patch-set";
|
||||||
private static final String COMMENT_RANGE = "Comment-range";
|
static final String COMMENT_RANGE = "Comment-range";
|
||||||
private static final String FILE = "File";
|
static final String FILE = "File";
|
||||||
private static final String LENGTH = "Bytes";
|
static final String LENGTH = "Bytes";
|
||||||
private static final String PARENT = "Parent";
|
static final String PARENT = "Parent";
|
||||||
private static final String PARENT_NUMBER = "Parent-number";
|
static final String PARENT_NUMBER = "Parent-number";
|
||||||
private static final String PATCH_SET = "Patch-set";
|
static final String PATCH_SET = "Patch-set";
|
||||||
private static final String REAL_AUTHOR = "Real-author";
|
static final String REAL_AUTHOR = "Real-author";
|
||||||
private static final String REVISION = "Revision";
|
static final String REVISION = "Revision";
|
||||||
private static final String UUID = "UUID";
|
static final String UUID = "UUID";
|
||||||
private static final String UNRESOLVED = "Unresolved";
|
static final String UNRESOLVED = "Unresolved";
|
||||||
private static final String TAG = FOOTER_TAG.getName();
|
static final String TAG = FOOTER_TAG.getName();
|
||||||
|
|
||||||
public static String formatTime(PersonIdent ident, Timestamp t) {
|
private final LegacyChangeNoteRead legacyChangeNoteRead;
|
||||||
GitDateFormatter dateFormatter = new GitDateFormatter(Format.DEFAULT);
|
private final LegacyChangeNoteWrite legacyChangeNoteWrite;
|
||||||
// TODO(dborowitz): Use a ThreadLocal or use Joda.
|
private final ChangeNoteJson changeNoteJson;
|
||||||
PersonIdent newIdent = new PersonIdent(ident, t);
|
|
||||||
return dateFormatter.formatDate(newIdent);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Gson newGson() {
|
|
||||||
return new GsonBuilder()
|
|
||||||
.registerTypeAdapter(Timestamp.class, new CommentTimestampAdapter().nullSafe())
|
|
||||||
.setPrettyPrinting()
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final AccountCache accountCache;
|
|
||||||
private final PersonIdent serverIdent;
|
|
||||||
private final String serverId;
|
|
||||||
private final Gson gson = newGson();
|
|
||||||
private final boolean writeJson;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ChangeNoteUtil(
|
public ChangeNoteUtil(
|
||||||
AccountCache accountCache,
|
ChangeNoteJson changeNoteJson,
|
||||||
@GerritPersonIdent PersonIdent serverIdent,
|
LegacyChangeNoteRead legacyChangeNoteRead,
|
||||||
@GerritServerId String serverId,
|
LegacyChangeNoteWrite legacyChangeNoteWrite) {
|
||||||
@GerritServerConfig Config config) {
|
this.changeNoteJson = changeNoteJson;
|
||||||
this.accountCache = accountCache;
|
this.legacyChangeNoteRead = legacyChangeNoteRead;
|
||||||
this.serverIdent = serverIdent;
|
this.legacyChangeNoteWrite = legacyChangeNoteWrite;
|
||||||
this.serverId = serverId;
|
|
||||||
this.writeJson = config.getBoolean("notedb", "writeJson", true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PersonIdent newIdent(Account.Id authorId, Date when, PersonIdent serverIdent) {
|
public LegacyChangeNoteRead getLegacyChangeNoteRead() {
|
||||||
Optional<Account> author = accountCache.get(authorId).map(AccountState::getAccount);
|
return legacyChangeNoteRead;
|
||||||
return new PersonIdent(
|
|
||||||
author.map(Account::getName).orElseGet(() -> Account.getName(authorId)),
|
|
||||||
authorId.get() + "@" + serverId,
|
|
||||||
when,
|
|
||||||
serverIdent.getTimeZone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
public ChangeNoteJson getChangeNoteJson() {
|
||||||
public PersonIdent newIdent(Account author, Date when, PersonIdent serverIdent) {
|
return changeNoteJson;
|
||||||
return new PersonIdent(
|
|
||||||
author.getName(), author.getId().get() + "@" + serverId, when, serverIdent.getTimeZone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getWriteJson() {
|
public LegacyChangeNoteWrite getLegacyChangeNoteWrite() {
|
||||||
return writeJson;
|
return legacyChangeNoteWrite;
|
||||||
}
|
|
||||||
|
|
||||||
public Gson getGson() {
|
|
||||||
return gson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getServerId() {
|
|
||||||
return serverId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Account.Id parseIdent(PersonIdent ident, Change.Id changeId)
|
|
||||||
throws ConfigInvalidException {
|
|
||||||
return NoteDbUtil.parseIdent(ident, serverId)
|
|
||||||
.orElseThrow(
|
|
||||||
() ->
|
|
||||||
parseException(
|
|
||||||
changeId,
|
|
||||||
"invalid identity, expected <id>@%s: %s",
|
|
||||||
serverId,
|
|
||||||
ident.getEmailAddress()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean match(byte[] note, MutableInteger p, byte[] expected) {
|
|
||||||
int m = RawParseUtils.match(note, p.value, expected);
|
|
||||||
return m == p.value + expected.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Comment> parseNote(byte[] note, MutableInteger p, Change.Id changeId)
|
|
||||||
throws ConfigInvalidException {
|
|
||||||
if (p.value >= note.length) {
|
|
||||||
return ImmutableList.of();
|
|
||||||
}
|
|
||||||
Set<Comment.Key> seen = new HashSet<>();
|
|
||||||
List<Comment> result = new ArrayList<>();
|
|
||||||
int sizeOfNote = note.length;
|
|
||||||
byte[] psb = PATCH_SET.getBytes(UTF_8);
|
|
||||||
byte[] bpsb = BASE_PATCH_SET.getBytes(UTF_8);
|
|
||||||
byte[] bpn = PARENT_NUMBER.getBytes(UTF_8);
|
|
||||||
|
|
||||||
RevId revId = new RevId(parseStringField(note, p, changeId, REVISION));
|
|
||||||
String fileName = null;
|
|
||||||
PatchSet.Id psId = null;
|
|
||||||
boolean isForBase = false;
|
|
||||||
Integer parentNumber = null;
|
|
||||||
|
|
||||||
while (p.value < sizeOfNote) {
|
|
||||||
boolean matchPs = match(note, p, psb);
|
|
||||||
boolean matchBase = match(note, p, bpsb);
|
|
||||||
if (matchPs) {
|
|
||||||
fileName = null;
|
|
||||||
psId = parsePsId(note, p, changeId, PATCH_SET);
|
|
||||||
isForBase = false;
|
|
||||||
} else if (matchBase) {
|
|
||||||
fileName = null;
|
|
||||||
psId = parsePsId(note, p, changeId, BASE_PATCH_SET);
|
|
||||||
isForBase = true;
|
|
||||||
if (match(note, p, bpn)) {
|
|
||||||
parentNumber = parseParentNumber(note, p, changeId);
|
|
||||||
}
|
|
||||||
} else if (psId == null) {
|
|
||||||
throw parseException(changeId, "missing %s or %s header", PATCH_SET, BASE_PATCH_SET);
|
|
||||||
}
|
|
||||||
|
|
||||||
Comment c = parseComment(note, p, fileName, psId, revId, isForBase, parentNumber);
|
|
||||||
fileName = c.key.filename;
|
|
||||||
if (!seen.add(c.key)) {
|
|
||||||
throw parseException(changeId, "multiple comments for %s in note", c.key);
|
|
||||||
}
|
|
||||||
result.add(c);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Comment parseComment(
|
|
||||||
byte[] note,
|
|
||||||
MutableInteger curr,
|
|
||||||
String currentFileName,
|
|
||||||
PatchSet.Id psId,
|
|
||||||
RevId revId,
|
|
||||||
boolean isForBase,
|
|
||||||
Integer parentNumber)
|
|
||||||
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, AUTHOR);
|
|
||||||
boolean hasRealAuthor =
|
|
||||||
(RawParseUtils.match(note, curr.value, REAL_AUTHOR.getBytes(UTF_8))) != -1;
|
|
||||||
Account.Id raId = null;
|
|
||||||
if (hasRealAuthor) {
|
|
||||||
raId = parseAuthor(note, curr, changeId, REAL_AUTHOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasParent = (RawParseUtils.match(note, curr.value, PARENT.getBytes(UTF_8))) != -1;
|
|
||||||
String parentUUID = null;
|
|
||||||
boolean unresolved = false;
|
|
||||||
if (hasParent) {
|
|
||||||
parentUUID = parseStringField(note, curr, changeId, PARENT);
|
|
||||||
}
|
|
||||||
boolean hasUnresolved =
|
|
||||||
(RawParseUtils.match(note, curr.value, UNRESOLVED.getBytes(UTF_8))) != -1;
|
|
||||||
if (hasUnresolved) {
|
|
||||||
unresolved = parseBooleanField(note, curr, changeId, UNRESOLVED);
|
|
||||||
}
|
|
||||||
|
|
||||||
String uuid = parseStringField(note, curr, changeId, UUID);
|
|
||||||
|
|
||||||
boolean hasTag = (RawParseUtils.match(note, curr.value, TAG.getBytes(UTF_8))) != -1;
|
|
||||||
String tag = null;
|
|
||||||
if (hasTag) {
|
|
||||||
tag = parseStringField(note, curr, changeId, TAG);
|
|
||||||
}
|
|
||||||
|
|
||||||
int commentLength = parseCommentLength(note, curr, changeId);
|
|
||||||
|
|
||||||
String message = RawParseUtils.decode(UTF_8, note, curr.value, curr.value + commentLength);
|
|
||||||
checkResult(message, "message contents", changeId);
|
|
||||||
|
|
||||||
Comment c =
|
|
||||||
new Comment(
|
|
||||||
new Comment.Key(uuid, currentFileName, psId.get()),
|
|
||||||
aId,
|
|
||||||
commentTime,
|
|
||||||
isForBase ? (short) (parentNumber == null ? 0 : -parentNumber) : (short) 1,
|
|
||||||
message,
|
|
||||||
serverId,
|
|
||||||
unresolved);
|
|
||||||
c.lineNbr = range.getEndLine();
|
|
||||||
c.parentUuid = parentUUID;
|
|
||||||
c.tag = tag;
|
|
||||||
c.setRevId(revId);
|
|
||||||
if (raId != null) {
|
|
||||||
c.setRealAuthor(raId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (range.getStartCharacter() != -1) {
|
|
||||||
c.setRange(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
curr.value = RawParseUtils.nextLF(note, curr.value + commentLength);
|
|
||||||
curr.value = RawParseUtils.nextLF(note, curr.value);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 last = ptr.value;
|
|
||||||
int startLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
|
||||||
if (ptr.value == last) {
|
|
||||||
return null;
|
|
||||||
} else 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
last = ptr.value;
|
|
||||||
int startChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
|
||||||
if (ptr.value == last) {
|
|
||||||
return null;
|
|
||||||
} else if (note[ptr.value] == '-') {
|
|
||||||
range.setStartCharacter(startChar);
|
|
||||||
ptr.value += 1;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
last = ptr.value;
|
|
||||||
int endLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
|
||||||
if (ptr.value == last) {
|
|
||||||
return null;
|
|
||||||
} else if (note[ptr.value] == ':') {
|
|
||||||
range.setEndLine(endLine);
|
|
||||||
ptr.value += 1;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
last = ptr.value;
|
|
||||||
int endChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
|
||||||
if (ptr.value == last) {
|
|
||||||
return null;
|
|
||||||
} else 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 Integer parseParentNumber(byte[] note, MutableInteger curr, Change.Id changeId)
|
|
||||||
throws ConfigInvalidException {
|
|
||||||
checkHeaderLineFormat(note, curr, PARENT_NUMBER, changeId);
|
|
||||||
|
|
||||||
int start = RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
|
|
||||||
MutableInteger i = new MutableInteger();
|
|
||||||
int parentNumber = RawParseUtils.parseBase10(note, start, i);
|
|
||||||
int endOfLine = RawParseUtils.nextLF(note, curr.value);
|
|
||||||
if (i.value != endOfLine - 1) {
|
|
||||||
throw parseException(changeId, "could not parse %s", PARENT_NUMBER);
|
|
||||||
}
|
|
||||||
checkResult(parentNumber, "parent number", changeId);
|
|
||||||
curr.value = endOfLine;
|
|
||||||
return Integer.valueOf(parentNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, String fieldName)
|
|
||||||
throws ConfigInvalidException {
|
|
||||||
checkHeaderLineFormat(note, curr, fieldName, 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, fieldName, 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();
|
|
||||||
i.value = startOfLength;
|
|
||||||
int commentLength = RawParseUtils.parseBase10(note, startOfLength, i);
|
|
||||||
if (i.value == startOfLength) {
|
|
||||||
throw parseException(changeId, "could not parse %s", LENGTH);
|
|
||||||
}
|
|
||||||
int endOfLine = RawParseUtils.nextLF(note, curr.value);
|
|
||||||
if (i.value != endOfLine - 1) {
|
|
||||||
throw parseException(changeId, "could not parse %s", LENGTH);
|
|
||||||
}
|
|
||||||
curr.value = endOfLine;
|
|
||||||
return checkResult(commentLength, "comment length", changeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean parseBooleanField(
|
|
||||||
byte[] note, MutableInteger curr, Change.Id changeId, String fieldName)
|
|
||||||
throws ConfigInvalidException {
|
|
||||||
String str = parseStringField(note, curr, changeId, fieldName);
|
|
||||||
if ("true".equalsIgnoreCase(str)) {
|
|
||||||
return true;
|
|
||||||
} else if ("false".equalsIgnoreCase(str)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw parseException(changeId, "invalid boolean for %s: %s", fieldName, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a note that contains the metadata for and the contents of all of the comments in the
|
|
||||||
* given comments.
|
|
||||||
*
|
|
||||||
* @param comments Comments to be written to the output stream, keyed by patch set ID; multiple
|
|
||||||
* patch sets are allowed since base revisions may be shared across patch sets. All of the
|
|
||||||
* comments must share the same RevId, and all the comments for a given patch set must have
|
|
||||||
* the same side.
|
|
||||||
* @param out output stream to write to.
|
|
||||||
*/
|
|
||||||
void buildNote(ListMultimap<Integer, Comment> comments, OutputStream out) {
|
|
||||||
if (comments.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Integer> psIds = new ArrayList<>(comments.keySet());
|
|
||||||
Collections.sort(psIds);
|
|
||||||
|
|
||||||
OutputStreamWriter streamWriter = new OutputStreamWriter(out, UTF_8);
|
|
||||||
try (PrintWriter writer = new PrintWriter(streamWriter)) {
|
|
||||||
String revId = comments.values().iterator().next().revId;
|
|
||||||
appendHeaderField(writer, REVISION, revId);
|
|
||||||
|
|
||||||
for (int psId : psIds) {
|
|
||||||
List<Comment> psComments = COMMENT_ORDER.sortedCopy(comments.get(psId));
|
|
||||||
Comment first = psComments.get(0);
|
|
||||||
|
|
||||||
short side = first.side;
|
|
||||||
appendHeaderField(writer, side <= 0 ? BASE_PATCH_SET : PATCH_SET, Integer.toString(psId));
|
|
||||||
if (side < 0) {
|
|
||||||
appendHeaderField(writer, PARENT_NUMBER, Integer.toString(-side));
|
|
||||||
}
|
|
||||||
|
|
||||||
String currentFilename = null;
|
|
||||||
|
|
||||||
for (Comment c : psComments) {
|
|
||||||
checkArgument(
|
|
||||||
revId.equals(c.revId),
|
|
||||||
"All comments being added must have all the same RevId. The "
|
|
||||||
+ "comment below does not have the same RevId as the others "
|
|
||||||
+ "(%s).\n%s",
|
|
||||||
revId,
|
|
||||||
c);
|
|
||||||
checkArgument(
|
|
||||||
side == c.side,
|
|
||||||
"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);
|
|
||||||
String commentFilename = QuotedString.GIT_PATH.quote(c.key.filename);
|
|
||||||
|
|
||||||
if (!commentFilename.equals(currentFilename)) {
|
|
||||||
currentFilename = commentFilename;
|
|
||||||
writer.print("File: ");
|
|
||||||
writer.print(commentFilename);
|
|
||||||
writer.print("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
appendOneComment(writer, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendOneComment(PrintWriter writer, Comment c) {
|
|
||||||
// The CommentRange field for a comment is allowed to be null. If it is
|
|
||||||
// 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.
|
|
||||||
Comment.Range range = c.range;
|
|
||||||
if (range != null) {
|
|
||||||
writer.print(range.startLine);
|
|
||||||
writer.print(':');
|
|
||||||
writer.print(range.startChar);
|
|
||||||
writer.print('-');
|
|
||||||
writer.print(range.endLine);
|
|
||||||
writer.print(':');
|
|
||||||
writer.print(range.endChar);
|
|
||||||
} else {
|
|
||||||
writer.print(c.lineNbr);
|
|
||||||
}
|
|
||||||
writer.print("\n");
|
|
||||||
|
|
||||||
writer.print(formatTime(serverIdent, c.writtenOn));
|
|
||||||
writer.print("\n");
|
|
||||||
|
|
||||||
appendIdent(writer, AUTHOR, c.author.getId(), c.writtenOn);
|
|
||||||
if (!c.getRealAuthor().equals(c.author)) {
|
|
||||||
appendIdent(writer, REAL_AUTHOR, c.getRealAuthor().getId(), c.writtenOn);
|
|
||||||
}
|
|
||||||
|
|
||||||
String parent = c.parentUuid;
|
|
||||||
if (parent != null) {
|
|
||||||
appendHeaderField(writer, PARENT, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
appendHeaderField(writer, UNRESOLVED, Boolean.toString(c.unresolved));
|
|
||||||
appendHeaderField(writer, UUID, c.key.uuid);
|
|
||||||
|
|
||||||
if (c.tag != null) {
|
|
||||||
appendHeaderField(writer, TAG, c.tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] messageBytes = c.message.getBytes(UTF_8);
|
|
||||||
appendHeaderField(writer, LENGTH, Integer.toString(messageBytes.length));
|
|
||||||
|
|
||||||
writer.print(c.message);
|
|
||||||
writer.print("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendIdent(PrintWriter writer, String header, Account.Id id, Timestamp ts) {
|
|
||||||
PersonIdent ident = newIdent(id, ts, serverIdent);
|
|
||||||
StringBuilder name = new StringBuilder();
|
|
||||||
PersonIdent.appendSanitized(name, ident.getName());
|
|
||||||
name.append(" <");
|
|
||||||
PersonIdent.appendSanitized(name, ident.getEmailAddress());
|
|
||||||
name.append('>');
|
|
||||||
appendHeaderField(writer, header, name.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final CharMatcher INVALID_FOOTER_CHARS = CharMatcher.anyOf("\r\n\0");
|
|
||||||
|
|
||||||
static String sanitizeFooter(String value) {
|
|
||||||
// Remove characters that would confuse JGit's footer parser if they were
|
|
||||||
// included in footer values, for example by splitting the footer block into
|
|
||||||
// multiple paragraphs.
|
|
||||||
//
|
|
||||||
// One painful example: RevCommit#getShorMessage() might return a message
|
|
||||||
// containing "\r\r", which RevCommit#getFooterLines() will treat as an
|
|
||||||
// empty paragraph for the purposes of footer parsing.
|
|
||||||
return INVALID_FOOTER_CHARS.trimAndCollapseFrom(value, ' ');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -346,7 +346,13 @@ public class ChangeNotesCache {
|
|||||||
@Override
|
@Override
|
||||||
public ChangeNotesState call() throws ConfigInvalidException, IOException {
|
public ChangeNotesState call() throws ConfigInvalidException, IOException {
|
||||||
ChangeNotesParser parser =
|
ChangeNotesParser parser =
|
||||||
new ChangeNotesParser(key.changeId(), key.id(), rw, args.noteUtil, args.metrics);
|
new ChangeNotesParser(
|
||||||
|
key.changeId(),
|
||||||
|
key.id(),
|
||||||
|
rw,
|
||||||
|
args.changeNoteJson,
|
||||||
|
args.legacyChangeNoteRead,
|
||||||
|
args.metrics);
|
||||||
ChangeNotesState result = parser.parseAll();
|
ChangeNotesState result = parser.parseAll();
|
||||||
// This assignment only happens if call() was actually called, which only
|
// This assignment only happens if call() was actually called, which only
|
||||||
// happens when Cache#get(K, Callable<V>) incurs a cache miss.
|
// happens when Cache#get(K, Callable<V>) incurs a cache miss.
|
||||||
|
@@ -124,7 +124,9 @@ class ChangeNotesParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private final members initialized in the constructor.
|
// Private final members initialized in the constructor.
|
||||||
private final ChangeNoteUtil noteUtil;
|
private final ChangeNoteJson changeNoteJson;
|
||||||
|
private final LegacyChangeNoteRead legacyChangeNoteRead;
|
||||||
|
|
||||||
private final NoteDbMetrics metrics;
|
private final NoteDbMetrics metrics;
|
||||||
private final Change.Id id;
|
private final Change.Id id;
|
||||||
private final ObjectId tip;
|
private final ObjectId tip;
|
||||||
@@ -175,12 +177,14 @@ class ChangeNotesParser {
|
|||||||
Change.Id changeId,
|
Change.Id changeId,
|
||||||
ObjectId tip,
|
ObjectId tip,
|
||||||
ChangeNotesRevWalk walk,
|
ChangeNotesRevWalk walk,
|
||||||
ChangeNoteUtil noteUtil,
|
ChangeNoteJson changeNoteJson,
|
||||||
|
LegacyChangeNoteRead legacyChangeNoteRead,
|
||||||
NoteDbMetrics metrics) {
|
NoteDbMetrics metrics) {
|
||||||
this.id = changeId;
|
this.id = changeId;
|
||||||
this.tip = tip;
|
this.tip = tip;
|
||||||
this.walk = walk;
|
this.walk = walk;
|
||||||
this.noteUtil = noteUtil;
|
this.changeNoteJson = changeNoteJson;
|
||||||
|
this.legacyChangeNoteRead = legacyChangeNoteRead;
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
approvals = new LinkedHashMap<>();
|
approvals = new LinkedHashMap<>();
|
||||||
bufferedApprovals = new ArrayList<>();
|
bufferedApprovals = new ArrayList<>();
|
||||||
@@ -446,7 +450,7 @@ class ChangeNotesParser {
|
|||||||
return effectiveAccountId;
|
return effectiveAccountId;
|
||||||
}
|
}
|
||||||
PersonIdent ident = RawParseUtils.parsePersonIdent(realUser);
|
PersonIdent ident = RawParseUtils.parsePersonIdent(realUser);
|
||||||
return noteUtil.parseIdent(ident, id);
|
return legacyChangeNoteRead.parseIdent(ident, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseTopic(ChangeNotesCommit commit) throws ConfigInvalidException {
|
private String parseTopic(ChangeNotesCommit commit) throws ConfigInvalidException {
|
||||||
@@ -581,7 +585,7 @@ class ChangeNotesParser {
|
|||||||
parsedAssignee = Optional.empty();
|
parsedAssignee = Optional.empty();
|
||||||
} else {
|
} else {
|
||||||
PersonIdent ident = RawParseUtils.parsePersonIdent(assigneeValue);
|
PersonIdent ident = RawParseUtils.parsePersonIdent(assigneeValue);
|
||||||
parsedAssignee = Optional.ofNullable(noteUtil.parseIdent(ident, id));
|
parsedAssignee = Optional.ofNullable(legacyChangeNoteRead.parseIdent(ident, id));
|
||||||
}
|
}
|
||||||
if (assignee == null) {
|
if (assignee == null) {
|
||||||
assignee = parsedAssignee;
|
assignee = parsedAssignee;
|
||||||
@@ -749,7 +753,8 @@ class ChangeNotesParser {
|
|||||||
ChangeNotesCommit tipCommit = walk.parseCommit(tip);
|
ChangeNotesCommit tipCommit = walk.parseCommit(tip);
|
||||||
revisionNoteMap =
|
revisionNoteMap =
|
||||||
RevisionNoteMap.parse(
|
RevisionNoteMap.parse(
|
||||||
noteUtil,
|
changeNoteJson,
|
||||||
|
legacyChangeNoteRead,
|
||||||
id,
|
id,
|
||||||
reader,
|
reader,
|
||||||
NoteMap.read(reader, tipCommit),
|
NoteMap.read(reader, tipCommit),
|
||||||
@@ -807,7 +812,7 @@ class ChangeNotesParser {
|
|||||||
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);
|
||||||
effectiveAccountId = noteUtil.parseIdent(ident, id);
|
effectiveAccountId = legacyChangeNoteRead.parseIdent(ident, id);
|
||||||
} else {
|
} else {
|
||||||
labelVoteStr = line;
|
labelVoteStr = line;
|
||||||
effectiveAccountId = committerId;
|
effectiveAccountId = committerId;
|
||||||
@@ -849,7 +854,7 @@ class ChangeNotesParser {
|
|||||||
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);
|
||||||
effectiveAccountId = noteUtil.parseIdent(ident, id);
|
effectiveAccountId = legacyChangeNoteRead.parseIdent(ident, id);
|
||||||
} else {
|
} else {
|
||||||
label = line.substring(1);
|
label = line.substring(1);
|
||||||
effectiveAccountId = committerId;
|
effectiveAccountId = committerId;
|
||||||
@@ -913,7 +918,7 @@ class ChangeNotesParser {
|
|||||||
label.label = line.substring(c + 2, c2);
|
label.label = line.substring(c + 2, c2);
|
||||||
PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
|
PersonIdent ident = RawParseUtils.parsePersonIdent(line.substring(c2 + 2));
|
||||||
checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
|
checkFooter(ident != null, FOOTER_SUBMITTED_WITH, line);
|
||||||
label.appliedBy = noteUtil.parseIdent(ident, id);
|
label.appliedBy = legacyChangeNoteRead.parseIdent(ident, id);
|
||||||
} else {
|
} else {
|
||||||
label.label = line.substring(c + 2);
|
label.label = line.substring(c + 2);
|
||||||
}
|
}
|
||||||
@@ -929,7 +934,7 @@ class ChangeNotesParser {
|
|||||||
if (a.getName().equals(c.getName()) && a.getEmailAddress().equals(c.getEmailAddress())) {
|
if (a.getName().equals(c.getName()) && a.getEmailAddress().equals(c.getEmailAddress())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return noteUtil.parseIdent(commit.getAuthorIdent(), id);
|
return legacyChangeNoteRead.parseIdent(commit.getAuthorIdent(), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseReviewer(Timestamp ts, ReviewerStateInternal state, String line)
|
private void parseReviewer(Timestamp ts, ReviewerStateInternal state, String line)
|
||||||
@@ -938,7 +943,7 @@ class ChangeNotesParser {
|
|||||||
if (ident == null) {
|
if (ident == null) {
|
||||||
throw invalidFooter(state.getFooterKey(), line);
|
throw invalidFooter(state.getFooterKey(), line);
|
||||||
}
|
}
|
||||||
Account.Id accountId = noteUtil.parseIdent(ident, id);
|
Account.Id accountId = legacyChangeNoteRead.parseIdent(ident, id);
|
||||||
reviewerUpdates.add(ReviewerStatusUpdate.create(ts, ownerId, accountId, state));
|
reviewerUpdates.add(ReviewerStatusUpdate.create(ts, ownerId, accountId, state));
|
||||||
if (!reviewers.containsRow(accountId)) {
|
if (!reviewers.containsRow(accountId)) {
|
||||||
reviewers.put(accountId, state, ts);
|
reviewers.put(accountId, state, ts);
|
||||||
|
@@ -37,19 +37,22 @@ class ChangeRevisionNote extends RevisionNote<Comment> {
|
|||||||
// See org.eclipse.jgit.transport.PushCertificateParser.END_SIGNATURE
|
// See org.eclipse.jgit.transport.PushCertificateParser.END_SIGNATURE
|
||||||
private static final byte[] END_SIGNATURE = "-----END PGP SIGNATURE-----\n".getBytes(UTF_8);
|
private static final byte[] END_SIGNATURE = "-----END PGP SIGNATURE-----\n".getBytes(UTF_8);
|
||||||
|
|
||||||
private final ChangeNoteUtil noteUtil;
|
private final ChangeNoteJson noteJson;
|
||||||
|
private final LegacyChangeNoteRead legacyChangeNoteRead;
|
||||||
private final Change.Id changeId;
|
private final Change.Id changeId;
|
||||||
private final PatchLineComment.Status status;
|
private final PatchLineComment.Status status;
|
||||||
private String pushCert;
|
private String pushCert;
|
||||||
|
|
||||||
ChangeRevisionNote(
|
ChangeRevisionNote(
|
||||||
ChangeNoteUtil noteUtil,
|
ChangeNoteJson noteJson,
|
||||||
|
LegacyChangeNoteRead legacyChangeNoteRead,
|
||||||
Change.Id changeId,
|
Change.Id changeId,
|
||||||
ObjectReader reader,
|
ObjectReader reader,
|
||||||
ObjectId noteId,
|
ObjectId noteId,
|
||||||
PatchLineComment.Status status) {
|
PatchLineComment.Status status) {
|
||||||
super(reader, noteId);
|
super(reader, noteId);
|
||||||
this.noteUtil = noteUtil;
|
this.legacyChangeNoteRead = legacyChangeNoteRead;
|
||||||
|
this.noteJson = noteJson;
|
||||||
this.changeId = changeId;
|
this.changeId = changeId;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
@@ -65,7 +68,7 @@ class ChangeRevisionNote extends RevisionNote<Comment> {
|
|||||||
p.value = offset;
|
p.value = offset;
|
||||||
|
|
||||||
if (isJson(raw, p.value)) {
|
if (isJson(raw, p.value)) {
|
||||||
RevisionNoteData data = parseJson(noteUtil, raw, p.value);
|
RevisionNoteData data = parseJson(noteJson, raw, p.value);
|
||||||
if (status == PatchLineComment.Status.PUBLISHED) {
|
if (status == PatchLineComment.Status.PUBLISHED) {
|
||||||
pushCert = data.pushCert;
|
pushCert = data.pushCert;
|
||||||
} else {
|
} else {
|
||||||
@@ -80,7 +83,7 @@ class ChangeRevisionNote extends RevisionNote<Comment> {
|
|||||||
} else {
|
} else {
|
||||||
pushCert = null;
|
pushCert = null;
|
||||||
}
|
}
|
||||||
List<Comment> comments = noteUtil.parseNote(raw, p, changeId);
|
List<Comment> comments = legacyChangeNoteRead.parseNote(raw, p, changeId);
|
||||||
comments.forEach(c -> c.legacyFormat = true);
|
comments.forEach(c -> c.legacyFormat = true);
|
||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
@@ -89,7 +92,7 @@ class ChangeRevisionNote extends RevisionNote<Comment> {
|
|||||||
return raw[offset] == '{' || raw[offset] == '[';
|
return raw[offset] == '{' || raw[offset] == '[';
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevisionNoteData parseJson(ChangeNoteUtil noteUtil, byte[] raw, int offset)
|
private RevisionNoteData parseJson(ChangeNoteJson noteUtil, byte[] raw, int offset)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
try (InputStream is = new ByteArrayInputStream(raw, offset, raw.length - offset);
|
try (InputStream is = new ByteArrayInputStream(raw, offset, raw.length - offset);
|
||||||
Reader r = new InputStreamReader(is, UTF_8)) {
|
Reader r = new InputStreamReader(is, UTF_8)) {
|
||||||
|
@@ -40,7 +40,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WI
|
|||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_WORK_IN_PROGRESS;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_WORK_IN_PROGRESS;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.sanitizeFooter;
|
import static com.google.gerrit.server.notedb.NoteDbUtil.sanitizeFooter;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||||
|
|
||||||
@@ -527,7 +527,13 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
|
|
||||||
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
||||||
ObjectId data =
|
ObjectId data =
|
||||||
inserter.insert(OBJ_BLOB, e.getValue().build(noteUtil, noteUtil.getWriteJson()));
|
inserter.insert(
|
||||||
|
OBJ_BLOB,
|
||||||
|
e.getValue()
|
||||||
|
.build(
|
||||||
|
noteUtil.getChangeNoteJson(),
|
||||||
|
noteUtil.getLegacyChangeNoteWrite(),
|
||||||
|
noteUtil.getChangeNoteJson().getWriteJson()));
|
||||||
rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
|
rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +561,12 @@ 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(
|
||||||
noteUtil, getId(), rw.getObjectReader(), noteMap, PatchLineComment.Status.PUBLISHED);
|
noteUtil.getChangeNoteJson(),
|
||||||
|
noteUtil.getLegacyChangeNoteRead(),
|
||||||
|
getId(),
|
||||||
|
rw.getObjectReader(),
|
||||||
|
noteMap,
|
||||||
|
PatchLineComment.Status.PUBLISHED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkComments(
|
private void checkComments(
|
||||||
@@ -738,7 +749,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (readOnlyUntil != null) {
|
if (readOnlyUntil != null) {
|
||||||
addFooter(msg, FOOTER_READ_ONLY_UNTIL, ChangeNoteUtil.formatTime(serverIdent, readOnlyUntil));
|
addFooter(msg, FOOTER_READ_ONLY_UNTIL, NoteDbUtil.formatTime(serverIdent, readOnlyUntil));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPrivate != null) {
|
if (isPrivate != null) {
|
||||||
|
@@ -133,9 +133,14 @@ public class DeleteCommentRewriter implements NoteDbRewriter {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static Map<String, Comment> getPublishedComments(
|
public static Map<String, Comment> getPublishedComments(
|
||||||
ChangeNoteUtil noteUtil, Change.Id changeId, ObjectReader reader, NoteMap noteMap)
|
ChangeNoteJson changeNoteJson,
|
||||||
|
LegacyChangeNoteRead legacyChangeNoteRead,
|
||||||
|
Change.Id changeId,
|
||||||
|
ObjectReader reader,
|
||||||
|
NoteMap noteMap)
|
||||||
throws IOException, ConfigInvalidException {
|
throws IOException, ConfigInvalidException {
|
||||||
return RevisionNoteMap.parse(noteUtil, changeId, reader, noteMap, PUBLISHED)
|
return RevisionNoteMap.parse(
|
||||||
|
changeNoteJson, legacyChangeNoteRead, changeId, reader, noteMap, PUBLISHED)
|
||||||
.revisionNotes
|
.revisionNotes
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.stream()
|
||||||
@@ -143,6 +148,16 @@ public class DeleteCommentRewriter implements NoteDbRewriter {
|
|||||||
.collect(toMap(c -> c.key.uuid, Function.identity()));
|
.collect(toMap(c -> c.key.uuid, Function.identity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Map<String, Comment> getPublishedComments(
|
||||||
|
ChangeNoteUtil noteUtil, Change.Id changeId, ObjectReader reader, NoteMap noteMap)
|
||||||
|
throws IOException, ConfigInvalidException {
|
||||||
|
return getPublishedComments(
|
||||||
|
noteUtil.getChangeNoteJson(),
|
||||||
|
noteUtil.getLegacyChangeNoteRead(),
|
||||||
|
changeId,
|
||||||
|
reader,
|
||||||
|
noteMap);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Gets the comments put in by the current commit. The message of the target comment will be
|
* Gets the comments put in by the current commit. The message of the target comment will be
|
||||||
* replaced by the new message.
|
* replaced by the new message.
|
||||||
@@ -205,7 +220,12 @@ public class DeleteCommentRewriter implements NoteDbRewriter {
|
|||||||
throws IOException, ConfigInvalidException {
|
throws IOException, ConfigInvalidException {
|
||||||
RevisionNoteMap<ChangeRevisionNote> revNotesMap =
|
RevisionNoteMap<ChangeRevisionNote> revNotesMap =
|
||||||
RevisionNoteMap.parse(
|
RevisionNoteMap.parse(
|
||||||
noteUtil, changeId, reader, NoteMap.read(reader, parentCommit), PUBLISHED);
|
noteUtil.getChangeNoteJson(),
|
||||||
|
noteUtil.getLegacyChangeNoteRead(),
|
||||||
|
changeId,
|
||||||
|
reader,
|
||||||
|
NoteMap.read(reader, parentCommit),
|
||||||
|
PUBLISHED);
|
||||||
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(revNotesMap);
|
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(revNotesMap);
|
||||||
|
|
||||||
for (Comment c : putInComments) {
|
for (Comment c : putInComments) {
|
||||||
@@ -219,7 +239,13 @@ public class DeleteCommentRewriter implements NoteDbRewriter {
|
|||||||
Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
|
Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
|
||||||
for (Map.Entry<RevId, RevisionNoteBuilder> entry : builders.entrySet()) {
|
for (Map.Entry<RevId, RevisionNoteBuilder> entry : builders.entrySet()) {
|
||||||
ObjectId objectId = ObjectId.fromString(entry.getKey().get());
|
ObjectId objectId = ObjectId.fromString(entry.getKey().get());
|
||||||
byte[] data = entry.getValue().build(noteUtil, noteUtil.getWriteJson());
|
byte[] data =
|
||||||
|
entry
|
||||||
|
.getValue()
|
||||||
|
.build(
|
||||||
|
noteUtil.getChangeNoteJson(),
|
||||||
|
noteUtil.getLegacyChangeNoteWrite(),
|
||||||
|
noteUtil.getChangeNoteJson().getWriteJson());
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
revNotesMap.noteMap.remove(objectId);
|
revNotesMap.noteMap.remove(objectId);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -155,7 +155,8 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
ObjectReader reader = handle.walk().getObjectReader();
|
ObjectReader reader = handle.walk().getObjectReader();
|
||||||
revisionNoteMap =
|
revisionNoteMap =
|
||||||
RevisionNoteMap.parse(
|
RevisionNoteMap.parse(
|
||||||
args.noteUtil,
|
args.changeNoteJson,
|
||||||
|
args.legacyChangeNoteRead,
|
||||||
getChangeId(),
|
getChangeId(),
|
||||||
reader,
|
reader,
|
||||||
NoteMap.read(reader, tipCommit),
|
NoteMap.read(reader, tipCommit),
|
||||||
|
402
java/com/google/gerrit/server/notedb/LegacyChangeNoteRead.java
Normal file
402
java/com/google/gerrit/server/notedb/LegacyChangeNoteRead.java
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
// Copyright (C) 2018 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.gerrit.server.notedb.ChangeNotes.parseException;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
|
import com.google.gerrit.reviewdb.client.Comment.Key;
|
||||||
|
import com.google.gerrit.reviewdb.client.CommentRange;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.server.config.GerritServerId;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.util.GitDateParser;
|
||||||
|
import org.eclipse.jgit.util.MutableInteger;
|
||||||
|
import org.eclipse.jgit.util.QuotedString;
|
||||||
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
|
|
||||||
|
public class LegacyChangeNoteRead {
|
||||||
|
private final String serverId;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LegacyChangeNoteRead(@GerritServerId String serverId) {
|
||||||
|
this.serverId = serverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account.Id parseIdent(PersonIdent ident, Change.Id changeId)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
return NoteDbUtil.parseIdent(ident, serverId)
|
||||||
|
.orElseThrow(
|
||||||
|
() ->
|
||||||
|
parseException(
|
||||||
|
changeId,
|
||||||
|
"invalid identity, expected <id>@%s: %s",
|
||||||
|
serverId,
|
||||||
|
ident.getEmailAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean match(byte[] note, MutableInteger p, byte[] expected) {
|
||||||
|
int m = RawParseUtils.match(note, p.value, expected);
|
||||||
|
return m == p.value + expected.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Comment> parseNote(byte[] note, MutableInteger p, Change.Id changeId)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
if (p.value >= note.length) {
|
||||||
|
return ImmutableList.of();
|
||||||
|
}
|
||||||
|
Set<Key> seen = new HashSet<>();
|
||||||
|
List<Comment> result = new ArrayList<>();
|
||||||
|
int sizeOfNote = note.length;
|
||||||
|
byte[] psb = ChangeNoteUtil.PATCH_SET.getBytes(UTF_8);
|
||||||
|
byte[] bpsb = ChangeNoteUtil.BASE_PATCH_SET.getBytes(UTF_8);
|
||||||
|
byte[] bpn = ChangeNoteUtil.PARENT_NUMBER.getBytes(UTF_8);
|
||||||
|
|
||||||
|
RevId revId = new RevId(parseStringField(note, p, changeId, ChangeNoteUtil.REVISION));
|
||||||
|
String fileName = null;
|
||||||
|
PatchSet.Id psId = null;
|
||||||
|
boolean isForBase = false;
|
||||||
|
Integer parentNumber = null;
|
||||||
|
|
||||||
|
while (p.value < sizeOfNote) {
|
||||||
|
boolean matchPs = match(note, p, psb);
|
||||||
|
boolean matchBase = match(note, p, bpsb);
|
||||||
|
if (matchPs) {
|
||||||
|
fileName = null;
|
||||||
|
psId = parsePsId(note, p, changeId, ChangeNoteUtil.PATCH_SET);
|
||||||
|
isForBase = false;
|
||||||
|
} else if (matchBase) {
|
||||||
|
fileName = null;
|
||||||
|
psId = parsePsId(note, p, changeId, ChangeNoteUtil.BASE_PATCH_SET);
|
||||||
|
isForBase = true;
|
||||||
|
if (match(note, p, bpn)) {
|
||||||
|
parentNumber = parseParentNumber(note, p, changeId);
|
||||||
|
}
|
||||||
|
} else if (psId == null) {
|
||||||
|
throw parseException(
|
||||||
|
changeId,
|
||||||
|
"missing %s or %s header",
|
||||||
|
ChangeNoteUtil.PATCH_SET,
|
||||||
|
ChangeNoteUtil.BASE_PATCH_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
Comment c = parseComment(note, p, fileName, psId, revId, isForBase, parentNumber);
|
||||||
|
fileName = c.key.filename;
|
||||||
|
if (!seen.add(c.key)) {
|
||||||
|
throw parseException(changeId, "multiple comments for %s in note", c.key);
|
||||||
|
}
|
||||||
|
result.add(c);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comment parseComment(
|
||||||
|
byte[] note,
|
||||||
|
MutableInteger curr,
|
||||||
|
String currentFileName,
|
||||||
|
PatchSet.Id psId,
|
||||||
|
RevId revId,
|
||||||
|
boolean isForBase,
|
||||||
|
Integer parentNumber)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
Change.Id changeId = psId.getParentKey();
|
||||||
|
|
||||||
|
// Check if there is a new file.
|
||||||
|
boolean newFile =
|
||||||
|
(RawParseUtils.match(note, curr.value, ChangeNoteUtil.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", ChangeNoteUtil.FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommentRange range = parseCommentRange(note, curr);
|
||||||
|
if (range == null) {
|
||||||
|
throw parseException(changeId, "could not parse %s", ChangeNoteUtil.COMMENT_RANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Timestamp commentTime = parseTimestamp(note, curr, changeId);
|
||||||
|
Account.Id aId = parseAuthor(note, curr, changeId, ChangeNoteUtil.AUTHOR);
|
||||||
|
boolean hasRealAuthor =
|
||||||
|
(RawParseUtils.match(note, curr.value, ChangeNoteUtil.REAL_AUTHOR.getBytes(UTF_8))) != -1;
|
||||||
|
Account.Id raId = null;
|
||||||
|
if (hasRealAuthor) {
|
||||||
|
raId = parseAuthor(note, curr, changeId, ChangeNoteUtil.REAL_AUTHOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasParent =
|
||||||
|
(RawParseUtils.match(note, curr.value, ChangeNoteUtil.PARENT.getBytes(UTF_8))) != -1;
|
||||||
|
String parentUUID = null;
|
||||||
|
boolean unresolved = false;
|
||||||
|
if (hasParent) {
|
||||||
|
parentUUID = parseStringField(note, curr, changeId, ChangeNoteUtil.PARENT);
|
||||||
|
}
|
||||||
|
boolean hasUnresolved =
|
||||||
|
(RawParseUtils.match(note, curr.value, ChangeNoteUtil.UNRESOLVED.getBytes(UTF_8))) != -1;
|
||||||
|
if (hasUnresolved) {
|
||||||
|
unresolved = parseBooleanField(note, curr, changeId, ChangeNoteUtil.UNRESOLVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
String uuid = parseStringField(note, curr, changeId, ChangeNoteUtil.UUID);
|
||||||
|
|
||||||
|
boolean hasTag =
|
||||||
|
(RawParseUtils.match(note, curr.value, ChangeNoteUtil.TAG.getBytes(UTF_8))) != -1;
|
||||||
|
String tag = null;
|
||||||
|
if (hasTag) {
|
||||||
|
tag = parseStringField(note, curr, changeId, ChangeNoteUtil.TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
int commentLength = parseCommentLength(note, curr, changeId);
|
||||||
|
|
||||||
|
String message = RawParseUtils.decode(UTF_8, note, curr.value, curr.value + commentLength);
|
||||||
|
checkResult(message, "message contents", changeId);
|
||||||
|
|
||||||
|
Comment c =
|
||||||
|
new Comment(
|
||||||
|
new Comment.Key(uuid, currentFileName, psId.get()),
|
||||||
|
aId,
|
||||||
|
commentTime,
|
||||||
|
isForBase ? (short) (parentNumber == null ? 0 : -parentNumber) : (short) 1,
|
||||||
|
message,
|
||||||
|
serverId,
|
||||||
|
unresolved);
|
||||||
|
c.lineNbr = range.getEndLine();
|
||||||
|
c.parentUuid = parentUUID;
|
||||||
|
c.tag = tag;
|
||||||
|
c.setRevId(revId);
|
||||||
|
if (raId != null) {
|
||||||
|
c.setRealAuthor(raId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range.getStartCharacter() != -1) {
|
||||||
|
c.setRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
curr.value = RawParseUtils.nextLF(note, curr.value + commentLength);
|
||||||
|
curr.value = RawParseUtils.nextLF(note, curr.value);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 last = ptr.value;
|
||||||
|
int startLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
||||||
|
if (ptr.value == last) {
|
||||||
|
return null;
|
||||||
|
} else 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
last = ptr.value;
|
||||||
|
int startChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
||||||
|
if (ptr.value == last) {
|
||||||
|
return null;
|
||||||
|
} else if (note[ptr.value] == '-') {
|
||||||
|
range.setStartCharacter(startChar);
|
||||||
|
ptr.value += 1;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
last = ptr.value;
|
||||||
|
int endLine = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
||||||
|
if (ptr.value == last) {
|
||||||
|
return null;
|
||||||
|
} else if (note[ptr.value] == ':') {
|
||||||
|
range.setEndLine(endLine);
|
||||||
|
ptr.value += 1;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
last = ptr.value;
|
||||||
|
int endChar = RawParseUtils.parseBase10(note, ptr.value, ptr);
|
||||||
|
if (ptr.value == last) {
|
||||||
|
return null;
|
||||||
|
} else 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 Integer parseParentNumber(byte[] note, MutableInteger curr, Change.Id changeId)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
checkHeaderLineFormat(note, curr, ChangeNoteUtil.PARENT_NUMBER, changeId);
|
||||||
|
|
||||||
|
int start = RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
|
||||||
|
MutableInteger i = new MutableInteger();
|
||||||
|
int parentNumber = RawParseUtils.parseBase10(note, start, i);
|
||||||
|
int endOfLine = RawParseUtils.nextLF(note, curr.value);
|
||||||
|
if (i.value != endOfLine - 1) {
|
||||||
|
throw parseException(changeId, "could not parse %s", ChangeNoteUtil.PARENT_NUMBER);
|
||||||
|
}
|
||||||
|
checkResult(parentNumber, "parent number", changeId);
|
||||||
|
curr.value = endOfLine;
|
||||||
|
return Integer.valueOf(parentNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parseFilename(byte[] note, MutableInteger curr, Change.Id changeId)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
checkHeaderLineFormat(note, curr, ChangeNoteUtil.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, String fieldName)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
checkHeaderLineFormat(note, curr, fieldName, 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, fieldName, changeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseCommentLength(byte[] note, MutableInteger curr, Change.Id changeId)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
checkHeaderLineFormat(note, curr, ChangeNoteUtil.LENGTH, changeId);
|
||||||
|
int startOfLength = RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
|
||||||
|
MutableInteger i = new MutableInteger();
|
||||||
|
i.value = startOfLength;
|
||||||
|
int commentLength = RawParseUtils.parseBase10(note, startOfLength, i);
|
||||||
|
if (i.value == startOfLength) {
|
||||||
|
throw parseException(changeId, "could not parse %s", ChangeNoteUtil.LENGTH);
|
||||||
|
}
|
||||||
|
int endOfLine = RawParseUtils.nextLF(note, curr.value);
|
||||||
|
if (i.value != endOfLine - 1) {
|
||||||
|
throw parseException(changeId, "could not parse %s", ChangeNoteUtil.LENGTH);
|
||||||
|
}
|
||||||
|
curr.value = endOfLine;
|
||||||
|
return checkResult(commentLength, "comment length", changeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseBooleanField(
|
||||||
|
byte[] note, MutableInteger curr, Change.Id changeId, String fieldName)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
String str = parseStringField(note, curr, changeId, fieldName);
|
||||||
|
if ("true".equalsIgnoreCase(str)) {
|
||||||
|
return true;
|
||||||
|
} else if ("false".equalsIgnoreCase(str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw parseException(changeId, "invalid boolean for %s: %s", fieldName, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
Normal file
206
java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
// Copyright (C) 2018 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.CommentsUtil.COMMENT_ORDER;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.Comment;
|
||||||
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
|
import com.google.gerrit.server.account.AccountCache;
|
||||||
|
import com.google.gerrit.server.account.AccountState;
|
||||||
|
import com.google.gerrit.server.config.GerritServerId;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.util.QuotedString;
|
||||||
|
|
||||||
|
public class LegacyChangeNoteWrite {
|
||||||
|
|
||||||
|
private final AccountCache accountCache;
|
||||||
|
private final PersonIdent serverIdent;
|
||||||
|
private final String serverId;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LegacyChangeNoteWrite(
|
||||||
|
AccountCache accountCache,
|
||||||
|
@GerritPersonIdent PersonIdent serverIdent,
|
||||||
|
@GerritServerId String serverId) {
|
||||||
|
this.accountCache = accountCache;
|
||||||
|
this.serverIdent = serverIdent;
|
||||||
|
this.serverId = serverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PersonIdent newIdent(Account.Id authorId, Date when, PersonIdent serverIdent) {
|
||||||
|
Optional<Account> author = accountCache.get(authorId).map(AccountState::getAccount);
|
||||||
|
return new PersonIdent(
|
||||||
|
author.map(Account::getName).orElseGet(() -> Account.getName(authorId)),
|
||||||
|
authorId.get() + "@" + serverId,
|
||||||
|
when,
|
||||||
|
serverIdent.getTimeZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public PersonIdent newIdent(Account author, Date when, PersonIdent serverIdent) {
|
||||||
|
return new PersonIdent(
|
||||||
|
author.getName(), author.getId().get() + "@" + serverId, when, serverIdent.getTimeZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerId() {
|
||||||
|
return serverId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendHeaderField(PrintWriter writer, String field, String value) {
|
||||||
|
writer.print(field);
|
||||||
|
writer.print(": ");
|
||||||
|
writer.print(value);
|
||||||
|
writer.print('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a note that contains the metadata for and the contents of all of the comments in the
|
||||||
|
* given comments.
|
||||||
|
*
|
||||||
|
* @param comments Comments to be written to the output stream, keyed by patch set ID; multiple
|
||||||
|
* patch sets are allowed since base revisions may be shared across patch sets. All of the
|
||||||
|
* comments must share the same RevId, and all the comments for a given patch set must have
|
||||||
|
* the same side.
|
||||||
|
* @param out output stream to write to.
|
||||||
|
*/
|
||||||
|
void buildNote(ListMultimap<Integer, Comment> comments, OutputStream out) {
|
||||||
|
if (comments.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> psIds = new ArrayList<>(comments.keySet());
|
||||||
|
Collections.sort(psIds);
|
||||||
|
|
||||||
|
OutputStreamWriter streamWriter = new OutputStreamWriter(out, UTF_8);
|
||||||
|
try (PrintWriter writer = new PrintWriter(streamWriter)) {
|
||||||
|
String revId = comments.values().iterator().next().revId;
|
||||||
|
appendHeaderField(writer, ChangeNoteUtil.REVISION, revId);
|
||||||
|
|
||||||
|
for (int psId : psIds) {
|
||||||
|
List<Comment> psComments = COMMENT_ORDER.sortedCopy(comments.get(psId));
|
||||||
|
Comment first = psComments.get(0);
|
||||||
|
|
||||||
|
short side = first.side;
|
||||||
|
appendHeaderField(
|
||||||
|
writer,
|
||||||
|
side <= 0 ? ChangeNoteUtil.BASE_PATCH_SET : ChangeNoteUtil.PATCH_SET,
|
||||||
|
Integer.toString(psId));
|
||||||
|
if (side < 0) {
|
||||||
|
appendHeaderField(writer, ChangeNoteUtil.PARENT_NUMBER, Integer.toString(-side));
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentFilename = null;
|
||||||
|
|
||||||
|
for (Comment c : psComments) {
|
||||||
|
checkArgument(
|
||||||
|
revId.equals(c.revId),
|
||||||
|
"All comments being added must have all the same RevId. The "
|
||||||
|
+ "comment below does not have the same RevId as the others "
|
||||||
|
+ "(%s).\n%s",
|
||||||
|
revId,
|
||||||
|
c);
|
||||||
|
checkArgument(
|
||||||
|
side == c.side,
|
||||||
|
"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);
|
||||||
|
String commentFilename = QuotedString.GIT_PATH.quote(c.key.filename);
|
||||||
|
|
||||||
|
if (!commentFilename.equals(currentFilename)) {
|
||||||
|
currentFilename = commentFilename;
|
||||||
|
writer.print("File: ");
|
||||||
|
writer.print(commentFilename);
|
||||||
|
writer.print("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
appendOneComment(writer, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendOneComment(PrintWriter writer, Comment c) {
|
||||||
|
// The CommentRange field for a comment is allowed to be null. If it is
|
||||||
|
// 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.
|
||||||
|
Comment.Range range = c.range;
|
||||||
|
if (range != null) {
|
||||||
|
writer.print(range.startLine);
|
||||||
|
writer.print(':');
|
||||||
|
writer.print(range.startChar);
|
||||||
|
writer.print('-');
|
||||||
|
writer.print(range.endLine);
|
||||||
|
writer.print(':');
|
||||||
|
writer.print(range.endChar);
|
||||||
|
} else {
|
||||||
|
writer.print(c.lineNbr);
|
||||||
|
}
|
||||||
|
writer.print("\n");
|
||||||
|
|
||||||
|
writer.print(NoteDbUtil.formatTime(serverIdent, c.writtenOn));
|
||||||
|
writer.print("\n");
|
||||||
|
|
||||||
|
appendIdent(writer, ChangeNoteUtil.AUTHOR, c.author.getId(), c.writtenOn);
|
||||||
|
if (!c.getRealAuthor().equals(c.author)) {
|
||||||
|
appendIdent(writer, ChangeNoteUtil.REAL_AUTHOR, c.getRealAuthor().getId(), c.writtenOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
String parent = c.parentUuid;
|
||||||
|
if (parent != null) {
|
||||||
|
appendHeaderField(writer, ChangeNoteUtil.PARENT, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
appendHeaderField(writer, ChangeNoteUtil.UNRESOLVED, Boolean.toString(c.unresolved));
|
||||||
|
appendHeaderField(writer, ChangeNoteUtil.UUID, c.key.uuid);
|
||||||
|
|
||||||
|
if (c.tag != null) {
|
||||||
|
appendHeaderField(writer, ChangeNoteUtil.TAG, c.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] messageBytes = c.message.getBytes(UTF_8);
|
||||||
|
appendHeaderField(writer, ChangeNoteUtil.LENGTH, Integer.toString(messageBytes.length));
|
||||||
|
|
||||||
|
writer.print(c.message);
|
||||||
|
writer.print("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendIdent(PrintWriter writer, String header, Account.Id id, Timestamp ts) {
|
||||||
|
PersonIdent ident = newIdent(id, ts, serverIdent);
|
||||||
|
StringBuilder name = new StringBuilder();
|
||||||
|
PersonIdent.appendSanitized(name, ident.getName());
|
||||||
|
name.append(" <");
|
||||||
|
PersonIdent.appendSanitized(name, ident.getEmailAddress());
|
||||||
|
name.append('>');
|
||||||
|
appendHeaderField(writer, header, name.toString());
|
||||||
|
}
|
||||||
|
}
|
@@ -14,12 +14,21 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.notedb;
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
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 java.sql.Timestamp;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.util.GitDateFormatter;
|
||||||
|
import org.eclipse.jgit.util.GitDateFormatter.Format;
|
||||||
|
|
||||||
public class NoteDbUtil {
|
public class NoteDbUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an AccountId for the given email address. Returns empty if the address isn't on this
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
public static Optional<Account.Id> parseIdent(PersonIdent ident, String serverId) {
|
public static Optional<Account.Id> parseIdent(PersonIdent ident, String serverId) {
|
||||||
String email = ident.getEmailAddress();
|
String email = ident.getEmailAddress();
|
||||||
int at = email.indexOf('@');
|
int at = email.indexOf('@');
|
||||||
@@ -36,4 +45,24 @@ public class NoteDbUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private NoteDbUtil() {}
|
private NoteDbUtil() {}
|
||||||
|
|
||||||
|
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 static final CharMatcher INVALID_FOOTER_CHARS = CharMatcher.anyOf("\r\n\0");
|
||||||
|
|
||||||
|
static String sanitizeFooter(String value) {
|
||||||
|
// Remove characters that would confuse JGit's footer parser if they were
|
||||||
|
// included in footer values, for example by splitting the footer block into
|
||||||
|
// multiple paragraphs.
|
||||||
|
//
|
||||||
|
// One painful example: RevCommit#getShorMessage() might return a message
|
||||||
|
// containing "\r\r", which RevCommit#getFooterLines() will treat as an
|
||||||
|
// empty paragraph for the purposes of footer parsing.
|
||||||
|
return INVALID_FOOTER_CHARS.trimAndCollapseFrom(value, ' ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -83,11 +83,17 @@ class RevisionNoteBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] build(ChangeNoteUtil noteUtil, boolean writeJson) throws IOException {
|
public byte[] build(ChangeNoteUtil noteUtil, boolean writeJson) throws IOException {
|
||||||
|
return build(noteUtil.getChangeNoteJson(), noteUtil.getLegacyChangeNoteWrite(), writeJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] build(
|
||||||
|
ChangeNoteJson changeNoteJson, LegacyChangeNoteWrite legacyChangeNoteWrite, boolean writeJson)
|
||||||
|
throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
if (writeJson) {
|
if (writeJson) {
|
||||||
buildNoteJson(noteUtil, out);
|
buildNoteJson(changeNoteJson, out);
|
||||||
} else {
|
} else {
|
||||||
buildNoteLegacy(noteUtil, out);
|
buildNoteLegacy(legacyChangeNoteWrite, out);
|
||||||
}
|
}
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
@@ -122,7 +128,7 @@ class RevisionNoteBuilder {
|
|||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildNoteJson(ChangeNoteUtil noteUtil, OutputStream out) throws IOException {
|
private void buildNoteJson(ChangeNoteJson noteUtil, OutputStream out) throws IOException {
|
||||||
ListMultimap<Integer, Comment> comments = buildCommentMap();
|
ListMultimap<Integer, Comment> comments = buildCommentMap();
|
||||||
if (comments.isEmpty() && pushCert == null) {
|
if (comments.isEmpty() && pushCert == null) {
|
||||||
return;
|
return;
|
||||||
@@ -137,7 +143,8 @@ class RevisionNoteBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildNoteLegacy(ChangeNoteUtil noteUtil, OutputStream out) throws IOException {
|
private void buildNoteLegacy(LegacyChangeNoteWrite noteUtil, OutputStream out)
|
||||||
|
throws IOException {
|
||||||
if (pushCert != null) {
|
if (pushCert != null) {
|
||||||
byte[] certBytes = pushCert.getBytes(UTF_8);
|
byte[] certBytes = pushCert.getBytes(UTF_8);
|
||||||
out.write(certBytes, 0, trimTrailingNewlines(certBytes));
|
out.write(certBytes, 0, trimTrailingNewlines(certBytes));
|
||||||
|
@@ -32,7 +32,8 @@ class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
|
|||||||
final ImmutableMap<RevId, T> revisionNotes;
|
final ImmutableMap<RevId, T> revisionNotes;
|
||||||
|
|
||||||
static RevisionNoteMap<ChangeRevisionNote> parse(
|
static RevisionNoteMap<ChangeRevisionNote> parse(
|
||||||
ChangeNoteUtil noteUtil,
|
ChangeNoteJson noteJson,
|
||||||
|
LegacyChangeNoteRead legacyChangeNoteRead,
|
||||||
Change.Id changeId,
|
Change.Id changeId,
|
||||||
ObjectReader reader,
|
ObjectReader reader,
|
||||||
NoteMap noteMap,
|
NoteMap noteMap,
|
||||||
@@ -41,7 +42,8 @@ class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
|
|||||||
Map<RevId, ChangeRevisionNote> result = new HashMap<>();
|
Map<RevId, ChangeRevisionNote> result = new HashMap<>();
|
||||||
for (Note note : noteMap) {
|
for (Note note : noteMap) {
|
||||||
ChangeRevisionNote rn =
|
ChangeRevisionNote rn =
|
||||||
new ChangeRevisionNote(noteUtil, changeId, reader, note.getData(), status);
|
new ChangeRevisionNote(
|
||||||
|
noteJson, legacyChangeNoteRead, changeId, reader, note.getData(), status);
|
||||||
rn.parse();
|
rn.parse();
|
||||||
result.put(new RevId(note.name()), rn);
|
result.put(new RevId(note.name()), rn);
|
||||||
}
|
}
|
||||||
@@ -49,12 +51,12 @@ class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static RevisionNoteMap<RobotCommentsRevisionNote> parseRobotComments(
|
static RevisionNoteMap<RobotCommentsRevisionNote> parseRobotComments(
|
||||||
ChangeNoteUtil noteUtil, ObjectReader reader, NoteMap noteMap)
|
ChangeNoteJson changeNoteJson, ObjectReader reader, NoteMap noteMap)
|
||||||
throws ConfigInvalidException, IOException {
|
throws ConfigInvalidException, IOException {
|
||||||
Map<RevId, RobotCommentsRevisionNote> result = new HashMap<>();
|
Map<RevId, RobotCommentsRevisionNote> result = new HashMap<>();
|
||||||
for (Note note : noteMap) {
|
for (Note note : noteMap) {
|
||||||
RobotCommentsRevisionNote rn =
|
RobotCommentsRevisionNote rn =
|
||||||
new RobotCommentsRevisionNote(noteUtil, reader, note.getData());
|
new RobotCommentsRevisionNote(changeNoteJson, reader, note.getData());
|
||||||
rn.parse();
|
rn.parse();
|
||||||
result.put(new RevId(note.name()), rn);
|
result.put(new RevId(note.name()), rn);
|
||||||
}
|
}
|
||||||
|
@@ -89,7 +89,8 @@ public class RobotCommentNotes extends AbstractChangeNotes<RobotCommentNotes> {
|
|||||||
RevCommit tipCommit = handle.walk().parseCommit(metaId);
|
RevCommit tipCommit = handle.walk().parseCommit(metaId);
|
||||||
ObjectReader reader = handle.walk().getObjectReader();
|
ObjectReader reader = handle.walk().getObjectReader();
|
||||||
revisionNoteMap =
|
revisionNoteMap =
|
||||||
RevisionNoteMap.parseRobotComments(args.noteUtil, reader, NoteMap.read(reader, tipCommit));
|
RevisionNoteMap.parseRobotComments(
|
||||||
|
args.changeNoteJson, reader, NoteMap.read(reader, tipCommit));
|
||||||
ListMultimap<RevId, RobotComment> cs = MultimapBuilder.hashKeys().arrayListValues().build();
|
ListMultimap<RevId, RobotComment> cs = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
for (RobotCommentsRevisionNote rn : revisionNoteMap.revisionNotes.values()) {
|
for (RobotCommentsRevisionNote rn : revisionNoteMap.revisionNotes.values()) {
|
||||||
for (RobotComment c : rn.getComments()) {
|
for (RobotComment c : rn.getComments()) {
|
||||||
|
@@ -202,7 +202,8 @@ public class RobotCommentUpdate 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.parseRobotComments(noteUtil, rw.getObjectReader(), noteMap);
|
return RevisionNoteMap.parseRobotComments(
|
||||||
|
noteUtil.getChangeNoteJson(), rw.getObjectReader(), noteMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -27,9 +27,9 @@ import org.eclipse.jgit.lib.ObjectId;
|
|||||||
import org.eclipse.jgit.lib.ObjectReader;
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
|
||||||
public class RobotCommentsRevisionNote extends RevisionNote<RobotComment> {
|
public class RobotCommentsRevisionNote extends RevisionNote<RobotComment> {
|
||||||
private final ChangeNoteUtil noteUtil;
|
private final ChangeNoteJson noteUtil;
|
||||||
|
|
||||||
RobotCommentsRevisionNote(ChangeNoteUtil noteUtil, ObjectReader reader, ObjectId noteId) {
|
RobotCommentsRevisionNote(ChangeNoteJson noteUtil, ObjectReader reader, ObjectId noteId) {
|
||||||
super(reader, noteId);
|
super(reader, noteId);
|
||||||
this.noteUtil = noteUtil;
|
this.noteUtil = noteUtil;
|
||||||
}
|
}
|
||||||
|
@@ -546,7 +546,7 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
|
|||||||
if (id == null) {
|
if (id == null) {
|
||||||
return new PersonIdent(serverIdent, events.getWhen());
|
return new PersonIdent(serverIdent, events.getWhen());
|
||||||
}
|
}
|
||||||
return changeNoteUtil.newIdent(id, events.getWhen(), serverIdent);
|
return changeNoteUtil.getLegacyChangeNoteWrite().newIdent(id, events.getWhen(), serverIdent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HashtagsEvent> getHashtagsEvents(Change change, NoteDbUpdateManager manager)
|
private List<HashtagsEvent> getHashtagsEvents(Change change, NoteDbUpdateManager manager)
|
||||||
@@ -564,7 +564,10 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
|
|||||||
for (RevCommit commit : rw) {
|
for (RevCommit commit : rw) {
|
||||||
Account.Id authorId;
|
Account.Id authorId;
|
||||||
try {
|
try {
|
||||||
authorId = changeNoteUtil.parseIdent(commit.getAuthorIdent(), change.getId());
|
authorId =
|
||||||
|
changeNoteUtil
|
||||||
|
.getLegacyChangeNoteRead()
|
||||||
|
.parseIdent(commit.getAuthorIdent(), change.getId());
|
||||||
} catch (ConfigInvalidException e) {
|
} catch (ConfigInvalidException e) {
|
||||||
continue; // Corrupt data, no valid hashtags in this commit.
|
continue; // Corrupt data, no valid hashtags in this commit.
|
||||||
}
|
}
|
||||||
|
@@ -2676,7 +2676,9 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
assertThat(commitPatchSetCreation.getShortMessage()).isEqualTo("Create patch set 2");
|
assertThat(commitPatchSetCreation.getShortMessage()).isEqualTo("Create patch set 2");
|
||||||
PersonIdent expectedAuthor =
|
PersonIdent expectedAuthor =
|
||||||
changeNoteUtil.newIdent(getAccount(admin.id), c.updated, serverIdent.get());
|
changeNoteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(getAccount(admin.id), c.updated, serverIdent.get());
|
||||||
assertThat(commitPatchSetCreation.getAuthorIdent()).isEqualTo(expectedAuthor);
|
assertThat(commitPatchSetCreation.getAuthorIdent()).isEqualTo(expectedAuthor);
|
||||||
assertThat(commitPatchSetCreation.getCommitterIdent())
|
assertThat(commitPatchSetCreation.getCommitterIdent())
|
||||||
.isEqualTo(new PersonIdent(serverIdent.get(), c.updated));
|
.isEqualTo(new PersonIdent(serverIdent.get(), c.updated));
|
||||||
@@ -2684,7 +2686,10 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
|
|
||||||
RevCommit commitChangeCreation = rw.parseCommit(commitPatchSetCreation.getParent(0));
|
RevCommit commitChangeCreation = rw.parseCommit(commitPatchSetCreation.getParent(0));
|
||||||
assertThat(commitChangeCreation.getShortMessage()).isEqualTo("Create change");
|
assertThat(commitChangeCreation.getShortMessage()).isEqualTo("Create change");
|
||||||
expectedAuthor = changeNoteUtil.newIdent(getAccount(admin.id), c.created, serverIdent.get());
|
expectedAuthor =
|
||||||
|
changeNoteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(getAccount(admin.id), c.created, serverIdent.get());
|
||||||
assertThat(commitChangeCreation.getAuthorIdent()).isEqualTo(expectedAuthor);
|
assertThat(commitChangeCreation.getAuthorIdent()).isEqualTo(expectedAuthor);
|
||||||
assertThat(commitChangeCreation.getCommitterIdent())
|
assertThat(commitChangeCreation.getCommitterIdent())
|
||||||
.isEqualTo(new PersonIdent(serverIdent.get(), c.created));
|
.isEqualTo(new PersonIdent(serverIdent.get(), c.created));
|
||||||
|
@@ -433,7 +433,9 @@ public class RefAdvertisementIT extends AbstractDaemonTest {
|
|||||||
if (notesMigration.commitChangeWrites()) {
|
if (notesMigration.commitChangeWrites()) {
|
||||||
PersonIdent committer = serverIdent.get();
|
PersonIdent committer = serverIdent.get();
|
||||||
PersonIdent author =
|
PersonIdent author =
|
||||||
noteUtil.newIdent(getAccount(admin.getId()), committer.getWhen(), committer);
|
noteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(getAccount(admin.getId()), committer.getWhen(), committer);
|
||||||
tr.branch(RefNames.changeMetaRef(c3.getId()))
|
tr.branch(RefNames.changeMetaRef(c3.getId()))
|
||||||
.commit()
|
.commit()
|
||||||
.author(author)
|
.author(author)
|
||||||
|
@@ -273,7 +273,9 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
assertThat(commit.getShortMessage()).isEqualTo("Create change");
|
assertThat(commit.getShortMessage()).isEqualTo("Create change");
|
||||||
|
|
||||||
PersonIdent expectedAuthor =
|
PersonIdent expectedAuthor =
|
||||||
changeNoteUtil.newIdent(getAccount(admin.id), c.created, serverIdent.get());
|
changeNoteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(getAccount(admin.id), c.created, serverIdent.get());
|
||||||
assertThat(commit.getAuthorIdent()).isEqualTo(expectedAuthor);
|
assertThat(commit.getAuthorIdent()).isEqualTo(expectedAuthor);
|
||||||
|
|
||||||
assertThat(commit.getCommitterIdent())
|
assertThat(commit.getCommitterIdent())
|
||||||
|
@@ -950,7 +950,7 @@ public class CommentsIT extends AbstractDaemonTest {
|
|||||||
@Test
|
@Test
|
||||||
public void jsonCommentHasLegacyFormatFalse() throws Exception {
|
public void jsonCommentHasLegacyFormatFalse() throws Exception {
|
||||||
assume().that(notesMigration.readChanges()).isTrue();
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
assertThat(noteUtil.getWriteJson()).isTrue();
|
assertThat(noteUtil.getChangeNoteJson().getWriteJson()).isTrue();
|
||||||
|
|
||||||
PushOneCommit.Result result = createChange();
|
PushOneCommit.Result result = createChange();
|
||||||
Change.Id changeId = result.getChange().getId();
|
Change.Id changeId = result.getChange().getId();
|
||||||
|
@@ -902,7 +902,9 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
PersonIdent committer = serverIdent.get();
|
PersonIdent committer = serverIdent.get();
|
||||||
PersonIdent author =
|
PersonIdent author =
|
||||||
noteUtil.newIdent(getAccount(admin.getId()), committer.getWhen(), committer);
|
noteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(getAccount(admin.getId()), committer.getWhen(), committer);
|
||||||
serverSideTestRepo
|
serverSideTestRepo
|
||||||
.branch(RefNames.changeMetaRef(id))
|
.branch(RefNames.changeMetaRef(id))
|
||||||
.commit()
|
.commit()
|
||||||
|
@@ -53,7 +53,7 @@ public class LegacyCommentsIT extends AbstractDaemonTest {
|
|||||||
@Test
|
@Test
|
||||||
public void legacyCommentHasLegacyFormatTrue() throws Exception {
|
public void legacyCommentHasLegacyFormatTrue() throws Exception {
|
||||||
assume().that(notesMigration.readChanges()).isTrue();
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
assertThat(noteUtil.getWriteJson()).isFalse();
|
assertThat(noteUtil.getChangeNoteJson().getWriteJson()).isFalse();
|
||||||
|
|
||||||
PushOneCommit.Result result = createChange();
|
PushOneCommit.Result result = createChange();
|
||||||
Change.Id changeId = result.getChange().getId();
|
Change.Id changeId = result.getChange().getId();
|
||||||
|
@@ -17,8 +17,8 @@ package com.google.gerrit.acceptance.server.notedb;
|
|||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.common.truth.Truth8.assertThat;
|
import static com.google.common.truth.Truth8.assertThat;
|
||||||
import static com.google.common.truth.TruthJUnit.assume;
|
import static com.google.common.truth.TruthJUnit.assume;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.formatTime;
|
|
||||||
import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.REVIEW_DB;
|
import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.REVIEW_DB;
|
||||||
|
import static com.google.gerrit.server.notedb.NoteDbUtil.formatTime;
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
import static java.util.concurrent.TimeUnit.DAYS;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
@@ -498,7 +498,11 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
|
|||||||
private RevCommit writeCommit(String body) throws Exception {
|
private RevCommit writeCommit(String body) throws Exception {
|
||||||
ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class);
|
ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class);
|
||||||
return writeCommit(
|
return writeCommit(
|
||||||
body, noteUtil.newIdent(changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent), false);
|
body,
|
||||||
|
noteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent),
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit writeCommit(String body, PersonIdent author) throws Exception {
|
private RevCommit writeCommit(String body, PersonIdent author) throws Exception {
|
||||||
@@ -509,7 +513,9 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
|
|||||||
ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class);
|
ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class);
|
||||||
return writeCommit(
|
return writeCommit(
|
||||||
body,
|
body,
|
||||||
noteUtil.newIdent(changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent),
|
noteUtil
|
||||||
|
.getLegacyChangeNoteWrite()
|
||||||
|
.newIdent(changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent),
|
||||||
initWorkInProgress);
|
initWorkInProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +561,9 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
|
|||||||
|
|
||||||
private ChangeNotesParser newParser(ObjectId tip) throws Exception {
|
private ChangeNotesParser newParser(ObjectId tip) throws Exception {
|
||||||
walk.reset();
|
walk.reset();
|
||||||
ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class);
|
ChangeNoteJson changeNoteJson = injector.getInstance(ChangeNoteJson.class);
|
||||||
return new ChangeNotesParser(newChange().getId(), tip, walk, noteUtil, args.metrics);
|
LegacyChangeNoteRead reader = injector.getInstance(LegacyChangeNoteRead.class);
|
||||||
|
return new ChangeNotesParser(
|
||||||
|
newChange().getId(), tip, walk, changeNoteJson, reader, args.metrics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -77,6 +77,8 @@ import org.junit.Test;
|
|||||||
public class ChangeNotesTest extends AbstractChangeNotesTest {
|
public class ChangeNotesTest extends AbstractChangeNotesTest {
|
||||||
@Inject private DraftCommentNotes.Factory draftNotesFactory;
|
@Inject private DraftCommentNotes.Factory draftNotesFactory;
|
||||||
|
|
||||||
|
@Inject private ChangeNoteJson changeNoteJson;
|
||||||
|
@Inject private LegacyChangeNoteRead legacyChangeNoteRead;
|
||||||
@Inject private ChangeNoteUtil noteUtil;
|
@Inject private ChangeNoteUtil noteUtil;
|
||||||
|
|
||||||
@Inject private @GerritServerId String serverId;
|
@Inject private @GerritServerId String serverId;
|
||||||
@@ -1195,7 +1197,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"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, ts)
|
+ NoteDbUtil.formatTime(serverIdent, ts)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Change Owner <1@gerrit>\n"
|
+ "Author: Change Owner <1@gerrit>\n"
|
||||||
+ "Unresolved: false\n"
|
+ "Unresolved: false\n"
|
||||||
@@ -1288,7 +1290,13 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
|
|
||||||
try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
|
try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
|
||||||
ChangeNotesParser notesWithComments =
|
ChangeNotesParser notesWithComments =
|
||||||
new ChangeNotesParser(c.getId(), commitWithComments.copy(), rw, noteUtil, args.metrics);
|
new ChangeNotesParser(
|
||||||
|
c.getId(),
|
||||||
|
commitWithComments.copy(),
|
||||||
|
rw,
|
||||||
|
changeNoteJson,
|
||||||
|
legacyChangeNoteRead,
|
||||||
|
args.metrics);
|
||||||
ChangeNotesState state = notesWithComments.parseAll();
|
ChangeNotesState state = notesWithComments.parseAll();
|
||||||
assertThat(state.approvals()).isEmpty();
|
assertThat(state.approvals()).isEmpty();
|
||||||
assertThat(state.publishedComments()).hasSize(1);
|
assertThat(state.publishedComments()).hasSize(1);
|
||||||
@@ -1296,7 +1304,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
|
|
||||||
try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
|
try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
|
||||||
ChangeNotesParser notesWithApprovals =
|
ChangeNotesParser notesWithApprovals =
|
||||||
new ChangeNotesParser(c.getId(), commitWithApprovals.copy(), rw, noteUtil, args.metrics);
|
new ChangeNotesParser(
|
||||||
|
c.getId(),
|
||||||
|
commitWithApprovals.copy(),
|
||||||
|
rw,
|
||||||
|
changeNoteJson,
|
||||||
|
legacyChangeNoteRead,
|
||||||
|
args.metrics);
|
||||||
|
|
||||||
ChangeNotesState state = notesWithApprovals.parseAll();
|
ChangeNotesState state = notesWithApprovals.parseAll();
|
||||||
assertThat(state.approvals()).hasSize(1);
|
assertThat(state.approvals()).hasSize(1);
|
||||||
assertThat(state.publishedComments()).hasSize(1);
|
assertThat(state.publishedComments()).hasSize(1);
|
||||||
@@ -1666,7 +1681,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "File: file1\n"
|
+ "File: file1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "1:1-2:1\n"
|
+ "1:1-2:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time1)
|
+ NoteDbUtil.formatTime(serverIdent, time1)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Unresolved: false\n"
|
+ "Unresolved: false\n"
|
||||||
@@ -1675,7 +1690,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "comment 1\n"
|
+ "comment 1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "2:1-3:1\n"
|
+ "2:1-3:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time2)
|
+ NoteDbUtil.formatTime(serverIdent, time2)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Unresolved: false\n"
|
+ "Unresolved: false\n"
|
||||||
@@ -1686,7 +1701,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "File: file2\n"
|
+ "File: file2\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "3:0-4:1\n"
|
+ "3:0-4:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time3)
|
+ NoteDbUtil.formatTime(serverIdent, time3)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Unresolved: false\n"
|
+ "Unresolved: false\n"
|
||||||
@@ -1766,7 +1781,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "File: file1\n"
|
+ "File: file1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "1:1-2:1\n"
|
+ "1:1-2:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time1)
|
+ NoteDbUtil.formatTime(serverIdent, time1)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Unresolved: false\n"
|
+ "Unresolved: false\n"
|
||||||
@@ -1775,7 +1790,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "comment 1\n"
|
+ "comment 1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "2:1-3:1\n"
|
+ "2:1-3:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time2)
|
+ NoteDbUtil.formatTime(serverIdent, time2)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Unresolved: false\n"
|
+ "Unresolved: false\n"
|
||||||
@@ -1854,7 +1869,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "File: file1\n"
|
+ "File: file1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "1:1-2:1\n"
|
+ "1:1-2:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time1)
|
+ NoteDbUtil.formatTime(serverIdent, time1)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Unresolved: false\n"
|
+ "Unresolved: false\n"
|
||||||
@@ -1863,7 +1878,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "comment 1\n"
|
+ "comment 1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "1:1-2:1\n"
|
+ "1:1-2:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time2)
|
+ NoteDbUtil.formatTime(serverIdent, time2)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Parent: uuid1\n"
|
+ "Parent: uuid1\n"
|
||||||
@@ -1951,7 +1966,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
|
|
||||||
byte[] bytes = walk.getObjectReader().open(note.getData(), Constants.OBJ_BLOB).getBytes();
|
byte[] bytes = walk.getObjectReader().open(note.getData(), Constants.OBJ_BLOB).getBytes();
|
||||||
String noteString = new String(bytes, UTF_8);
|
String noteString = new String(bytes, UTF_8);
|
||||||
String timeStr = ChangeNoteUtil.formatTime(serverIdent, time);
|
String timeStr = NoteDbUtil.formatTime(serverIdent, time);
|
||||||
|
|
||||||
if (!testJson()) {
|
if (!testJson()) {
|
||||||
assertThat(noteString)
|
assertThat(noteString)
|
||||||
@@ -2048,7 +2063,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
+ "File: file\n"
|
+ "File: file\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "1:1-2:1\n"
|
+ "1:1-2:1\n"
|
||||||
+ ChangeNoteUtil.formatTime(serverIdent, time)
|
+ NoteDbUtil.formatTime(serverIdent, time)
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Author: Other Account <2@gerrit>\n"
|
+ "Author: Other Account <2@gerrit>\n"
|
||||||
+ "Real-author: Change Owner <1@gerrit>\n"
|
+ "Real-author: Change Owner <1@gerrit>\n"
|
||||||
@@ -2103,7 +2118,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
|
|
||||||
byte[] bytes = walk.getObjectReader().open(note.getData(), Constants.OBJ_BLOB).getBytes();
|
byte[] bytes = walk.getObjectReader().open(note.getData(), Constants.OBJ_BLOB).getBytes();
|
||||||
String noteString = new String(bytes, UTF_8);
|
String noteString = new String(bytes, UTF_8);
|
||||||
String timeStr = ChangeNoteUtil.formatTime(serverIdent, time);
|
String timeStr = NoteDbUtil.formatTime(serverIdent, time);
|
||||||
|
|
||||||
if (!testJson()) {
|
if (!testJson()) {
|
||||||
assertThat(noteString)
|
assertThat(noteString)
|
||||||
@@ -3511,7 +3526,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean testJson() {
|
private boolean testJson() {
|
||||||
return noteUtil.getWriteJson();
|
return noteUtil.getChangeNoteJson().getWriteJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception {
|
private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception {
|
||||||
|
@@ -68,7 +68,7 @@ public class CommentTimestampAdapterTest {
|
|||||||
|
|
||||||
// Match ChangeNoteUtil#gson as of 4e1f02db913d91f2988f559048e513e6093a1bce
|
// Match ChangeNoteUtil#gson as of 4e1f02db913d91f2988f559048e513e6093a1bce
|
||||||
legacyGson = new GsonBuilder().setPrettyPrinting().create();
|
legacyGson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
gson = ChangeNoteUtil.newGson();
|
gson = ChangeNoteJson.newGson();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
Reference in New Issue
Block a user