Merge changes I86a8c524,Icd3b72b1,I178b0395

* changes:
  Expose the history of all reviewers ever on a change
  Move Change out of AbstractChangeNotes
  Store draft PatchLineComments in Git notes
This commit is contained in:
Dave Borowitz
2014-07-25 21:48:45 +00:00
committed by Gerrit Code Review
13 changed files with 970 additions and 61 deletions

View File

@@ -43,6 +43,8 @@ public class RefNames {
*/
public static final String REFS_CACHE_AUTOMERGE = "refs/cache-automerge/";
public static final String REFS_DRAFT_PREFIX = "comments-";
public static String refsUsers(Account.Id accountId) {
StringBuilder r = new StringBuilder();
r.append(REFS_USER);
@@ -57,6 +59,16 @@ public class RefNames {
return r.toString();
}
public static String refsDraftComments(Account.Id accountId,
Change.Id changeId) {
StringBuilder r = new StringBuilder();
r.append(refsUsers(accountId));
r.append('/');
r.append(REFS_DRAFT_PREFIX);
r.append(changeId.get());
return r.toString();
}
private RefNames() {
}
}

View File

@@ -77,6 +77,15 @@ public class PatchLineCommentsUtil {
return commentsOnPs;
}
// TODO(yyonas): Delete drafts if they already existed.
public void addPublishedComments(ReviewDb db, ChangeUpdate update,
Iterable<PatchLineComment> comments) throws OrmException {
for (PatchLineComment c : comments) {
update.upsertComment(c);
}
db.patchComments().upsert(comments);
}
private static Collection<PatchLineComment> addCommentsInFile(
Collection<PatchLineComment> commentsOnFile,
Collection<PatchLineComment> allComments,
@@ -89,12 +98,4 @@ public class PatchLineCommentsUtil {
}
return commentsOnFile;
}
public void addPublishedComments(ReviewDb db, ChangeUpdate update,
Iterable<PatchLineComment> comments) throws OrmException {
for (PatchLineComment c : comments) {
update.putComment(c);
}
db.patchComments().upsert(comments);
}
}

View File

