All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.CommentsUtil Maven / Gradle / Ivy

There is a newer version: 3.10.0-rc4
Show newest version
// 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 com.google.gerrit.reviewdb.client.PatchLineComment.Status.PUBLISHED;
import static java.util.stream.Collectors.toList;

import com.google.common.collect.ComparisonChain;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
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.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RobotComment;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
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.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.update.BatchUpdateReviewDb;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
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.Collections;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;

/**
 * Utility functions to manipulate Comments.
 *
 * 

These methods either query for and update Comments in the NoteDb or ReviewDb, depending on the * state of the NotesMigration. */ @Singleton public class CommentsUtil { public static final Ordering COMMENT_ORDER = new Ordering() { @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 COMMENT_INFO_ORDER = new Ordering() { @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 new 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> NULLS_FIRST = Ordering.natural().nullsFirst(); private final GitRepositoryManager repoManager; private final AllUsersName allUsers; private final NotesMigration migration; private final String serverId; @Inject CommentsUtil( GitRepositoryManager repoManager, AllUsersName allUsers, NotesMigration migration, @GerritServerId String serverId) { this.repoManager = repoManager; this.allUsers = allUsers; this.migration = migration; this.serverId = serverId; } public Comment newComment( ChangeContext ctx, String path, PatchSet.Id psId, short side, String message, @Nullable Boolean unresolved, @Nullable String parentUuid) throws OrmException, 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.patchSetId); Optional parent = getPublished(ctx.getDb(), 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 getPublished(ReviewDb db, ChangeNotes notes, Comment.Key key) throws OrmException { if (!migration.readChanges()) { return getReviewDb(db, notes, key); } return publishedByChange(db, notes).stream().filter(c -> key.equals(c.key)).findFirst(); } public Optional getDraft( ReviewDb db, ChangeNotes notes, IdentifiedUser user, Comment.Key key) throws OrmException { if (!migration.readChanges()) { Optional c = getReviewDb(db, notes, key); if (c.isPresent() && !c.get().author.getId().equals(user.getAccountId())) { throw new OrmException( String.format( "Expected draft %s to belong to account %s, but it belongs to %s", key, user.getAccountId(), c.get().author.getId())); } return c; } return draftByChangeAuthor(db, notes, user.getAccountId()) .stream() .filter(c -> key.equals(c.key)) .findFirst(); } private Optional getReviewDb(ReviewDb db, ChangeNotes notes, Comment.Key key) throws OrmException { return Optional.ofNullable( db.patchComments().get(PatchLineComment.Key.from(notes.getChangeId(), key))) .map(plc -> plc.asComment(serverId)); } public List publishedByChange(ReviewDb db, ChangeNotes notes) throws OrmException { if (!migration.readChanges()) { return sort(byCommentStatus(db.patchComments().byChange(notes.getChangeId()), PUBLISHED)); } notes.load(); return sort(Lists.newArrayList(notes.getComments().values())); } public List robotCommentsByChange(ChangeNotes notes) throws OrmException { if (!migration.readChanges()) { return ImmutableList.of(); } notes.load(); return sort(Lists.newArrayList(notes.getRobotComments().values())); } public List draftByChange(ReviewDb db, ChangeNotes notes) throws OrmException { if (!migration.readChanges()) { return sort(byCommentStatus(db.patchComments().byChange(notes.getChangeId()), Status.DRAFT)); } List comments = new ArrayList<>(); for (Ref ref : getDraftRefs(notes.getChangeId())) { Account.Id account = Account.Id.fromRefSuffix(ref.getName()); if (account != null) { comments.addAll(draftByChangeAuthor(db, notes, account)); } } return sort(comments); } private List byCommentStatus( ResultSet comments, PatchLineComment.Status status) { return toComments( serverId, Lists.newArrayList(Iterables.filter(comments, c -> c.getStatus() == status))); } public List byPatchSet(ReviewDb db, ChangeNotes notes, PatchSet.Id psId) throws OrmException { if (!migration.readChanges()) { return sort(toComments(serverId, db.patchComments().byPatchSet(psId).toList())); } List comments = new ArrayList<>(); comments.addAll(publishedByPatchSet(db, notes, psId)); for (Ref ref : getDraftRefs(notes.getChangeId())) { Account.Id account = Account.Id.fromRefSuffix(ref.getName()); if (account != null) { comments.addAll(draftByPatchSetAuthor(db, psId, account, notes)); } } return sort(comments); } public List publishedByChangeFile( ReviewDb db, ChangeNotes notes, Change.Id changeId, String file) throws OrmException { if (!migration.readChanges()) { return sort( toComments(serverId, db.patchComments().publishedByChangeFile(changeId, file).toList())); } return commentsOnFile(notes.load().getComments().values(), file); } public List publishedByPatchSet(ReviewDb db, ChangeNotes notes, PatchSet.Id psId) throws OrmException { if (!migration.readChanges()) { return removeCommentsOnAncestorOfCommitMessage( sort(toComments(serverId, db.patchComments().publishedByPatchSet(psId).toList()))); } return removeCommentsOnAncestorOfCommitMessage( commentsOnPatchSet(notes.load().getComments().values(), psId)); } public List robotCommentsByPatchSet(ChangeNotes notes, PatchSet.Id psId) throws OrmException { if (!migration.readChanges()) { return ImmutableList.of(); } 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 removeCommentsOnAncestorOfCommitMessage(List list) { return list.stream() .filter(c -> c.side != 0 || !Patch.COMMIT_MSG.equals(c.key.filename)) .collect(toList()); } public List draftByPatchSetAuthor( ReviewDb db, PatchSet.Id psId, Account.Id author, ChangeNotes notes) throws OrmException { if (!migration.readChanges()) { return sort( toComments(serverId, db.patchComments().draftByPatchSetAuthor(psId, author).toList())); } return commentsOnPatchSet(notes.load().getDraftComments(author).values(), psId); } public List draftByChangeFileAuthor( ReviewDb db, ChangeNotes notes, String file, Account.Id author) throws OrmException { if (!migration.readChanges()) { return sort( toComments( serverId, db.patchComments() .draftByChangeFileAuthor(notes.getChangeId(), file, author) .toList())); } return commentsOnFile(notes.load().getDraftComments(author).values(), file); } public List draftByChangeAuthor(ReviewDb db, ChangeNotes notes, Account.Id author) throws OrmException { if (!migration.readChanges()) { return Streams.stream(db.patchComments().draftByAuthor(author)) .filter(c -> c.getPatchSetId().getParentKey().equals(notes.getChangeId())) .map(plc -> plc.asComment(serverId)) .sorted(COMMENT_ORDER) .collect(toList()); } List comments = new ArrayList<>(); comments.addAll(notes.getDraftComments(author).values()); return sort(comments); } public void putComments( ReviewDb db, ChangeUpdate update, PatchLineComment.Status status, Iterable comments) throws OrmException { for (Comment c : comments) { update.putComment(status, c); } db.patchComments().upsert(toPatchLineComments(update.getId(), status, comments)); } public void putRobotComments(ChangeUpdate update, Iterable comments) { for (RobotComment c : comments) { update.putRobotComment(c); } } public void deleteComments(ReviewDb db, ChangeUpdate update, Iterable comments) throws OrmException { for (Comment c : comments) { update.deleteComment(c); } db.patchComments() .delete(toPatchLineComments(update.getId(), PatchLineComment.Status.DRAFT, comments)); } public void deleteCommentByRewritingHistory( ReviewDb db, ChangeUpdate update, Comment.Key commentKey, PatchSet.Id psId, String newMessage) throws OrmException { if (PrimaryStorage.of(update.getChange()).equals(PrimaryStorage.REVIEW_DB)) { PatchLineComment.Key key = new PatchLineComment.Key(new Patch.Key(psId, commentKey.filename), commentKey.uuid); if (db instanceof BatchUpdateReviewDb) { db = ((BatchUpdateReviewDb) db).unsafeGetDelegate(); } db = ReviewDbUtil.unwrapDb(db); PatchLineComment patchLineComment = db.patchComments().get(key); if (!patchLineComment.getStatus().equals(PUBLISHED)) { throw new OrmException(String.format("comment %s is not published", key)); } patchLineComment.setMessage(newMessage); db.patchComments().upsert(Collections.singleton(patchLineComment)); } update.deleteCommentByRewritingHistory(commentKey.uuid, newMessage); } public void deleteAllDraftsFromAllUsers(Change.Id changeId) throws IOException { try (Repository repo = repoManager.openRepository(allUsers); RevWalk rw = new RevWalk(repo)) { BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate(); for (Ref ref : getDraftRefs(repo, changeId)) { bru.addCommand(new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName())); } bru.setRefLogMessage("Delete drafts from NoteDb", false); bru.execute(rw, NullProgressMonitor.INSTANCE); for (ReceiveCommand cmd : bru.getCommands()) { if (cmd.getResult() != ReceiveCommand.Result.OK) { throw new IOException( String.format( "Failed to delete draft comment ref %s at %s: %s (%s)", cmd.getRefName(), cmd.getOldId(), cmd.getResult(), cmd.getMessage())); } } } } private static List commentsOnFile(Collection allComments, String file) { List 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 List commentsOnPatchSet( Collection allComments, PatchSet.Id psId) { List result = new ArrayList<>(allComments.size()); for (T c : allComments) { if (c.key.patchSetId == psId.get()) { result.add(c); } } return sort(result); } public static void setCommentRevId(Comment c, PatchListCache cache, Change change, PatchSet ps) throws PatchListNotAvailableException { checkArgument( c.key.patchSetId == ps.getId().get(), "cannot set RevId for patch set %s on comment %s", ps.getId(), c); if (c.revId == null) { if (Side.fromShort(c.side) == Side.PARENT) { if (c.side < 0) { c.revId = ObjectId.toString(cache.getOldId(change, ps, -c.side)); } else { c.revId = ObjectId.toString(cache.getOldId(change, ps, null)); } } else { c.revId = ps.getRevision().get(); } } } /** * Get NoteDb draft refs for a change. * *

Works if NoteDb is not enabled, but the results are not meaningful. * *

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 getDraftRefs(Change.Id changeId) throws OrmException { try (Repository repo = repoManager.openRepository(allUsers)) { return getDraftRefs(repo, changeId); } catch (IOException e) { throw new OrmException(e); } } private Collection getDraftRefs(Repository repo, Change.Id changeId) throws IOException { return repo.getRefDatabase().getRefsByPrefix(RefNames.refsDraftCommentsPrefix(changeId)); } private static List sort(List comments) { comments.sort(COMMENT_ORDER); return comments; } public static Iterable toPatchLineComments( Change.Id changeId, PatchLineComment.Status status, Iterable comments) { return FluentIterable.from(comments).transform(c -> PatchLineComment.from(changeId, status, c)); } public static List toComments( final String serverId, Iterable comments) { return COMMENT_ORDER.sortedCopy( FluentIterable.from(comments).transform(plc -> plc.asComment(serverId))); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy