Files
gerrit/java/com/google/gerrit/server/CommentsUtil.java
Edwin Kempin 371a8612d7 Remove unused PatchLineComment class
This class represented comments in ReviewDb. Since ReviewDb is gone,
this class is no longer needed. Only PatchLineComment.Status is still
needed. Move this enum into the Comment class.

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: Ibd14562de9622df9dd816350ea2e5d0f77dc0172
2019-10-17 10:12:08 +02:00

354 lines
12 KiB
Java

// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Comment;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.update.ChangeContext;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
/** Utility functions to manipulate Comments. */
@Singleton
public class CommentsUtil {
public static final Ordering<Comment> COMMENT_ORDER =
new Ordering<Comment>() {
@Override
public int compare(Comment c1, Comment c2) {
return ComparisonChain.start()
.compare(c1.key.filename, c2.key.filename)
.compare(c1.key.patchSetId, c2.key.patchSetId)
.compare(c1.side, c2.side)
.compare(c1.lineNbr, c2.lineNbr)
.compare(c1.writtenOn, c2.writtenOn)
.result();
}
};
public static final Ordering<CommentInfo> COMMENT_INFO_ORDER =
new Ordering<CommentInfo>() {
@Override
public int compare(CommentInfo a, CommentInfo b) {
return ComparisonChain.start()
.compare(a.path, b.path, NULLS_FIRST)
.compare(a.patchSet, b.patchSet, NULLS_FIRST)
.compare(side(a), side(b))
.compare(a.line, b.line, NULLS_FIRST)
.compare(a.inReplyTo, b.inReplyTo, NULLS_FIRST)
.compare(a.message, b.message)
.compare(a.id, b.id)
.result();
}
private int side(CommentInfo c) {
return firstNonNull(c.side, Side.REVISION).ordinal();
}
};
public static PatchSet.Id getCommentPsId(Change.Id changeId, Comment comment) {
return PatchSet.id(changeId, comment.key.patchSetId);
}
public static String extractMessageId(@Nullable String tag) {
if (tag == null || !tag.startsWith("mailMessageId=")) {
return null;
}
return tag.substring("mailMessageId=".length());
}
private static final Ordering<Comparable<?>> NULLS_FIRST = Ordering.natural().nullsFirst();
private final GitRepositoryManager repoManager;
private final AllUsersName allUsers;
private final String serverId;
@Inject
CommentsUtil(
GitRepositoryManager repoManager, AllUsersName allUsers, @GerritServerId String serverId) {
this.repoManager = repoManager;
this.allUsers = allUsers;
this.serverId = serverId;
}
public Comment newComment(
ChangeContext ctx,
String path,
PatchSet.Id psId,
short side,
String message,
@Nullable Boolean unresolved,
@Nullable String parentUuid)
throws UnprocessableEntityException {
if (unresolved == null) {
if (parentUuid == null) {
// Default to false if comment is not descended from another.
unresolved = false;
} else {
// Inherit unresolved value from inReplyTo comment if not specified.
Comment.Key key = new Comment.Key(parentUuid, path, psId.get());
Optional<Comment> parent = getPublished(ctx.getNotes(), key);
if (!parent.isPresent()) {
throw new UnprocessableEntityException("Invalid parentUuid supplied for comment");
}
unresolved = parent.get().unresolved;
}
}
Comment c =
new Comment(
new Comment.Key(ChangeUtil.messageUuid(), path, psId.get()),
ctx.getUser().getAccountId(),
ctx.getWhen(),
side,
message,
serverId,
unresolved);
c.parentUuid = parentUuid;
ctx.getUser().updateRealAccountId(c::setRealAuthor);
return c;
}
public RobotComment newRobotComment(
ChangeContext ctx,
String path,
PatchSet.Id psId,
short side,
String message,
String robotId,
String robotRunId) {
RobotComment c =
new RobotComment(
new Comment.Key(ChangeUtil.messageUuid(), path, psId.get()),
ctx.getUser().getAccountId(),
ctx.getWhen(),
side,
message,
serverId,
robotId,
robotRunId);
ctx.getUser().updateRealAccountId(c::setRealAuthor);
return c;
}
public Optional<Comment> getPublished(ChangeNotes notes, Comment.Key key) {
return publishedByChange(notes).stream().filter(c -> key.equals(c.key)).findFirst();
}
public Optional<Comment> getDraft(ChangeNotes notes, IdentifiedUser user, Comment.Key key) {
return draftByChangeAuthor(notes, user.getAccountId()).stream()
.filter(c -> key.equals(c.key))
.findFirst();
}
public List<Comment> publishedByChange(ChangeNotes notes) {
notes.load();
return sort(Lists.newArrayList(notes.getComments().values()));
}
public List<RobotComment> robotCommentsByChange(ChangeNotes notes) {
notes.load();
return sort(Lists.newArrayList(notes.getRobotComments().values()));
}
public List<Comment> draftByChange(ChangeNotes notes) {
List<Comment> comments = new ArrayList<>();
for (Ref ref : getDraftRefs(notes.getChangeId())) {
Account.Id account = Account.Id.fromRefSuffix(ref.getName());
if (account != null) {
comments.addAll(draftByChangeAuthor(notes, account));
}
}
return sort(comments);
}
public List<Comment> byPatchSet(ChangeNotes notes, PatchSet.Id psId) {
List<Comment> comments = new ArrayList<>();
comments.addAll(publishedByPatchSet(notes, psId));
for (Ref ref : getDraftRefs(notes.getChangeId())) {
Account.Id account = Account.Id.fromRefSuffix(ref.getName());
if (account != null) {
comments.addAll(draftByPatchSetAuthor(psId, account, notes));
}
}
return sort(comments);
}
public List<Comment> publishedByChangeFile(ChangeNotes notes, String file) {
return commentsOnFile(notes.load().getComments().values(), file);
}
public List<Comment> publishedByPatchSet(ChangeNotes notes, PatchSet.Id psId) {
return removeCommentsOnAncestorOfCommitMessage(
commentsOnPatchSet(notes.load().getComments().values(), psId));
}
public List<RobotComment> robotCommentsByPatchSet(ChangeNotes notes, PatchSet.Id psId) {
return commentsOnPatchSet(notes.load().getRobotComments().values(), psId);
}
/**
* For the commit message the A side in a diff view is always empty when a comparison against an
* ancestor is done, so there can't be any comments on this ancestor. However earlier we showed
* the auto-merge commit message on side A when for a merge commit a comparison against the
* auto-merge was done. From that time there may still be comments on the auto-merge commit
* message and those we want to filter out.
*/
private List<Comment> removeCommentsOnAncestorOfCommitMessage(List<Comment> list) {
return list.stream()
.filter(c -> c.side != 0 || !Patch.COMMIT_MSG.equals(c.key.filename))
.collect(toList());
}
public List<Comment> draftByPatchSetAuthor(
PatchSet.Id psId, Account.Id author, ChangeNotes notes) {
return commentsOnPatchSet(notes.load().getDraftComments(author).values(), psId);
}
public List<Comment> draftByChangeFileAuthor(ChangeNotes notes, String file, Account.Id author) {
return commentsOnFile(notes.load().getDraftComments(author).values(), file);
}
public List<Comment> draftByChangeAuthor(ChangeNotes notes, Account.Id author) {
List<Comment> comments = new ArrayList<>();
comments.addAll(notes.getDraftComments(author).values());
return sort(comments);
}
public void putComments(ChangeUpdate update, Comment.Status status, Iterable<Comment> comments) {
for (Comment c : comments) {
update.putComment(status, c);
}
}
public void putRobotComments(ChangeUpdate update, Iterable<RobotComment> comments) {
for (RobotComment c : comments) {
update.putRobotComment(c);
}
}
public void deleteComments(ChangeUpdate update, Iterable<Comment> comments) {
for (Comment c : comments) {
update.deleteComment(c);
}
}
public void deleteCommentByRewritingHistory(
ChangeUpdate update, Comment.Key commentKey, String newMessage) {
update.deleteCommentByRewritingHistory(commentKey.uuid, newMessage);
}
private static List<Comment> commentsOnFile(Collection<Comment> allComments, String file) {
List<Comment> result = new ArrayList<>(allComments.size());
for (Comment c : allComments) {
String currentFilename = c.key.filename;
if (currentFilename.equals(file)) {
result.add(c);
}
}
return sort(result);
}
private static <T extends Comment> List<T> commentsOnPatchSet(
Collection<T> allComments, PatchSet.Id psId) {
List<T> result = new ArrayList<>(allComments.size());
for (T c : allComments) {
if (c.key.patchSetId == psId.get()) {
result.add(c);
}
}
return sort(result);
}
public static void setCommentCommitId(Comment c, PatchListCache cache, Change change, PatchSet ps)
throws PatchListNotAvailableException {
checkArgument(
c.key.patchSetId == ps.id().get(),
"cannot set commit ID for patch set %s on comment %s",
ps.id(),
c);
if (c.getCommitId() == null) {
if (Side.fromShort(c.side) == Side.PARENT) {
if (c.side < 0) {
c.setCommitId(cache.getOldId(change, ps, -c.side));
} else {
c.setCommitId(cache.getOldId(change, ps, null));
}
} else {
c.setCommitId(ps.commitId());
}
}
}
/**
* Get NoteDb draft refs for a change.
*
* <p>Works if NoteDb is not enabled, but the results are not meaningful.
*
* <p>This is just a simple ref scan, so the results may potentially include refs for zombie draft
* comments. A zombie draft is one which has been published but the write to delete the draft ref
* from All-Users failed.
*
* @param changeId change ID.
* @return raw refs from All-Users repo.
*/
public Collection<Ref> getDraftRefs(Change.Id changeId) {
try (Repository repo = repoManager.openRepository(allUsers)) {
return getDraftRefs(repo, changeId);
} catch (IOException e) {
throw new StorageException(e);
}
}
private Collection<Ref> getDraftRefs(Repository repo, Change.Id changeId) throws IOException {
return repo.getRefDatabase().getRefsByPrefix(RefNames.refsDraftCommentsPrefix(changeId));
}
private static <T extends Comment> List<T> sort(List<T> comments) {
comments.sort(COMMENT_ORDER);
return comments;
}
}