@@ -175,6 +175,7 @@ public abstract class VersionedMetaData {
void write(CommitBuilder commit) throws IOException;
void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
RevCommit createRef(String refName) throws IOException;
void removeRef(String refName) throws IOException;
RevCommit commit() throws IOException;
RevCommit commitAt(ObjectId revision) throws IOException;
void close();
@@ -267,6 +268,24 @@ public abstract class VersionedMetaData {
}
}
@Override
public void removeRef(String refName) throws IOException {
RefUpdate ru = db.updateRef(refName);
ru.setForceUpdate(true);
if (revision != null) {
ru.setExpectedOldObjectId(revision);
}
RefUpdate.Result result = ru.delete();
switch (result) {
case FORCED:
update.fireGitRefUpdatedEvent(ru);
return;
default:
throw new IOException("Cannot delete " + ru.getName() + " in "
+ db.getDirectory() + ": " + ru.getResult());
}
}
@Override
public RevCommit commit() throws IOException {
return commitAt(revision);

View File

@@ -29,19 +29,15 @@ import java.io.IOException;
public abstract class AbstractChangeNotes<T> extends VersionedMetaData {
private boolean loaded;
protected final GitRepositoryManager repoManager;
private final Change change;
private final Change.Id changeId;
AbstractChangeNotes(GitRepositoryManager repoManager, Change change) {
AbstractChangeNotes(GitRepositoryManager repoManager, Change.Id changeId) {
this.repoManager = repoManager;
this.change = new Change(change);
this.changeId = changeId;
}
public Change.Id getChangeId() {
return change.getId();
}
public Change getChange() {
return change;
return changeId;
}
public T load() throws OrmException {

View File

@@ -59,6 +59,10 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
this.when = when;
}
public ChangeNotes getChangeNotes() {
return ctl.getNotes();
}
public Change getChange() {
return ctl.getChange();
}
@@ -119,6 +123,11 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
return null;
}
@Override
public void removeRef(String refName) {
// Do nothing.
}
@Override
public RevCommit commit() {
return null;

View File

@@ -0,0 +1,286 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import com.google.gerrit.reviewdb.client.Account;
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.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevCommit;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A single delta to apply atomically to a change.
* <p>
* This delta contains only draft comments on a single patch set of a change by
* a single author. This delta will become a single commit in the All-Users
* repository.
* <p>
* This class is not thread safe.
*/
public class ChangeDraftUpdate extends AbstractChangeUpdate {
public interface Factory {
ChangeDraftUpdate create(ChangeControl ctl, Date when);
}
private final AllUsersName draftsProject;
private final Account.Id accountId;
private final CommentsInNotesUtil commentsUtil;
private final ChangeNotes changeNotes;
private final DraftCommentNotes draftNotes;
private List<PatchLineComment> upsertComments;
private List<PatchLineComment> deleteComments;
@AssistedInject
private ChangeDraftUpdate(
@GerritPersonIdent PersonIdent serverIdent,
GitRepositoryManager repoManager,
NotesMigration migration,
MetaDataUpdate.User updateFactory,
DraftCommentNotes.Factory draftNotesFactory,
AllUsersName allUsers,
CommentsInNotesUtil commentsUtil,
@Assisted ChangeControl ctl,
@Assisted Date when) throws OrmException {
super(migration, repoManager, updateFactory, ctl, serverIdent, when);
this.draftsProject = allUsers;
this.commentsUtil = commentsUtil;
checkState(ctl.getCurrentUser().isIdentifiedUser(),
"Current user must be identified");
IdentifiedUser user = (IdentifiedUser) ctl.getCurrentUser();
this.accountId = user.getAccountId();
this.changeNotes = getChangeNotes().load();
this.draftNotes = draftNotesFactory.create(ctl.getChange().getId(),
user.getAccountId()).load();
this.upsertComments = Lists.newArrayList();
this.deleteComments = Lists.newArrayList();
}
public void insertComment(PatchLineComment c) throws OrmException {
verifyComment(c);
checkArgument(c.getStatus() == Status.DRAFT,
"Cannot insert a published comment into a ChangeDraftUpdate");
checkArgument(!changeNotes.containsComment(c),
"A comment already exists with the same key,"
+ " so the following comment cannot be inserted: %s", c);
upsertComments.add(c);
}
public void upsertComment(PatchLineComment c) {
verifyComment(c);
checkArgument(c.getStatus() == Status.DRAFT,
"Cannot upsert a published comment into a ChangeDraftUpdate");
upsertComments.add(c);
}
public void updateComment(PatchLineComment c) throws OrmException {
verifyComment(c);
checkArgument(c.getStatus() == Status.DRAFT,
"Cannot update a published comment into a ChangeDraftUpdate");
checkArgument(draftNotes.containsComment(c),
"Cannot update this comment because it didn't exist previously");
upsertComments.add(c);
}
public void deleteComment(PatchLineComment c) {
verifyComment(c);
checkArgument(draftNotes.containsComment(c), "Cannot delete this comment"
+ " because it didn't previously exist as a draft");
deleteComments.add(c);
}
/**
* Deletes a PatchLineComment from the list of drafts only if it existed
* previously as a draft. If it wasn't a draft previously, this is a no-op.
*/
public void deleteCommentIfPresent(PatchLineComment c) {
if (draftNotes.containsComment(c)) {
verifyComment(c);
deleteComments.add(c);
}
}
private void verifyComment(PatchLineComment comment) {
checkState(psId != null,
"setPatchSetId must be called first");
checkArgument(getCommentPsId(comment).equals(psId),
"Comment on %s does not match configured patch set %s",
getCommentPsId(comment), psId);
checkArgument(comment.getRevId() != null);
checkArgument(comment.getAuthor().equals(accountId),
"The author for the following comment does not match the author of"
+ " this ChangeDraftUpdate (%s): %s", accountId, comment);
}
/** @return the tree id for the updated tree */
private ObjectId storeCommentsInNotes(AtomicBoolean removedAllComments)
throws OrmException, IOException {
if (isEmpty()) {
return null;
}
NoteMap noteMap = draftNotes.getNoteMap();
if (noteMap == null) {
noteMap = NoteMap.newEmptyMap();
}
Table<PatchSet.Id, String, PatchLineComment> baseDrafts =
draftNotes.getDraftBaseComments();
Table<PatchSet.Id, String, PatchLineComment> psDrafts =
draftNotes.getDraftPsComments();
// There is no need to rewrite the note for one of the sides of the patch
// set if all of the modifications were made to the comments of one side,
// so we set these flags to potentially save that extra work.
boolean baseSideChanged = false;
boolean revisionSideChanged = false;
// We must define these RevIds so that if this update deletes all
// remaining comments on a given side, then we can remove that note.
// However, if this update doesn't delete any comments, it is okay for these
// to be null because they won't be used.
RevId baseRevId = null;
RevId psRevId = null;
for (PatchLineComment c : deleteComments) {
if (c.getSide() == (short) 0) {
baseSideChanged = true;
baseRevId = c.getRevId();
baseDrafts.remove(psId, c.getKey().get());
} else {
revisionSideChanged = true;
psRevId = c.getRevId();
psDrafts.remove(psId, c.getKey().get());
}
}
for (PatchLineComment c : upsertComments) {
if (c.getSide() == (short) 0) {
baseSideChanged = true;
baseDrafts.put(psId, c.getKey().get(), c);
} else {
revisionSideChanged = true;
psDrafts.put(psId, c.getKey().get(), c);
}
}
List<PatchLineComment> newBaseDrafts =
Lists.newArrayList(baseDrafts.row(psId).values());
List<PatchLineComment> newPsDrafts =
Lists.newArrayList(psDrafts.row(psId).values());
updateNoteMap(baseSideChanged, noteMap, newBaseDrafts,
baseRevId);
updateNoteMap(revisionSideChanged, noteMap, newPsDrafts,
psRevId);
removedAllComments.set(baseDrafts.isEmpty() && psDrafts.isEmpty());
return noteMap.writeTree(inserter);
}
private void updateNoteMap(boolean changed, NoteMap noteMap,
List<PatchLineComment> comments, RevId commitId)
throws IOException, OrmException {
if (changed) {
if (comments.isEmpty()) {
commentsUtil.removeNote(noteMap, commitId);
} else {
commentsUtil.writeCommentsToNoteMap(noteMap, comments, inserter);
}
}
}
public RevCommit commit() throws IOException {
BatchMetaDataUpdate batch = openUpdate();
try {
CommitBuilder builder = new CommitBuilder();
if (migration.write()) {
AtomicBoolean removedAllComments = new AtomicBoolean();
ObjectId treeId = storeCommentsInNotes(removedAllComments);
if (treeId != null) {
if (removedAllComments.get()) {
batch.removeRef(getRefName());
} else {
builder.setTreeId(treeId);
batch.write(builder);
}
}
}
return batch.commit();
} catch (OrmException e) {
throw new IOException(e);
} finally {
batch.close();
}
}
@Override
protected Project.NameKey getProjectName() {
return draftsProject;
}
@Override
protected String getRefName() {
return RefNames.refsDraftComments(accountId, getChange().getId());
}
@Override
protected boolean onSave(CommitBuilder commit) throws IOException,
ConfigInvalidException {
if (isEmpty()) {
return false;
}
commit.setAuthor(newIdent(getUser().getAccount(), when));
commit.setCommitter(new PersonIdent(serverIdent, when));
commit.setMessage(String.format("Comment on patch set %d", psId.get()));
return true;
}
private boolean isEmpty() {
return deleteComments.isEmpty()
&& upsertComments.isEmpty();
}
}

View File

@@ -19,6 +19,7 @@ import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_STATUS;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Enums;
@@ -49,8 +50,11 @@ import com.google.gerrit.reviewdb.client.PatchSet.Id;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.util.LabelVote;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -134,15 +138,17 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
@Singleton
public static class Factory {
private final GitRepositoryManager repoManager;
private final AllUsersNameProvider allUsersProvider;
@VisibleForTesting
@Inject
public Factory(GitRepositoryManager repoManager) {
public Factory(GitRepositoryManager repoManager, AllUsersNameProvider allUsersProvider) {
this.repoManager = repoManager;
this.allUsersProvider = allUsersProvider;
}
public ChangeNotes create(Change change) {
return new ChangeNotes(repoManager, change);
return new ChangeNotes(repoManager, allUsersProvider, change);
}
}
@@ -154,6 +160,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
private final Map<PatchSet.Id,
Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
private final Map<Account.Id, ReviewerState> reviewers;
private final List<Account.Id> allPastReviewers;
private final List<SubmitRecord> submitRecords;
private final Multimap<PatchSet.Id, ChangeMessage> changeMessages;
private final Multimap<Id, PatchLineComment> commentsForPs;
@@ -170,6 +177,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
this.repo = repoManager.openRepository(getProjectName(change));
approvals = Maps.newHashMap();
reviewers = Maps.newLinkedHashMap();
allPastReviewers = Lists.newArrayList();
submitRecords = Lists.newArrayListWithExpectedSize(1);
changeMessages = LinkedListMultimap.create();
commentsForPs = ArrayListMultimap.create();
@@ -182,6 +190,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
parse(commit);
}
parseComments();
allPastReviewers.addAll(reviewers.keySet());
pruneReviewers();
}
@@ -484,17 +493,30 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
}
}
private final Change change;
private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
private ImmutableSetMultimap<ReviewerState, Account.Id> reviewers;
private ImmutableList<Account.Id> allPastReviewers;
private ImmutableList<SubmitRecord> submitRecords;
private ImmutableListMultimap<PatchSet.Id, ChangeMessage> changeMessages;
private ImmutableListMultimap<PatchSet.Id, PatchLineComment> commentsForBase;
private ImmutableListMultimap<PatchSet.Id, PatchLineComment> commentsForPS;
NoteMap noteMap;
private final AllUsersName allUsers;
private DraftCommentNotes draftCommentNotes;
@Inject
@VisibleForTesting
public ChangeNotes(GitRepositoryManager repoManager, Change change) {
super(repoManager, change);
public ChangeNotes(GitRepositoryManager repoManager,
AllUsersNameProvider allUsersProvider, Change change) {
super(repoManager, change.getId());
this.allUsers = allUsersProvider.get();
this.change = new Change(change);
}
public Change getChange() {
return change;
}
public ImmutableListMultimap<PatchSet.Id, PatchSetApproval> getApprovals() {
@@ -505,6 +527,13 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
return reviewers;
}
/**
* @return a list of all users who have ever been a reviewer on this change.
*/
public ImmutableList<Account.Id> getAllPastReviewers() {
return allPastReviewers;
}
/**
* @return submit records stored during the most recent submit; only for
* changes that were actually submitted.
@@ -530,6 +559,55 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
return commentsForPS;
}
public Table<PatchSet.Id, String, PatchLineComment> getDraftBaseComments(
Account.Id author) throws OrmException {
loadDraftComments(author);
return draftCommentNotes.getDraftBaseComments();
}
public Table<PatchSet.Id, String, PatchLineComment> getDraftPsComments(
Account.Id author) throws OrmException {
loadDraftComments(author);
return draftCommentNotes.getDraftPsComments();
}
/**
* If draft comments have already been loaded for this author, then they will
* not be reloaded. However, this method will load the comments if no draft
* comments have been loaded or if the caller would like the drafts for
* another author.
*/
private void loadDraftComments(Account.Id author)
throws OrmException {
if (draftCommentNotes == null ||
!author.equals(draftCommentNotes.getAuthor())) {
draftCommentNotes = new DraftCommentNotes(repoManager, allUsers,
getChangeId(), author);
draftCommentNotes.load();
}
}
public boolean containsComment(PatchLineComment c) throws OrmException {
if (containsCommentPublished(c)) {
return true;
}
loadDraftComments(c.getAuthor());
return draftCommentNotes.containsComment(c);
}
public boolean containsCommentPublished(PatchLineComment c) {
PatchSet.Id psId = getCommentPsId(c);
List<PatchLineComment> list = (c.getSide() == (short) 0)
? getBaseComments().get(psId)
: getPatchSetComments().get(psId);
for (PatchLineComment l : list) {
if (c.getKey().equals(l.getKey())) {
return true;
}
}
return false;
}
/** @return the NoteMap */
NoteMap getNoteMap() {
return noteMap;
@@ -569,6 +647,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
reviewers.put(e.getValue(), e.getKey());
}
this.reviewers = reviewers.build();
this.allPastReviewers = ImmutableList.copyOf(parser.allPastReviewers);
submitRecords = ImmutableList.copyOf(parser.submitRecords);
} catch (ParseException e1) {

View File

@@ -33,9 +33,12 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AllUsersName;
import com.google.inject.Provider;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.project.ChangeControl;
@@ -60,12 +63,14 @@ import java.util.List;
import java.util.Map;
/**
* A single delta to apply atomically to a change.
* A delta to apply to a change.
* <p>
* This delta becomes a single commit on the notes branch, so there are
* limitations on the set of modifications that can be handled in a single
* update. In particular, there is a single author and timestamp for each
* update.
* This delta will become two unique commits: one in the AllUsers repo that will
* contain the draft comments on this change and one in the notes branch that
* will contain approvals, reviewers, change status, subject, submit records,
* the change message, and published comments. There are limitations on the set
* of modifications that can be handled in a single update. In particular, there
* is a single author and timestamp for each update.
* <p>
* This class is not thread-safe.
*/
@@ -88,6 +93,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
private List<PatchLineComment> commentsForBase;
private List<PatchLineComment> commentsForPs;
private String changeMessage;
private ChangeNotes notes;
private final ChangeDraftUpdate.Factory draftUpdateFactory;
private ChangeDraftUpdate draftUpdate;
@AssistedInject
private ChangeUpdate(
@@ -96,12 +105,16 @@ public class ChangeUpdate extends AbstractChangeUpdate {
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
DraftCommentNotes.Factory draftNotesFactory,
Provider<AllUsersName> allUsers,
ChangeDraftUpdate.Factory draftUpdateFactory,
ProjectCache projectCache,
IdentifiedUser user,
@Assisted ChangeControl ctl,
CommentsInNotesUtil commentsUtil) {
this(serverIdent, repoManager, migration, accountCache, updateFactory,
projectCache, ctl, serverIdent.getWhen(), commentsUtil);
draftNotesFactory, allUsers, draftUpdateFactory, projectCache, ctl,
serverIdent.getWhen(), commentsUtil);
}
@AssistedInject
@@ -111,12 +124,15 @@ public class ChangeUpdate extends AbstractChangeUpdate {
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
DraftCommentNotes.Factory draftNotesFactory,
Provider<AllUsersName> allUsers,
ChangeDraftUpdate.Factory draftUpdateFactory,
ProjectCache projectCache,
@Assisted ChangeControl ctl,
@Assisted Date when,
CommentsInNotesUtil commentsUtil) {
this(serverIdent, repoManager, migration, accountCache, updateFactory, ctl,
when,
this(serverIdent, repoManager, migration, accountCache, updateFactory,
draftNotesFactory, allUsers, draftUpdateFactory, ctl, when,
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
commentsUtil);
}
@@ -132,11 +148,15 @@ public class ChangeUpdate extends AbstractChangeUpdate {
NotesMigration migration,
AccountCache accountCache,
MetaDataUpdate.User updateFactory,
DraftCommentNotes.Factory draftNotesFactory,
Provider<AllUsersName> allUsers,
ChangeDraftUpdate.Factory draftUpdateFactory,
@Assisted ChangeControl ctl,
@Assisted Date when,
@Assisted Comparator<String> labelNameComparator,
CommentsInNotesUtil commentsUtil) {
super(migration, repoManager, updateFactory, ctl, serverIdent, when);
this.draftUpdateFactory = draftUpdateFactory;
this.accountCache = accountCache;
this.commentsUtil = commentsUtil;
this.approvals = Maps.newTreeMap(labelNameComparator);
@@ -174,20 +194,132 @@ public class ChangeUpdate extends AbstractChangeUpdate {
this.changeMessage = changeMessage;
}
public void putComment(PatchLineComment comment) {
checkArgument(psId != null,
"setPatchSetId must be called before putComment");
checkArgument(getCommentPsId(comment).equals(psId),
"Comment on %s doesn't match previous patch set %s",
getCommentPsId(comment), psId);
checkArgument(comment.getRevId() != null);
if (comment.getSide() == 0) {
commentsForBase.add(comment);
public void insertComment(PatchLineComment comment) throws OrmException {
if (comment.getStatus() == Status.DRAFT) {
insertDraftComment(comment);
} else {
commentsForPs.add(comment);
insertPublishedComment(comment);
}
}
public void upsertComment(PatchLineComment comment) throws OrmException {
if (comment.getStatus() == Status.DRAFT) {
upsertDraftComment(comment);
} else {
deleteDraftCommentIfPresent(comment);
upsertPublishedComment(comment);
}
}
public void updateComment(PatchLineComment comment) throws OrmException {
if (comment.getStatus() == Status.DRAFT) {
updateDraftComment(comment);
} else {
deleteDraftCommentIfPresent(comment);
updatePublishedComment(comment);
}
}
public void deleteComment(PatchLineComment comment) throws OrmException {
if (comment.getStatus() == Status.DRAFT) {
deleteDraftComment(comment);
} else {
throw new IllegalArgumentException("Cannot delete a published comment.");
}
}
private void insertPublishedComment(PatchLineComment c) throws OrmException {
verifyComment(c);
if (notes == null) {
notes = getChangeNotes().load();
}
checkArgument(!notes.containsComment(c),
"A comment already exists with the same key as the following comment,"
+ " so we cannot insert this comment: %s", c);
if (c.getSide() == 0) {
commentsForBase.add(c);
} else {
commentsForPs.add(c);
}
}
private void insertDraftComment(PatchLineComment c) throws OrmException {
createDraftUpdateIfNull(c);
draftUpdate.insertComment(c);
}
private void upsertPublishedComment(PatchLineComment c) {
verifyComment(c);
if (c.getSide() == 0) {
commentsForBase.add(c);
} else {
commentsForPs.add(c);
}
}
private void upsertDraftComment(PatchLineComment c) throws OrmException {
createDraftUpdateIfNull(c);
draftUpdate.upsertComment(c);
}
private void updatePublishedComment(PatchLineComment c) throws OrmException {
verifyComment(c);
if (notes == null) {
notes = getChangeNotes().load();
}
checkArgument(!notes.containsCommentPublished(c),
"Cannot update a comment that has already been published and saved");
if (c.getSide() == 0) {
commentsForBase.add(c);
} else {
commentsForPs.add(c);
}
}
private void updateDraftComment(PatchLineComment c) throws OrmException {
createDraftUpdateIfNull(c);
draftUpdate.updateComment(c);
}
private void deleteDraftComment(PatchLineComment c) throws OrmException {
createDraftUpdateIfNull(c);
draftUpdate.deleteComment(c);
}
private void deleteDraftCommentIfPresent(PatchLineComment c)
throws OrmException {
createDraftUpdateIfNull(c);
draftUpdate.deleteCommentIfPresent(c);
}
private void createDraftUpdateIfNull(PatchLineComment c) throws OrmException {
if (draftUpdate == null) {
draftUpdate = draftUpdateFactory.create(ctl, when);
if (psId != null) {
draftUpdate.setPatchSetId(psId);
} else {
draftUpdate.setPatchSetId(getCommentPsId(c));
}
}
}
private void verifyComment(PatchLineComment c) {
checkArgument(psId != null,
"setPatchSetId must be called first");
checkArgument(getCommentPsId(c).equals(psId),
"Comment on %s doesn't match previous patch set %s",
getCommentPsId(c), psId);
checkArgument(c.getRevId() != null);
checkArgument(c.getStatus() == Status.PUBLISHED,
"Cannot add a draft comment to a ChangeUpdate. Use a ChangeDraftUpdate"
+ " for draft comments");
checkArgument(c.getAuthor().equals(getUser().getAccountId()),
"The author for the following comment does not match the author of"
+ " this ChangeDraftUpdate (%s): %s", getUser().getAccountId(), c);
}
public void putReviewer(Account.Id reviewer, ReviewerState type) {
checkArgument(type != ReviewerState.REMOVED, "invalid ReviewerType");
reviewers.put(reviewer, type);
@@ -243,6 +375,9 @@ public class ChangeUpdate extends AbstractChangeUpdate {
}
}
batch.write(builder);
if (draftUpdate != null) {
draftUpdate.commit();
}
RevCommit c = batch.commit();
return c;
} catch (OrmException e) {

View File

@@ -0,0 +1,199 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.notedb;
import static com.google.gerrit.server.notedb.CommentsInNotesUtil.getCommentPsId;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
/**
* View of the draft comments for a single {@link Change} based on the log of
* its drafts branch.
*/
public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
@Singleton
public static class Factory {
private final GitRepositoryManager repoManager;
private final AllUsersName draftsProject;
@VisibleForTesting
@Inject
public Factory(GitRepositoryManager repoManager,
AllUsersNameProvider allUsers) {
this.repoManager = repoManager;
this.draftsProject = allUsers.get();
}
public DraftCommentNotes create(Change.Id changeId, Account.Id accountId) {
return new DraftCommentNotes(repoManager, draftsProject, changeId,
accountId);
}
}
private static class Parser {
private final Change.Id changeId;
private final ObjectId tip;
private final RevWalk walk;
private final Repository repo;
private final Account.Id author;
private final Multimap<PatchSet.Id, PatchLineComment> draftBaseComments;
private final Multimap<PatchSet.Id, PatchLineComment> draftPsComments;
private NoteMap noteMap;
private Parser(Change.Id changeId, RevWalk walk, ObjectId tip,
GitRepositoryManager repoManager, AllUsersName draftsProject,
Account.Id author) throws RepositoryNotFoundException, IOException {
this.changeId = changeId;
this.walk = walk;
this.tip = tip;
this.repo = repoManager.openRepository(draftsProject);
this.author = author;
draftBaseComments = ArrayListMultimap.create();
draftPsComments = ArrayListMultimap.create();
}
private void parseDraftComments() throws IOException, ConfigInvalidException {
walk.markStart(walk.parseCommit(tip));
noteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
RefNames.refsDraftComments(author, changeId),
walk, changeId, draftBaseComments,
draftPsComments, Status.DRAFT);
}
}
private final AllUsersName draftsProject;
private final Account.Id author;
private final Table<PatchSet.Id, String, PatchLineComment> draftBaseComments;
private final Table<PatchSet.Id, String, PatchLineComment> draftPsComments;
private NoteMap noteMap;
DraftCommentNotes(GitRepositoryManager repoManager,
AllUsersName draftsProject, Change.Id changeId, Account.Id author) {
super(repoManager, changeId);
this.draftsProject = draftsProject;
this.author = author;
this.draftBaseComments = HashBasedTable.create();
this.draftPsComments = HashBasedTable.create();
}
public NoteMap getNoteMap() {
return noteMap;
}
public Account.Id getAuthor() {
return author;
}
/**
* @return a defensive copy of the table containing all draft comments
* on this change with side == 0. The row key is the comment's PatchSet.Id,
* the column key is the comment's UUID, and the value is the comment.
*/
public Table<PatchSet.Id, String, PatchLineComment>
getDraftBaseComments() {
return HashBasedTable.create(draftBaseComments);
}
/**
* @return a defensive copy of the table containing all draft comments
* on this change with side == 1. The row key is the comment's PatchSet.Id,
* the column key is the comment's UUID, and the value is the comment.
*/
public Table<PatchSet.Id, String, PatchLineComment>
getDraftPsComments() {
return HashBasedTable.create(draftPsComments);
}
public boolean containsComment(PatchLineComment c) {
Table<PatchSet.Id, String, PatchLineComment> t =
c.getSide() == (short) 0
? getDraftBaseComments()
: getDraftPsComments();
return t.contains(getCommentPsId(c), c.getKey().get());
}
@Override
protected String getRefName() {
return RefNames.refsDraftComments(author, getChangeId());
}
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
ObjectId rev = getRevision();
if (rev == null) {
return;
}
RevWalk walk = new RevWalk(reader);
Parser parser = new Parser(getChangeId(), walk, rev, repoManager,
draftsProject, author);
parser.parseDraftComments();
buildCommentTable(draftBaseComments, parser.draftBaseComments);
buildCommentTable(draftPsComments, parser.draftPsComments);
noteMap = parser.noteMap;
}
@Override
protected boolean onSave(CommitBuilder commit) throws IOException,
ConfigInvalidException {
throw new UnsupportedOperationException(
getClass().getSimpleName() + " is read-only");
}
@Override
protected Project.NameKey getProjectName() {
return draftsProject;
}
private void buildCommentTable(
Table<PatchSet.Id, String, PatchLineComment> commentTable,
Multimap<PatchSet.Id, PatchLineComment> allComments) {
for (PatchLineComment c : allComments.values()) {
commentTable.put(getCommentPsId(c), c.getKey().get(), c);
}
}
}

View File

@@ -20,5 +20,6 @@ public class NoteDbModule extends FactoryModule {
@Override
public void configure() {
factory(ChangeUpdate.Factory.class);
factory(ChangeDraftUpdate.Factory.class);
}
}

View File

@@ -50,6 +50,7 @@ import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -110,6 +111,7 @@ public class CommentsTest {
private Injector injector;
private Project.NameKey project;
private InMemoryRepositoryManager repoManager;
private AllUsersNameProvider allUsers;
private PatchLineCommentsUtil plcUtil;
private RevisionResource revRes1;
private RevisionResource revRes2;
@@ -165,6 +167,9 @@ public class CommentsTest {
injector = Guice.createInjector(mod);
NotesMigration migration = injector.getInstance(NotesMigration.class);
allUsers = injector.getInstance(AllUsersNameProvider.class);
repoManager.createRepository(allUsers.get());
plcUtil = new PatchLineCommentsUtil(migration);
Account co = new Account(new Account.Id(1), TimeUtil.nowTs());
@@ -250,7 +255,7 @@ public class CommentsTest {
}
private ChangeControl stubChangeControl(Change c) throws OrmException {
return TestChanges.stubChangeControl(repoManager, c, changeOwner);
return TestChanges.stubChangeControl(repoManager, c, allUsers, changeOwner);
}
private Change newChange() {
@@ -258,7 +263,7 @@ public class CommentsTest {
}
private ChangeUpdate newUpdate(Change c, final IdentifiedUser user) throws Exception {
return TestChanges.newUpdate(injector, repoManager, c, user);
return TestChanges.newUpdate(injector, repoManager, c, allUsers, user);
}
@Test

View File

@@ -49,6 +49,7 @@ import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -88,6 +89,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
@@ -104,8 +106,10 @@ public class ChangeNotesTest {
private InMemoryRepositoryManager repoManager;
private InMemoryRepository repo;
private FakeAccountCache accountCache;
private AllUsersNameProvider allUsers;
private IdentifiedUser changeOwner;
private IdentifiedUser otherUser;
private Account.Id otherUserId;
private Injector injector;
private String systemTimeZone;
private volatile long clockStepMs;
@@ -157,8 +161,11 @@ public class ChangeNotesTest {
IdentifiedUser.GenericFactory userFactory =
injector.getInstance(IdentifiedUser.GenericFactory.class);
allUsers = injector.getInstance(AllUsersNameProvider.class);
repoManager.createRepository(allUsers.get());
changeOwner = userFactory.create(co.getId());
otherUser = userFactory.create(ou.getId());
otherUserId = otherUser.getAccountId();
}
private void setTimeForTesting() {
@@ -858,7 +865,7 @@ public class ChangeNotesTest {
uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -867,7 +874,7 @@ public class ChangeNotesTest {
uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.upsertComment(comment2);
update.commit();
update = newUpdate(c, otherUser);
@@ -876,7 +883,7 @@ public class ChangeNotesTest {
uuid, range3, range3.getEndLine(), otherUser, null, time3, message3,
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment3);
update.upsertComment(comment3);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -936,7 +943,7 @@ public class ChangeNotesTest {
uuid, range1, range1.getEndLine(), otherUser, null, time1, message1,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -945,7 +952,7 @@ public class ChangeNotesTest {
uuid, range2, range2.getEndLine(), otherUser, null, time2, message2,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -998,7 +1005,7 @@ public class ChangeNotesTest {
range, range.getEndLine(), otherUser, null, now, messageForBase,
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(commentForBase);
update.upsertComment(commentForBase);
update.commit();
update = newUpdate(c, otherUser);
@@ -1007,7 +1014,7 @@ public class ChangeNotesTest {
range, range.getEndLine(), otherUser, null, now, messageForPS,
(short) 1, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(psId);
update.putComment(commentForPS);
update.upsertComment(commentForPS);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1040,7 +1047,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, timeForComment1,
"comment 1", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -1048,7 +1055,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, timeForComment2,
"comment 2", side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1086,7 +1093,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment 1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment1);
update.upsertComment(comment1);
update.commit();
update = newUpdate(c, otherUser);
@@ -1094,7 +1101,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment 2",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(psId);
update.putComment(comment2);
update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1130,7 +1137,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
side, "abcd1234abcd1234abcd1234abcd1234abcd1234");
update.setPatchSetId(ps1);
update.putComment(comment1);
update.upsertComment(comment1);
update.commit();
incrementPatchSet(c);
@@ -1142,7 +1149,7 @@ public class ChangeNotesTest {
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
side, "abcd4567abcd4567abcd4567abcd4567abcd4567");
update.setPatchSetId(ps2);
update.putComment(comment2);
update.upsertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
@@ -1165,6 +1172,160 @@ public class ChangeNotesTest {
assertEquals(comment2, commentFromPs2);
}
@Test
public void patchLineCommentSingleDraftToPublished() throws Exception {
Change c = newChange();
String uuid = "uuid";
CommentRange range = new CommentRange(1, 1, 2, 1);
PatchSet.Id ps1 = c.currentPatchSetId();
String filename = "filename1";
short side = (short) 1;
ChangeUpdate update = newUpdate(c, otherUser);
Timestamp now = TimeUtil.nowTs();
PatchLineComment comment1 = newPatchLineComment(ps1, filename, uuid,
range, range.getEndLine(), otherUser, null, now, "comment on ps1", side,
"abcd4567abcd4567abcd4567abcd4567abcd4567", Status.DRAFT);
update.setPatchSetId(ps1);
update.insertComment(comment1);
update.commit();
ChangeNotes notes = newNotes(c);
assertEquals(1, notes.getDraftPsComments(otherUserId).values().size());
assertEquals(0, notes.getDraftBaseComments(otherUserId).values().size());
comment1.setStatus(Status.PUBLISHED);
update = newUpdate(c, otherUser);
update.setPatchSetId(ps1);
update.updateComment(comment1);
update.commit();
notes = newNotes(c);
assertTrue(notes.getDraftPsComments(otherUserId).values().isEmpty());
assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
assertTrue(notes.getBaseComments().values().isEmpty());
PatchLineComment commentFromNotes =
Iterables.getOnlyElement(notes.getPatchSetComments().values());
assertEquals(comment1, commentFromNotes);
}
@Test
public void patchLineCommentMultipleDraftsSameSidePublishOne()
throws OrmException, IOException {
Change c = newChange();
String uuid1 = "uuid1";
String uuid2 = "uuid2";
CommentRange range1 = new CommentRange(1, 1, 2, 2);
CommentRange range2 = new CommentRange(2, 2, 3, 3);
String filename = "filename1";
short side = (short) 1;
Timestamp now = TimeUtil.nowTs();
String commitSHA1 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
PatchSet.Id psId = c.currentPatchSetId();
// Write two drafts on the same side of one patch set.
ChangeUpdate update = newUpdate(c, otherUser);
update.setPatchSetId(psId);
PatchLineComment comment1 = newPatchLineComment(psId, filename, uuid1,
range1, range1.getEndLine(), otherUser, null, now, "comment on ps1",
side, commitSHA1, Status.DRAFT);
PatchLineComment comment2 = newPatchLineComment(psId, filename, uuid2,
range2, range2.getEndLine(), otherUser, null, now, "other on ps1",
side, commitSHA1, Status.DRAFT);
update.insertComment(comment1);
update.insertComment(comment2);
update.commit();
ChangeNotes notes = newNotes(c);
assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
assertEquals(2, notes.getDraftPsComments(otherUserId).values().size());
assertTrue(notes.getDraftPsComments(otherUserId).containsValue(comment1));
assertTrue(notes.getDraftPsComments(otherUserId).containsValue(comment2));
// Publish first draft.
update = newUpdate(c, otherUser);
update.setPatchSetId(psId);
comment1.setStatus(Status.PUBLISHED);
update.updateComment(comment1);
update.commit();
notes = newNotes(c);
assertEquals(comment1,
Iterables.getOnlyElement(notes.getPatchSetComments().get(psId)));
assertEquals(comment2,
Iterables.getOnlyElement(
notes.getDraftPsComments(otherUserId).values()));
assertTrue(notes.getBaseComments().values().isEmpty());
assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
}
@Test
public void patchLineCommentsMultipleDraftsBothSidesPublishAll()
throws OrmException, IOException {
Change c = newChange();
String uuid1 = "uuid1";
String uuid2 = "uuid2";
CommentRange range1 = new CommentRange(1, 1, 2, 2);
CommentRange range2 = new CommentRange(2, 2, 3, 3);
String filename = "filename1";
Timestamp now = TimeUtil.nowTs();
String commitSHA1 = "abcd4567abcd4567abcd4567abcd4567abcd4567";
String baseSHA1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
PatchSet.Id psId = c.currentPatchSetId();
// Write two drafts, one on each side of the patchset.
ChangeUpdate update = newUpdate(c, otherUser);
update.setPatchSetId(psId);
PatchLineComment baseComment = newPatchLineComment(psId, filename, uuid1,
range1, range1.getEndLine(), otherUser, null, now, "comment on base",
(short) 0, baseSHA1, Status.DRAFT);
PatchLineComment psComment = newPatchLineComment(psId, filename, uuid2,
range2, range2.getEndLine(), otherUser, null, now, "comment on ps",
(short) 1, commitSHA1, Status.DRAFT);
update.insertComment(baseComment);
update.insertComment(psComment);
update.commit();
ChangeNotes notes = newNotes(c);
PatchLineComment baseDraftCommentFromNotes =
Iterables.getOnlyElement(
notes.getDraftBaseComments(otherUserId).values());
PatchLineComment psDraftCommentFromNotes =
Iterables.getOnlyElement(
notes.getDraftPsComments(otherUserId).values());
assertEquals(baseComment, baseDraftCommentFromNotes);
assertEquals(psComment, psDraftCommentFromNotes);
// Publish both comments.
update = newUpdate(c, otherUser);
update.setPatchSetId(psId);
baseComment.setStatus(Status.PUBLISHED);
psComment.setStatus(Status.PUBLISHED);
update.updateComment(baseComment);
update.updateComment(psComment);
update.commit();
notes = newNotes(c);
PatchLineComment baseCommentFromNotes =
Iterables.getOnlyElement(notes.getBaseComments().values());
PatchLineComment psCommentFromNotes =
Iterables.getOnlyElement(notes.getPatchSetComments().values());
assertEquals(baseComment, baseCommentFromNotes);
assertEquals(psComment, psCommentFromNotes);
assertTrue(notes.getDraftBaseComments(otherUserId).values().isEmpty());
assertTrue(notes.getDraftPsComments(otherUserId).values().isEmpty());
}
private Change newChange() {
return TestChanges.newChange(project, changeOwner);
}
@@ -1194,12 +1355,12 @@ public class ChangeNotesTest {
}
private ChangeUpdate newUpdate(Change c, IdentifiedUser user)
throws Exception {
return TestChanges.newUpdate(injector, repoManager, c, user);
throws OrmException {
return TestChanges.newUpdate(injector, repoManager, c, allUsers, user);
}
private ChangeNotes newNotes(Change c) throws OrmException {
return new ChangeNotes(repoManager, c).load();
return new ChangeNotes(repoManager, allUsers, c).load();
}
private static Timestamp truncate(Timestamp ts) {

View File

@@ -23,8 +23,11 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.FactoryModule;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeDraftUpdate;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.project.ChangeControl;
@@ -52,26 +55,29 @@ public class TestChanges {
}
public static ChangeUpdate newUpdate(Injector injector,
GitRepositoryManager repoManager, Change c, final IdentifiedUser user)
GitRepositoryManager repoManager, Change c,
final AllUsersNameProvider allUsers, final IdentifiedUser user)
throws OrmException {
return injector.createChildInjector(new FactoryModule() {
@Override
public void configure() {
factory(ChangeUpdate.Factory.class);
factory(ChangeDraftUpdate.Factory.class);
bind(IdentifiedUser.class).toInstance(user);
bind(AllUsersName.class).toProvider(allUsers);
}
}).getInstance(ChangeUpdate.Factory.class).create(
stubChangeControl(repoManager, c, user), TimeUtil.nowTs(),
stubChangeControl(repoManager, c, allUsers, user), TimeUtil.nowTs(),
Ordering.<String> natural());
}
public static ChangeControl stubChangeControl(
GitRepositoryManager repoManager, Change c, IdentifiedUser user)
throws OrmException {
GitRepositoryManager repoManager, Change c, AllUsersNameProvider allUsers,
IdentifiedUser user) throws OrmException {
ChangeControl ctl = EasyMock.createNiceMock(ChangeControl.class);
expect(ctl.getChange()).andStubReturn(c);
expect(ctl.getCurrentUser()).andStubReturn(user);
ChangeNotes notes = new ChangeNotes(repoManager, c);
ChangeNotes notes = new ChangeNotes(repoManager, allUsers, c);
notes = notes.load();
expect(ctl.getNotes()).andStubReturn(notes);
EasyMock.replay(ctl);