| // 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.common.collect.ImmutableList.toImmutableList; |
| 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.ImmutableList; |
| import com.google.common.collect.ListMultimap; |
| import com.google.gerrit.common.UsedAt; |
| 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.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.Date; |
| import java.util.List; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.util.QuotedString; |
| |
| public class LegacyChangeNoteWrite { |
| |
| private final PersonIdent serverIdent; |
| private final String serverId; |
| |
| @Inject |
| public LegacyChangeNoteWrite( |
| @GerritPersonIdent PersonIdent serverIdent, @GerritServerId String serverId) { |
| this.serverIdent = serverIdent; |
| this.serverId = serverId; |
| } |
| |
| public PersonIdent newIdent(Account.Id authorId, Date when, PersonIdent serverIdent) { |
| return new PersonIdent( |
| authorId.toString(), authorId.get() + "@" + serverId, when, serverIdent.getTimeZone()); |
| } |
| |
| @VisibleForTesting |
| public PersonIdent newIdent(Account author, Date when, PersonIdent serverIdent) { |
| return new PersonIdent( |
| author.toString(), 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. |
| */ |
| @UsedAt(UsedAt.Project.GOOGLE) |
| public void buildNote(ListMultimap<Integer, Comment> comments, OutputStream out) { |
| if (comments.isEmpty()) { |
| return; |
| } |
| |
| ImmutableList<Integer> psIds = comments.keySet().stream().sorted().collect(toImmutableList()); |
| |
| 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()); |
| } |
| } |