Merge changes from topics 'change-rebuilder-tests', 'note-db-update-manager'
* changes: NoteDb: Check change message author matches update author Create BatchUpdates using InternalUser where appropriate NoteDb: Add test for sequence of comment update operations Rewrite updating comments in NoteDb AbstractChangeUpdate: Set author/committer in superclass Support ChangeUpdates from InternalUser Start tests for ChangeRebuilder Rewrite ChangeRebuilder to better use NoteDbUpdateManager Rewrite AbstractChangeUpdate to not extend MetaDataUpdate
This commit is contained in:
commit
b6fa3a46c8
@ -0,0 +1,7 @@
|
|||||||
|
include_defs('//gerrit-acceptance-tests/tests.defs')
|
||||||
|
|
||||||
|
acceptance_tests(
|
||||||
|
group = 'server-notedb',
|
||||||
|
srcs = glob(['*IT.java']),
|
||||||
|
labels = ['notedb', 'server'],
|
||||||
|
)
|
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (C) 2016 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.acceptance.server.notedb;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.TruthJUnit.assume;
|
||||||
|
import static com.google.gerrit.common.TimeUtil.roundToSecond;
|
||||||
|
import static com.google.gerrit.testutil.GerritServerTests.isNoteDbTestEnabled;
|
||||||
|
|
||||||
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeRebuilder;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ChangeRebuilderIT extends AbstractDaemonTest {
|
||||||
|
@Inject
|
||||||
|
private ChangeRebuilder rebuilder;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
assume().that(isNoteDbTestEnabled()).isFalse();
|
||||||
|
notesMigration.setAllEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void changeFields() throws Exception {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
Change.Id id = r.getPatchSetId().getParentKey();
|
||||||
|
gApi.changes().id(id.get()).topic(name("a-topic"));
|
||||||
|
Change old = db.changes().get(id);
|
||||||
|
|
||||||
|
rebuild(id);
|
||||||
|
|
||||||
|
assertChangeEqual(old, notesFactory.create(db, project, id).getChange());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void patchSets() throws Exception {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
Change.Id id = r.getPatchSetId().getParentKey();
|
||||||
|
r = amendChange(r.getChangeId());
|
||||||
|
|
||||||
|
PatchSet ps1 = db.patchSets().get(new PatchSet.Id(id, 1));
|
||||||
|
PatchSet ps2 = db.patchSets().get(new PatchSet.Id(id, 2));
|
||||||
|
|
||||||
|
rebuild(id);
|
||||||
|
|
||||||
|
ChangeNotes notes = notesFactory.create(db, project, id);
|
||||||
|
Map<PatchSet.Id, PatchSet> patchSets = notes.getPatchSets();
|
||||||
|
assertThat(patchSets.keySet()).containsExactly(ps1.getId(), ps2.getId())
|
||||||
|
.inOrder();
|
||||||
|
|
||||||
|
assertPatchSetEqual(ps1, patchSets.get(ps1.getId()));
|
||||||
|
assertPatchSetEqual(ps2, patchSets.get(ps2.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuild(Change.Id... changeIds) throws Exception {
|
||||||
|
notesMigration.setWriteChanges(true);
|
||||||
|
for (Change.Id id : changeIds) {
|
||||||
|
rebuilder.rebuild(db, id);
|
||||||
|
}
|
||||||
|
notesMigration.setReadChanges(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertChangeEqual(Change expectedReviewDb,
|
||||||
|
Change actualNoteDb) {
|
||||||
|
assertThat(actualNoteDb.getId()).isEqualTo(expectedReviewDb.getId());
|
||||||
|
assertThat(actualNoteDb.getKey()).isEqualTo(expectedReviewDb.getKey());
|
||||||
|
|
||||||
|
// TODO(dborowitz): actualNoteDb's timestamps should come from notedb, currently
|
||||||
|
// they're read from reviewdb.
|
||||||
|
assertThat(roundToSecond(actualNoteDb.getCreatedOn()))
|
||||||
|
.isEqualTo(roundToSecond(expectedReviewDb.getCreatedOn()));
|
||||||
|
assertThat(roundToSecond(actualNoteDb.getLastUpdatedOn()))
|
||||||
|
.isEqualTo(roundToSecond(expectedReviewDb.getLastUpdatedOn()));
|
||||||
|
assertThat(actualNoteDb.getOwner()).isEqualTo(expectedReviewDb.getOwner());
|
||||||
|
assertThat(actualNoteDb.getDest()).isEqualTo(expectedReviewDb.getDest());
|
||||||
|
assertThat(actualNoteDb.getStatus())
|
||||||
|
.isEqualTo(expectedReviewDb.getStatus());
|
||||||
|
assertThat(actualNoteDb.currentPatchSetId())
|
||||||
|
.isEqualTo(expectedReviewDb.currentPatchSetId());
|
||||||
|
assertThat(actualNoteDb.getSubject())
|
||||||
|
.isEqualTo(expectedReviewDb.getSubject());
|
||||||
|
assertThat(actualNoteDb.getTopic()).isEqualTo(expectedReviewDb.getTopic());
|
||||||
|
assertThat(actualNoteDb.getOriginalSubject())
|
||||||
|
.isEqualTo(expectedReviewDb.getOriginalSubject());
|
||||||
|
assertThat(actualNoteDb.getSubmissionId())
|
||||||
|
.isEqualTo(expectedReviewDb.getSubmissionId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertPatchSetEqual(PatchSet expectedReviewDb,
|
||||||
|
PatchSet actualNoteDb) {
|
||||||
|
assertThat(actualNoteDb.getId()).isEqualTo(expectedReviewDb.getId());
|
||||||
|
assertThat(actualNoteDb.getRevision())
|
||||||
|
.isEqualTo(expectedReviewDb.getRevision());
|
||||||
|
assertThat(actualNoteDb.getUploader())
|
||||||
|
.isEqualTo(expectedReviewDb.getUploader());
|
||||||
|
assertThat(actualNoteDb.getCreatedOn())
|
||||||
|
.isEqualTo(roundToSecond(expectedReviewDb.getCreatedOn()));
|
||||||
|
assertThat(actualNoteDb.isDraft()).isEqualTo(expectedReviewDb.isDraft());
|
||||||
|
assertThat(actualNoteDb.getGroups())
|
||||||
|
.isEqualTo(expectedReviewDb.getGroups());
|
||||||
|
assertThat(actualNoteDb.getPushCertificate())
|
||||||
|
.isEqualTo(expectedReviewDb.getPushCertificate());
|
||||||
|
}
|
||||||
|
}
|
@ -108,49 +108,43 @@ public class RebuildNotedb extends SiteProgram {
|
|||||||
System.out.println("Rebuilding the notedb");
|
System.out.println("Rebuilding the notedb");
|
||||||
ChangeRebuilder rebuilder = sysInjector.getInstance(ChangeRebuilder.class);
|
ChangeRebuilder rebuilder = sysInjector.getInstance(ChangeRebuilder.class);
|
||||||
|
|
||||||
Multimap<Project.NameKey, Change> changesByProject = getChangesByProject();
|
Multimap<Project.NameKey, Change.Id> changesByProject =
|
||||||
final AtomicBoolean ok = new AtomicBoolean(true);
|
getChangesByProject();
|
||||||
|
AtomicBoolean ok = new AtomicBoolean(true);
|
||||||
Stopwatch sw = Stopwatch.createStarted();
|
Stopwatch sw = Stopwatch.createStarted();
|
||||||
GitRepositoryManager repoManager =
|
GitRepositoryManager repoManager =
|
||||||
sysInjector.getInstance(GitRepositoryManager.class);
|
sysInjector.getInstance(GitRepositoryManager.class);
|
||||||
final Project.NameKey allUsersName =
|
Project.NameKey allUsersName =
|
||||||
sysInjector.getInstance(AllUsersName.class);
|
sysInjector.getInstance(AllUsersName.class);
|
||||||
try (Repository allUsersRepo =
|
try (Repository allUsersRepo =
|
||||||
repoManager.openMetadataRepository(allUsersName)) {
|
repoManager.openMetadataRepository(allUsersName)) {
|
||||||
deleteRefs(RefNames.REFS_DRAFT_COMMENTS, allUsersRepo);
|
deleteRefs(RefNames.REFS_DRAFT_COMMENTS, allUsersRepo);
|
||||||
deleteRefs(RefNames.REFS_STARRED_CHANGES, allUsersRepo);
|
deleteRefs(RefNames.REFS_STARRED_CHANGES, allUsersRepo);
|
||||||
for (final Project.NameKey project : changesByProject.keySet()) {
|
for (Project.NameKey project : changesByProject.keySet()) {
|
||||||
try (Repository repo = repoManager.openMetadataRepository(project)) {
|
try {
|
||||||
final BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
|
|
||||||
final BatchRefUpdate bruAllUsers =
|
|
||||||
allUsersRepo.getRefDatabase().newBatchUpdate();
|
|
||||||
List<ListenableFuture<?>> futures = Lists.newArrayList();
|
List<ListenableFuture<?>> futures = Lists.newArrayList();
|
||||||
|
|
||||||
// Here, we elide the project name to 50 characters to ensure that
|
// Here, we elide the project name to 50 characters to ensure that
|
||||||
// the whole monitor line for a project fits on one line (<80 chars).
|
// the whole monitor line for a project fits on one line (<80 chars).
|
||||||
final MultiProgressMonitor mpm = new MultiProgressMonitor(System.out,
|
final MultiProgressMonitor mpm = new MultiProgressMonitor(System.out,
|
||||||
FormatUtil.elide(project.get(), 50));
|
FormatUtil.elide(project.get(), 50));
|
||||||
final Task doneTask =
|
Task doneTask =
|
||||||
mpm.beginSubTask("done", changesByProject.get(project).size());
|
mpm.beginSubTask("done", changesByProject.get(project).size());
|
||||||
final Task failedTask =
|
Task failedTask =
|
||||||
mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
|
mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
|
||||||
|
|
||||||
for (final Change c : changesByProject.get(project)) {
|
for (Change.Id id : changesByProject.get(project)) {
|
||||||
final ListenableFuture<?> future = rebuilder.rebuildAsync(c,
|
ListenableFuture<?> future = rebuilder.rebuildAsync(id, executor);
|
||||||
executor, bru, bruAllUsers, repo, allUsersRepo);
|
|
||||||
futures.add(future);
|
futures.add(future);
|
||||||
future.addListener(
|
future.addListener(
|
||||||
new RebuildListener(c.getId(), future, ok, doneTask, failedTask),
|
new RebuildListener(id, future, ok, doneTask, failedTask),
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
||||||
mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
|
mpm.waitFor(Futures.transformAsync(Futures.successfulAsList(futures),
|
||||||
new AsyncFunction<List<?>, Void>() {
|
new AsyncFunction<List<?>, Void>() {
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Void> apply(List<?> input)
|
public ListenableFuture<Void> apply(List<?> input) {
|
||||||
throws Exception {
|
|
||||||
execute(bru, repo);
|
|
||||||
execute(bruAllUsers, allUsersRepo);
|
|
||||||
mpm.end();
|
mpm.end();
|
||||||
return Futures.immediateFuture(null);
|
return Futures.immediateFuture(null);
|
||||||
}
|
}
|
||||||
@ -218,17 +212,17 @@ public class RebuildNotedb extends SiteProgram {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Multimap<Project.NameKey, Change> getChangesByProject()
|
private Multimap<Project.NameKey, Change.Id> getChangesByProject()
|
||||||
throws OrmException {
|
throws OrmException {
|
||||||
// Memorize all changes so we can close the db connection and allow
|
// Memorize all changes so we can close the db connection and allow
|
||||||
// rebuilder threads to use the full connection pool.
|
// rebuilder threads to use the full connection pool.
|
||||||
SchemaFactory<ReviewDb> schemaFactory = sysInjector.getInstance(Key.get(
|
SchemaFactory<ReviewDb> schemaFactory = sysInjector.getInstance(Key.get(
|
||||||
new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
|
new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
|
||||||
Multimap<Project.NameKey, Change> changesByProject =
|
Multimap<Project.NameKey, Change.Id> changesByProject =
|
||||||
ArrayListMultimap.create();
|
ArrayListMultimap.create();
|
||||||
try (ReviewDb db = schemaFactory.open()) {
|
try (ReviewDb db = schemaFactory.open()) {
|
||||||
for (Change c : db.changes().all()) {
|
for (Change c : db.changes().all()) {
|
||||||
changesByProject.put(c.getProject(), c);
|
changesByProject.put(c.getProject(), c.getId());
|
||||||
}
|
}
|
||||||
return changesByProject;
|
return changesByProject;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server;
|
package com.google.gerrit.server;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
@ -27,6 +29,7 @@ import com.google.inject.Singleton;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility functions to manipulate ChangeMessages.
|
* Utility functions to manipulate ChangeMessages.
|
||||||
@ -68,6 +71,11 @@ public class ChangeMessagesUtil {
|
|||||||
|
|
||||||
public void addChangeMessage(ReviewDb db, ChangeUpdate update,
|
public void addChangeMessage(ReviewDb db, ChangeUpdate update,
|
||||||
ChangeMessage changeMessage) throws OrmException {
|
ChangeMessage changeMessage) throws OrmException {
|
||||||
|
checkState(
|
||||||
|
Objects.equals(changeMessage.getAuthor(),
|
||||||
|
update.getUser().getAccountId()),
|
||||||
|
"cannot store change message of %s in update of %s",
|
||||||
|
changeMessage.getAuthor(), update.getUser().getAccountId());
|
||||||
update.setChangeMessage(changeMessage.getMessage());
|
update.setChangeMessage(changeMessage.getMessage());
|
||||||
db.changeMessages().insert(Collections.singleton(changeMessage));
|
db.changeMessages().insert(Collections.singleton(changeMessage));
|
||||||
}
|
}
|
||||||
|
@ -288,30 +288,14 @@ public class PatchLineCommentsUtil {
|
|||||||
return sort(comments);
|
return sort(comments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertComments(ReviewDb db, ChangeUpdate update,
|
public void putComments(ReviewDb db, ChangeUpdate update,
|
||||||
Iterable<PatchLineComment> comments) throws OrmException {
|
Iterable<PatchLineComment> comments) throws OrmException {
|
||||||
for (PatchLineComment c : comments) {
|
for (PatchLineComment c : comments) {
|
||||||
update.insertComment(c);
|
update.putComment(c);
|
||||||
}
|
|
||||||
db.patchComments().insert(comments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void upsertComments(ReviewDb db, ChangeUpdate update,
|
|
||||||
Iterable<PatchLineComment> comments) throws OrmException {
|
|
||||||
for (PatchLineComment c : comments) {
|
|
||||||
update.upsertComment(c);
|
|
||||||
}
|
}
|
||||||
db.patchComments().upsert(comments);
|
db.patchComments().upsert(comments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateComments(ReviewDb db, ChangeUpdate update,
|
|
||||||
Iterable<PatchLineComment> comments) throws OrmException {
|
|
||||||
for (PatchLineComment c : comments) {
|
|
||||||
update.updateComment(c);
|
|
||||||
}
|
|
||||||
db.patchComments().update(comments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteComments(ReviewDb db, ChangeUpdate update,
|
public void deleteComments(ReviewDb db, ChangeUpdate update,
|
||||||
Iterable<PatchLineComment> comments) throws OrmException {
|
Iterable<PatchLineComment> comments) throws OrmException {
|
||||||
for (PatchLineComment c : comments) {
|
for (PatchLineComment c : comments) {
|
||||||
|
@ -31,6 +31,7 @@ import com.google.gerrit.reviewdb.client.PatchSet;
|
|||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
import com.google.gerrit.server.ChangeUtil;
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.PatchSetUtil;
|
import com.google.gerrit.server.PatchSetUtil;
|
||||||
import com.google.gerrit.server.git.BatchUpdate;
|
import com.google.gerrit.server.git.BatchUpdate;
|
||||||
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
||||||
@ -85,18 +86,19 @@ public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
|
|||||||
if (!control.canAbandon(dbProvider.get())) {
|
if (!control.canAbandon(dbProvider.get())) {
|
||||||
throw new AuthException("abandon not permitted");
|
throw new AuthException("abandon not permitted");
|
||||||
}
|
}
|
||||||
Change change = abandon(control, input.message,
|
Change change = abandon(control, input.message);
|
||||||
control.getUser().asIdentifiedUser().getAccount());
|
|
||||||
return json.create(ChangeJson.NO_OPTIONS).format(change);
|
return json.create(ChangeJson.NO_OPTIONS).format(change);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Change abandon(ChangeControl control,
|
public Change abandon(ChangeControl control, String msgTxt)
|
||||||
final String msgTxt, final Account account)
|
|
||||||
throws RestApiException, UpdateException {
|
throws RestApiException, UpdateException {
|
||||||
|
CurrentUser user = control.getUser();
|
||||||
|
Account account = user.isIdentifiedUser()
|
||||||
|
? user.asIdentifiedUser().getAccount()
|
||||||
|
: null;
|
||||||
Op op = new Op(msgTxt, account);
|
Op op = new Op(msgTxt, account);
|
||||||
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
try (BatchUpdate u = batchUpdateFactory.create(dbProvider.get(),
|
||||||
control.getProject().getNameKey(), control.getUser(),
|
control.getProject().getNameKey(), user, TimeUtil.nowTs())) {
|
||||||
TimeUtil.nowTs())) {
|
|
||||||
u.addOp(control.getId(), op).execute();
|
u.addOp(control.getId(), op).execute();
|
||||||
}
|
}
|
||||||
return op.change;
|
return op.change;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
package com.google.gerrit.server.change;
|
package com.google.gerrit.server.change;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.InternalUser;
|
||||||
import com.google.gerrit.server.config.ChangeCleanupConfig;
|
import com.google.gerrit.server.config.ChangeCleanupConfig;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.query.QueryParseException;
|
import com.google.gerrit.server.query.QueryParseException;
|
||||||
@ -37,7 +37,7 @@ public class AbandonUtil {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(AbandonUtil.class);
|
private static final Logger log = LoggerFactory.getLogger(AbandonUtil.class);
|
||||||
|
|
||||||
private final ChangeCleanupConfig cfg;
|
private final ChangeCleanupConfig cfg;
|
||||||
private final IdentifiedUser.GenericFactory identifiedUserFactory;
|
private final InternalUser.Factory internalUserFactory;
|
||||||
private final QueryProcessor queryProcessor;
|
private final QueryProcessor queryProcessor;
|
||||||
private final ChangeQueryBuilder queryBuilder;
|
private final ChangeQueryBuilder queryBuilder;
|
||||||
private final Abandon abandon;
|
private final Abandon abandon;
|
||||||
@ -45,12 +45,12 @@ public class AbandonUtil {
|
|||||||
@Inject
|
@Inject
|
||||||
AbandonUtil(
|
AbandonUtil(
|
||||||
ChangeCleanupConfig cfg,
|
ChangeCleanupConfig cfg,
|
||||||
IdentifiedUser.GenericFactory identifiedUserFactory,
|
InternalUser.Factory internalUserFactory,
|
||||||
QueryProcessor queryProcessor,
|
QueryProcessor queryProcessor,
|
||||||
ChangeQueryBuilder queryBuilder,
|
ChangeQueryBuilder queryBuilder,
|
||||||
Abandon abandon) {
|
Abandon abandon) {
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.identifiedUserFactory = identifiedUserFactory;
|
this.internalUserFactory = internalUserFactory;
|
||||||
this.queryProcessor = queryProcessor;
|
this.queryProcessor = queryProcessor;
|
||||||
this.queryBuilder = queryBuilder;
|
this.queryBuilder = queryBuilder;
|
||||||
this.abandon = abandon;
|
this.abandon = abandon;
|
||||||
@ -73,7 +73,7 @@ public class AbandonUtil {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
for (ChangeData cd : changesToAbandon) {
|
for (ChangeData cd : changesToAbandon) {
|
||||||
try {
|
try {
|
||||||
abandon.abandon(changeControl(cd), cfg.getAbandonMessage(), null);
|
abandon.abandon(changeControl(cd), cfg.getAbandonMessage());
|
||||||
count++;
|
count++;
|
||||||
} catch (ResourceConflictException e) {
|
} catch (ResourceConflictException e) {
|
||||||
// Change was already merged or abandoned.
|
// Change was already merged or abandoned.
|
||||||
@ -91,7 +91,6 @@ public class AbandonUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ChangeControl changeControl(ChangeData cd) throws OrmException {
|
private ChangeControl changeControl(ChangeData cd) throws OrmException {
|
||||||
return cd.changeControl(
|
return cd.changeControl(internalUserFactory.create());
|
||||||
identifiedUserFactory.create(cd.change().getOwner()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ public class CreateDraftComment implements RestModifyView<RevisionResource, Draf
|
|||||||
comment.setRange(in.range);
|
comment.setRange(in.range);
|
||||||
setCommentRevId(
|
setCommentRevId(
|
||||||
comment, patchListCache, ctx.getChange(), ps);
|
comment, patchListCache, ctx.getChange(), ps);
|
||||||
plcUtil.insertComments(
|
plcUtil.putComments(
|
||||||
ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(comment));
|
ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(comment));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -476,7 +476,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
// TODO(dborowitz): Currently doesn't work for PUBLISH_ALL_REVISIONS with
|
// TODO(dborowitz): Currently doesn't work for PUBLISH_ALL_REVISIONS with
|
||||||
// notedb.
|
// notedb.
|
||||||
plcUtil.deleteComments(ctx.getDb(), u, del);
|
plcUtil.deleteComments(ctx.getDb(), u, del);
|
||||||
plcUtil.upsertComments(ctx.getDb(), u, ups);
|
plcUtil.putComments(ctx.getDb(), u, ups);
|
||||||
comments.addAll(ups);
|
comments.addAll(ups);
|
||||||
return !del.isEmpty() || !ups.isEmpty();
|
return !del.isEmpty() || !ups.isEmpty();
|
||||||
}
|
}
|
||||||
|
@ -142,14 +142,14 @@ public class PutDraftComment implements RestModifyView<DraftCommentResource, Dra
|
|||||||
ctx.getUser().getAccountId(),
|
ctx.getUser().getAccountId(),
|
||||||
comment.getParentUuid(), ctx.getWhen());
|
comment.getParentUuid(), ctx.getWhen());
|
||||||
setCommentRevId(comment, patchListCache, ctx.getChange(), ps);
|
setCommentRevId(comment, patchListCache, ctx.getChange(), ps);
|
||||||
plcUtil.insertComments(ctx.getDb(), update,
|
plcUtil.putComments(ctx.getDb(), update,
|
||||||
Collections.singleton(update(comment, in)));
|
Collections.singleton(update(comment, in)));
|
||||||
} else {
|
} else {
|
||||||
if (comment.getRevId() == null) {
|
if (comment.getRevId() == null) {
|
||||||
setCommentRevId(
|
setCommentRevId(
|
||||||
comment, patchListCache, ctx.getChange(), ps);
|
comment, patchListCache, ctx.getChange(), ps);
|
||||||
}
|
}
|
||||||
plcUtil.updateComments(ctx.getDb(), update,
|
plcUtil.putComments(ctx.getDb(), update,
|
||||||
Collections.singleton(update(comment, in)));
|
Collections.singleton(update(comment, in)));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -36,11 +36,11 @@ import com.google.gerrit.server.CurrentUser;
|
|||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.PatchLineCommentsUtil;
|
import com.google.gerrit.server.PatchLineCommentsUtil;
|
||||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||||
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
|
|
||||||
import com.google.gerrit.server.index.ChangeIndexer;
|
import com.google.gerrit.server.index.ChangeIndexer;
|
||||||
import com.google.gerrit.server.notedb.ChangeDelete;
|
import com.google.gerrit.server.notedb.ChangeDelete;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||||
|
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
|
||||||
import com.google.gerrit.server.notedb.NotesMigration;
|
import com.google.gerrit.server.notedb.NotesMigration;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||||
@ -377,6 +377,7 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
private final ChangeControl.GenericFactory changeControlFactory;
|
private final ChangeControl.GenericFactory changeControlFactory;
|
||||||
private final ChangeNotes.Factory changeNotesFactory;
|
private final ChangeNotes.Factory changeNotesFactory;
|
||||||
private final ChangeUpdate.Factory changeUpdateFactory;
|
private final ChangeUpdate.Factory changeUpdateFactory;
|
||||||
|
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
||||||
private final GitReferenceUpdated gitRefUpdated;
|
private final GitReferenceUpdated gitRefUpdated;
|
||||||
private final NotesMigration notesMigration;
|
private final NotesMigration notesMigration;
|
||||||
private final PatchLineCommentsUtil plcUtil;
|
private final PatchLineCommentsUtil plcUtil;
|
||||||
@ -406,6 +407,7 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
ChangeControl.GenericFactory changeControlFactory,
|
ChangeControl.GenericFactory changeControlFactory,
|
||||||
ChangeNotes.Factory changeNotesFactory,
|
ChangeNotes.Factory changeNotesFactory,
|
||||||
ChangeUpdate.Factory changeUpdateFactory,
|
ChangeUpdate.Factory changeUpdateFactory,
|
||||||
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
GitReferenceUpdated gitRefUpdated,
|
GitReferenceUpdated gitRefUpdated,
|
||||||
NotesMigration notesMigration,
|
NotesMigration notesMigration,
|
||||||
PatchLineCommentsUtil plcUtil,
|
PatchLineCommentsUtil plcUtil,
|
||||||
@ -420,6 +422,7 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
this.changeControlFactory = changeControlFactory;
|
this.changeControlFactory = changeControlFactory;
|
||||||
this.changeNotesFactory = changeNotesFactory;
|
this.changeNotesFactory = changeNotesFactory;
|
||||||
this.changeUpdateFactory = changeUpdateFactory;
|
this.changeUpdateFactory = changeUpdateFactory;
|
||||||
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
this.gitRefUpdated = gitRefUpdated;
|
this.gitRefUpdated = gitRefUpdated;
|
||||||
this.notesMigration = notesMigration;
|
this.notesMigration = notesMigration;
|
||||||
this.plcUtil = plcUtil;
|
this.plcUtil = plcUtil;
|
||||||
@ -579,16 +582,12 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
indexFutures.add(indexer.deleteAsync(id));
|
indexFutures.add(indexer.deleteAsync(id));
|
||||||
} else {
|
} else {
|
||||||
if (notesMigration.writeChanges()) {
|
if (notesMigration.writeChanges()) {
|
||||||
BatchMetaDataUpdate bmdu = null;
|
NoteDbUpdateManager manager =
|
||||||
|
updateManagerFactory.create(ctx.getProject());
|
||||||
for (ChangeUpdate u : ctx.updates.values()) {
|
for (ChangeUpdate u : ctx.updates.values()) {
|
||||||
if (bmdu == null) {
|
manager.add(u);
|
||||||
bmdu = u.openUpdate();
|
|
||||||
}
|
|
||||||
u.writeCommit(bmdu);
|
|
||||||
}
|
|
||||||
if (bmdu != null) {
|
|
||||||
bmdu.commit();
|
|
||||||
}
|
}
|
||||||
|
manager.execute();
|
||||||
}
|
}
|
||||||
indexFutures.add(indexer.indexAsync(ctx.getProject(), id));
|
indexFutures.add(indexer.indexAsync(ctx.getProject(), id));
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,17 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git;
|
package com.google.gerrit.server.git;
|
||||||
|
|
||||||
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
|
import org.eclipse.jgit.lib.BatchRefUpdate;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -33,8 +38,8 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class ChainedReceiveCommands {
|
public class ChainedReceiveCommands {
|
||||||
private final Map<String, ReceiveCommand> commands = new LinkedHashMap<>();
|
private final Map<String, ReceiveCommand> commands = new LinkedHashMap<>();
|
||||||
|
private final Map<String, ObjectId> oldIds = new HashMap<>();
|
||||||
|
|
||||||
/** @return true if no commands have been added. */
|
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return commands.isEmpty();
|
return commands.isEmpty();
|
||||||
}
|
}
|
||||||
@ -64,6 +69,43 @@ public class ChainedReceiveCommands {
|
|||||||
old.getOldId(), cmd.getNewId(), cmd.getRefName()));
|
old.getOldId(), cmd.getNewId(), cmd.getRefName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest value of a ref according to this sequence of commands.
|
||||||
|
* <p>
|
||||||
|
* Once the value for a ref is read once, it is cached in this instance, so
|
||||||
|
* that multiple callers using this instance for lookups see a single
|
||||||
|
* consistent snapshot.
|
||||||
|
*
|
||||||
|
* @param repo repository to read from, if result is not cached.
|
||||||
|
* @param refName name of the ref.
|
||||||
|
* @return value of the ref, taking into account commands that have already
|
||||||
|
* been added to this instance. Null if the ref is deleted, matching the
|
||||||
|
* behavior of {@link Repository#exactRef(String)}.
|
||||||
|
*/
|
||||||
|
public ObjectId getObjectId(Repository repo, String refName)
|
||||||
|
throws IOException {
|
||||||
|
ReceiveCommand cmd = commands.get(refName);
|
||||||
|
if (cmd != null) {
|
||||||
|
return zeroToNull(cmd.getNewId());
|
||||||
|
}
|
||||||
|
ObjectId old = oldIds.get(refName);
|
||||||
|
if (old != null) {
|
||||||
|
return zeroToNull(old);
|
||||||
|
}
|
||||||
|
Ref ref = repo.exactRef(refName);
|
||||||
|
ObjectId id = ref != null ? ref.getObjectId() : null;
|
||||||
|
// Cache missing ref as zeroId to match value in commands map.
|
||||||
|
oldIds.put(refName, firstNonNull(id, ObjectId.zeroId()));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectId zeroToNull(ObjectId id) {
|
||||||
|
if (id == null || id.equals(ObjectId.zeroId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add commands from this instance to a native JGit batch update.
|
* Add commands from this instance to a native JGit batch update.
|
||||||
* <p>
|
* <p>
|
||||||
@ -74,7 +116,6 @@ public class ChainedReceiveCommands {
|
|||||||
* @param bru batch update
|
* @param bru batch update
|
||||||
*/
|
*/
|
||||||
public void addTo(BatchRefUpdate bru) {
|
public void addTo(BatchRefUpdate bru) {
|
||||||
checkState(!isEmpty(), "no commands to add");
|
|
||||||
for (ReceiveCommand cmd : commands.values()) {
|
for (ReceiveCommand cmd : commands.values()) {
|
||||||
bru.addCommand(cmd);
|
bru.addCommand(cmd);
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
|
|||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
import com.google.gerrit.server.ChangeUtil;
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.InternalUser;
|
||||||
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
||||||
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||||
import com.google.gerrit.server.git.strategy.SubmitStrategy;
|
import com.google.gerrit.server.git.strategy.SubmitStrategy;
|
||||||
@ -309,7 +310,7 @@ public class MergeOp implements AutoCloseable {
|
|||||||
private final ChangeMessagesUtil cmUtil;
|
private final ChangeMessagesUtil cmUtil;
|
||||||
private final BatchUpdate.Factory batchUpdateFactory;
|
private final BatchUpdate.Factory batchUpdateFactory;
|
||||||
private final GitRepositoryManager repoManager;
|
private final GitRepositoryManager repoManager;
|
||||||
private final IdentifiedUser.GenericFactory identifiedUserFactory;
|
private final InternalUser.Factory internalUserFactory;
|
||||||
private final MergeSuperSet mergeSuperSet;
|
private final MergeSuperSet mergeSuperSet;
|
||||||
private final MergeValidators.Factory mergeValidatorsFactory;
|
private final MergeValidators.Factory mergeValidatorsFactory;
|
||||||
private final ProjectCache projectCache;
|
private final ProjectCache projectCache;
|
||||||
@ -341,7 +342,7 @@ public class MergeOp implements AutoCloseable {
|
|||||||
MergeOp(ChangeMessagesUtil cmUtil,
|
MergeOp(ChangeMessagesUtil cmUtil,
|
||||||
BatchUpdate.Factory batchUpdateFactory,
|
BatchUpdate.Factory batchUpdateFactory,
|
||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
IdentifiedUser.GenericFactory identifiedUserFactory,
|
InternalUser.Factory internalUserFactory,
|
||||||
MergeSuperSet mergeSuperSet,
|
MergeSuperSet mergeSuperSet,
|
||||||
MergeValidators.Factory mergeValidatorsFactory,
|
MergeValidators.Factory mergeValidatorsFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@ -351,7 +352,7 @@ public class MergeOp implements AutoCloseable {
|
|||||||
this.cmUtil = cmUtil;
|
this.cmUtil = cmUtil;
|
||||||
this.batchUpdateFactory = batchUpdateFactory;
|
this.batchUpdateFactory = batchUpdateFactory;
|
||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.identifiedUserFactory = identifiedUserFactory;
|
this.internalUserFactory = internalUserFactory;
|
||||||
this.mergeSuperSet = mergeSuperSet;
|
this.mergeSuperSet = mergeSuperSet;
|
||||||
this.mergeValidatorsFactory = mergeValidatorsFactory;
|
this.mergeValidatorsFactory = mergeValidatorsFactory;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
@ -898,10 +899,8 @@ public class MergeOp implements AutoCloseable {
|
|||||||
Project.NameKey destProject) {
|
Project.NameKey destProject) {
|
||||||
try {
|
try {
|
||||||
for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
|
for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
|
||||||
//TODO: Use InternalUser instead of change owner
|
|
||||||
try (BatchUpdate bu = batchUpdateFactory.create(db, destProject,
|
try (BatchUpdate bu = batchUpdateFactory.create(db, destProject,
|
||||||
identifiedUserFactory.create(cd.change().getOwner()),
|
internalUserFactory.create(), TimeUtil.nowTs())) {
|
||||||
TimeUtil.nowTs())) {
|
|
||||||
bu.addOp(cd.getId(), new BatchUpdate.Op() {
|
bu.addOp(cd.getId(), new BatchUpdate.Op() {
|
||||||
@Override
|
@Override
|
||||||
public boolean updateChange(ChangeContext ctx) throws OrmException {
|
public boolean updateChange(ChangeContext ctx) throws OrmException {
|
||||||
|
@ -20,49 +20,52 @@ import com.google.gerrit.reviewdb.client.Account;
|
|||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.InternalUser;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
|
||||||
import com.google.gerrit.server.git.VersionedMetaData;
|
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
|
||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
|
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectInserter;
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/** A single delta related to a specific patch-set of a change. */
|
/** A single delta related to a specific patch-set of a change. */
|
||||||
public abstract class AbstractChangeUpdate extends VersionedMetaData {
|
public abstract class AbstractChangeUpdate {
|
||||||
protected final NotesMigration migration;
|
protected final NotesMigration migration;
|
||||||
protected final GitRepositoryManager repoManager;
|
protected final GitRepositoryManager repoManager;
|
||||||
protected final MetaDataUpdate.User updateFactory;
|
|
||||||
protected final ChangeControl ctl;
|
protected final ChangeControl ctl;
|
||||||
protected final String anonymousCowardName;
|
protected final String anonymousCowardName;
|
||||||
protected final PersonIdent serverIdent;
|
|
||||||
protected final Date when;
|
protected final Date when;
|
||||||
protected PatchSet.Id psId;
|
private final PersonIdent serverIdent;
|
||||||
|
|
||||||
AbstractChangeUpdate(NotesMigration migration,
|
protected PatchSet.Id psId;
|
||||||
|
private ObjectId result;
|
||||||
|
|
||||||
|
protected AbstractChangeUpdate(NotesMigration migration,
|
||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
MetaDataUpdate.User updateFactory, ChangeControl ctl,
|
ChangeControl ctl,
|
||||||
PersonIdent serverIdent,
|
PersonIdent serverIdent,
|
||||||
String anonymousCowardName,
|
String anonymousCowardName,
|
||||||
Date when) {
|
Date when) {
|
||||||
this.migration = migration;
|
this.migration = migration;
|
||||||
this.repoManager = repoManager;
|
this.repoManager = repoManager;
|
||||||
this.updateFactory = updateFactory;
|
|
||||||
this.ctl = ctl;
|
this.ctl = ctl;
|
||||||
this.serverIdent = serverIdent;
|
this.serverIdent = serverIdent;
|
||||||
this.anonymousCowardName = anonymousCowardName;
|
this.anonymousCowardName = anonymousCowardName;
|
||||||
this.when = when;
|
this.when = when;
|
||||||
|
checkArgument(
|
||||||
|
(ctl.getUser() instanceof IdentifiedUser)
|
||||||
|
|| (ctl.getUser() instanceof InternalUser),
|
||||||
|
"user must be IdentifiedUser or InternalUser: %s", ctl.getUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeNotes getChangeNotes() {
|
public ChangeNotes getChangeNotes() {
|
||||||
@ -77,8 +80,8 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
|
|||||||
return when;
|
return when;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IdentifiedUser getUser() {
|
public CurrentUser getUser() {
|
||||||
return ctl.getUser().asIdentifiedUser();
|
return ctl.getUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PatchSet.Id getPatchSetId() {
|
public PatchSet.Id getPatchSetId() {
|
||||||
@ -90,85 +93,15 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
|
|||||||
this.psId = psId;
|
this.psId = psId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() throws IOException {
|
private PersonIdent newAuthorIdent() {
|
||||||
if (migration.writeChanges() && getRevision() == null) {
|
CurrentUser u = getUser();
|
||||||
try (Repository repo = repoManager.openMetadataRepository(getProjectName())) {
|
if (u instanceof IdentifiedUser) {
|
||||||
load(repo);
|
return ChangeNoteUtil.newIdent(u.asIdentifiedUser().getAccount(), when,
|
||||||
} catch (ConfigInvalidException e) {
|
serverIdent, anonymousCowardName);
|
||||||
throw new IOException(e);
|
} else if (u instanceof InternalUser) {
|
||||||
}
|
return serverIdent;
|
||||||
}
|
}
|
||||||
}
|
throw new IllegalStateException();
|
||||||
|
|
||||||
public void setInserter(ObjectInserter inserter) {
|
|
||||||
this.inserter = inserter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BatchMetaDataUpdate openUpdate(MetaDataUpdate update) throws IOException {
|
|
||||||
throw new UnsupportedOperationException("use openUpdate()");
|
|
||||||
}
|
|
||||||
|
|
||||||
public BatchMetaDataUpdate openUpdate() throws IOException {
|
|
||||||
return openUpdateInBatch(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BatchMetaDataUpdate openUpdateInBatch(BatchRefUpdate bru)
|
|
||||||
throws IOException {
|
|
||||||
if (migration.writeChanges()) {
|
|
||||||
load();
|
|
||||||
Project.NameKey p = getProjectName();
|
|
||||||
MetaDataUpdate md = updateFactory.create(
|
|
||||||
p, repoManager.openMetadataRepository(p), getUser(), bru);
|
|
||||||
md.setAllowEmpty(true);
|
|
||||||
return super.openUpdate(md);
|
|
||||||
}
|
|
||||||
return new BatchMetaDataUpdate() {
|
|
||||||
@Override
|
|
||||||
public void write(CommitBuilder commit) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(VersionedMetaData config, CommitBuilder commit) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RevCommit createRef(String refName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRef(String refName) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RevCommit commit() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RevCommit commitAt(ObjectId revision) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RevCommit commit(MetaDataUpdate md) throws IOException {
|
|
||||||
throw new UnsupportedOperationException("use commit()");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLoad() throws IOException, ConfigInvalidException {
|
|
||||||
//Do nothing; just reads the current revision.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PersonIdent newIdent(Account author, Date when) {
|
protected PersonIdent newIdent(Account author, Date when) {
|
||||||
@ -176,10 +109,6 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
|
|||||||
anonymousCowardName);
|
anonymousCowardName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Writes commit to a BatchMetaDataUpdate without committing the batch. */
|
|
||||||
public abstract void writeCommit(BatchMetaDataUpdate batch)
|
|
||||||
throws OrmException, IOException;
|
|
||||||
|
|
||||||
/** Whether no updates have been done. */
|
/** Whether no updates have been done. */
|
||||||
public abstract boolean isEmpty();
|
public abstract boolean isEmpty();
|
||||||
|
|
||||||
@ -188,4 +117,71 @@ public abstract class AbstractChangeUpdate extends VersionedMetaData {
|
|||||||
* which is not necessarily the same as the change's project.
|
* which is not necessarily the same as the change's project.
|
||||||
*/
|
*/
|
||||||
protected abstract Project.NameKey getProjectName();
|
protected abstract Project.NameKey getProjectName();
|
||||||
|
|
||||||
|
protected abstract String getRefName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply this update to the given inserter.
|
||||||
|
*
|
||||||
|
* @param rw walk for reading back any objects needed for the update.
|
||||||
|
* @param ins inserter to write to; callers should not flush.
|
||||||
|
* @param curr the current tip of the branch prior to this update.
|
||||||
|
* @return commit ID produced by inserting this update's commit, or null if
|
||||||
|
* this update is a no-op and should be skipped. The zero ID is a valid
|
||||||
|
* return value, and indicates the ref should be deleted.
|
||||||
|
* @throws OrmException if a Gerrit-level error occurred.
|
||||||
|
* @throws IOException if a lower-level error occurred.
|
||||||
|
*/
|
||||||
|
final ObjectId apply(RevWalk rw, ObjectInserter ins, ObjectId curr)
|
||||||
|
throws OrmException, IOException {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ObjectId z = ObjectId.zeroId();
|
||||||
|
CommitBuilder cb = applyImpl(rw, ins, curr);
|
||||||
|
if (cb == null) {
|
||||||
|
result = z;
|
||||||
|
return z; // Impl intends to delete the ref.
|
||||||
|
}
|
||||||
|
cb.setAuthor(newAuthorIdent());
|
||||||
|
cb.setCommitter(new PersonIdent(serverIdent, when));
|
||||||
|
if (!curr.equals(z)) {
|
||||||
|
cb.setParentId(curr);
|
||||||
|
} else {
|
||||||
|
cb.setParentIds(); // Ref is currently nonexistent, commit has no parents.
|
||||||
|
}
|
||||||
|
if (cb.getTreeId() == null) {
|
||||||
|
if (curr.equals(z)) {
|
||||||
|
cb.setTreeId(emptyTree(ins)); // No parent, assume empty tree.
|
||||||
|
} else {
|
||||||
|
RevCommit p = rw.parseCommit(curr);
|
||||||
|
cb.setTreeId(p.getTree()); // Copy tree from parent.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = ins.insert(cb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a commit containing the contents of this update.
|
||||||
|
*
|
||||||
|
* @param ins inserter to write to; callers should not flush.
|
||||||
|
* @return a new commit builder representing this commit, or null to indicate
|
||||||
|
* the meta ref should be deleted as a result of this update. The parent,
|
||||||
|
* author, and committer fields in the return value are always
|
||||||
|
* overwritten. The tree ID may be unset by this method, which indicates
|
||||||
|
* to the caller that it should be copied from the parent commit.
|
||||||
|
* @throws OrmException if a Gerrit-level error occurred.
|
||||||
|
* @throws IOException if a lower-level error occurred.
|
||||||
|
*/
|
||||||
|
protected abstract CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins,
|
||||||
|
ObjectId curr) throws OrmException, IOException;
|
||||||
|
|
||||||
|
ObjectId getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectId emptyTree(ObjectInserter ins) throws IOException {
|
||||||
|
return ins.insert(Constants.OBJ_TREE, new byte[] {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,16 +14,16 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.notedb;
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.gerrit.server.notedb.CommentsInNotesUtil.addCommentToMap;
|
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||||
|
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.auto.value.AutoValue;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
@ -32,7 +32,6 @@ import com.google.gerrit.server.IdentifiedUser;
|
|||||||
import com.google.gerrit.server.config.AllUsersName;
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
import com.google.gerrit.server.config.AnonymousCowardName;
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
@ -41,18 +40,17 @@ import com.google.inject.assistedinject.AssistedInject;
|
|||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.notes.NoteMap;
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single delta to apply atomically to a change.
|
* A single delta to apply atomically to a change.
|
||||||
@ -68,14 +66,23 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
|
|||||||
ChangeDraftUpdate create(ChangeControl ctl, Date when);
|
ChangeDraftUpdate create(ChangeControl ctl, Date when);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AutoValue
|
||||||
|
static abstract class Key {
|
||||||
|
abstract RevId revId();
|
||||||
|
abstract PatchLineComment.Key key();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Key key(PatchLineComment c) {
|
||||||
|
return new AutoValue_ChangeDraftUpdate_Key(c.getRevId(), c.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
private final AllUsersName draftsProject;
|
private final AllUsersName draftsProject;
|
||||||
private final Account.Id accountId;
|
private final Account.Id accountId;
|
||||||
private final CommentsInNotesUtil commentsUtil;
|
private final CommentsInNotesUtil commentsUtil;
|
||||||
private final ChangeNotes changeNotes;
|
|
||||||
private final DraftCommentNotes draftNotes;
|
|
||||||
|
|
||||||
private List<PatchLineComment> upsertComments;
|
// TODO: can go back to a list?
|
||||||
private List<PatchLineComment> deleteComments;
|
private Map<Key, PatchLineComment> put;
|
||||||
|
private Set<Key> delete;
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
private ChangeDraftUpdate(
|
private ChangeDraftUpdate(
|
||||||
@ -83,187 +90,129 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
|
|||||||
@AnonymousCowardName String anonymousCowardName,
|
@AnonymousCowardName String anonymousCowardName,
|
||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
MetaDataUpdate.User updateFactory,
|
|
||||||
DraftCommentNotes.Factory draftNotesFactory,
|
|
||||||
AllUsersName allUsers,
|
AllUsersName allUsers,
|
||||||
CommentsInNotesUtil commentsUtil,
|
CommentsInNotesUtil commentsUtil,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
@Assisted Date when) throws OrmException {
|
@Assisted Date when) {
|
||||||
super(migration, repoManager, updateFactory, ctl, serverIdent,
|
super(migration, repoManager, ctl, serverIdent, anonymousCowardName, when);
|
||||||
anonymousCowardName, when);
|
|
||||||
this.draftsProject = allUsers;
|
this.draftsProject = allUsers;
|
||||||
this.commentsUtil = commentsUtil;
|
this.commentsUtil = commentsUtil;
|
||||||
checkState(ctl.getUser().isIdentifiedUser(),
|
checkState(ctl.getUser().isIdentifiedUser(),
|
||||||
"Current user must be identified");
|
"Current user must be identified");
|
||||||
IdentifiedUser user = ctl.getUser().asIdentifiedUser();
|
IdentifiedUser user = ctl.getUser().asIdentifiedUser();
|
||||||
this.accountId = user.getAccountId();
|
this.accountId = user.getAccountId();
|
||||||
this.changeNotes = getChangeNotes().load();
|
|
||||||
this.draftNotes = draftNotesFactory.create(ctl.getId(),
|
|
||||||
user.getAccountId());
|
|
||||||
|
|
||||||
this.upsertComments = Lists.newArrayList();
|
this.put = new HashMap<>();
|
||||||
this.deleteComments = Lists.newArrayList();
|
this.delete = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertComment(PatchLineComment c) throws OrmException {
|
public void putComment(PatchLineComment c) {
|
||||||
verifyComment(c);
|
verifyComment(c);
|
||||||
checkArgument(c.getStatus() == Status.DRAFT,
|
checkArgument(c.getStatus() == PatchLineComment.Status.DRAFT,
|
||||||
"Cannot insert a published comment into a ChangeDraftUpdate");
|
"Cannot insert a published comment into a ChangeDraftUpdate");
|
||||||
if (migration.readChanges()) {
|
put.put(key(c), c);
|
||||||
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) {
|
public void deleteComment(PatchLineComment c) {
|
||||||
verifyComment(c);
|
verifyComment(c);
|
||||||
checkArgument(c.getStatus() == Status.DRAFT,
|
delete.add(key(c));
|
||||||
"Cannot upsert a published comment into a ChangeDraftUpdate");
|
|
||||||
upsertComments.add(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateComment(PatchLineComment c) throws OrmException {
|
public void deleteComment(RevId revId, PatchLineComment.Key key) {
|
||||||
verifyComment(c);
|
delete.add(new AutoValue_ChangeDraftUpdate_Key(revId, key));
|
||||||
checkArgument(c.getStatus() == Status.DRAFT,
|
|
||||||
"Cannot update a published comment into a ChangeDraftUpdate");
|
|
||||||
// Here, we check to see if this comment existed previously as a draft.
|
|
||||||
// However, this could cause a race condition if there is a delete and an
|
|
||||||
// update operation happening concurrently (or two deletes) and they both
|
|
||||||
// believe that the comment exists. If a delete happens first, then
|
|
||||||
// the update will fail. However, this is an acceptable risk since the
|
|
||||||
// caller wanted the comment deleted anyways, so the end result will be the
|
|
||||||
// same either way.
|
|
||||||
if (migration.readChanges()) {
|
|
||||||
checkArgument(draftNotes.load().containsComment(c),
|
|
||||||
"Cannot update this comment because it didn't exist previously");
|
|
||||||
}
|
|
||||||
upsertComments.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteComment(PatchLineComment c) throws OrmException {
|
|
||||||
verifyComment(c);
|
|
||||||
// See the comment above about potential race condition.
|
|
||||||
if (migration.readChanges()) {
|
|
||||||
checkArgument(draftNotes.load().containsComment(c),
|
|
||||||
"Cannot delete this comment because it didn't previously exist as a"
|
|
||||||
+ " draft");
|
|
||||||
}
|
|
||||||
if (migration.writeChanges()) {
|
|
||||||
if (draftNotes.load().containsComment(c)) {
|
|
||||||
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) throws OrmException {
|
|
||||||
if (draftNotes.load().containsComment(c)) {
|
|
||||||
verifyComment(c);
|
|
||||||
deleteComments.add(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyComment(PatchLineComment comment) {
|
private void verifyComment(PatchLineComment comment) {
|
||||||
if (migration.writeChanges()) {
|
|
||||||
checkArgument(comment.getRevId() != null);
|
|
||||||
}
|
|
||||||
checkArgument(comment.getAuthor().equals(accountId),
|
checkArgument(comment.getAuthor().equals(accountId),
|
||||||
"The author for the following comment does not match the author of"
|
"The author for the following comment does not match the author of"
|
||||||
+ " this ChangeDraftUpdate (%s): %s", accountId, comment);
|
+ " this ChangeDraftUpdate (%s): %s", accountId, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the tree id for the updated tree */
|
/** @return the tree id for the updated tree */
|
||||||
private ObjectId storeCommentsInNotes(AtomicBoolean removedAllComments)
|
private ObjectId storeCommentsInNotes(RevWalk rw, ObjectInserter ins,
|
||||||
throws OrmException, IOException {
|
ObjectId curr) throws ConfigInvalidException, OrmException, IOException {
|
||||||
if (isEmpty()) {
|
RevisionNoteMap rnm = getRevisionNoteMap(rw, curr);
|
||||||
return null;
|
Set<RevId> updatedRevs =
|
||||||
|
Sets.newHashSetWithExpectedSize(rnm.revisionNotes.size());
|
||||||
|
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
|
||||||
|
|
||||||
|
for (PatchLineComment c : put.values()) {
|
||||||
|
if (!delete.contains(key(c))) {
|
||||||
|
cache.get(c.getRevId()).putComment(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Key k : delete) {
|
||||||
|
cache.get(k.revId()).deleteComment(k.key());
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteMap noteMap = draftNotes.load().getNoteMap();
|
Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
|
||||||
if (noteMap == null) {
|
|
||||||
noteMap = NoteMap.newEmptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<RevId, List<PatchLineComment>> allComments = new HashMap<>();
|
|
||||||
|
|
||||||
boolean hasComments = false;
|
boolean hasComments = false;
|
||||||
int n = deleteComments.size() + upsertComments.size();
|
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
||||||
Set<RevId> updatedRevs = Sets.newHashSetWithExpectedSize(n);
|
updatedRevs.add(e.getKey());
|
||||||
Set<PatchLineComment.Key> updatedKeys = Sets.newHashSetWithExpectedSize(n);
|
ObjectId id = ObjectId.fromString(e.getKey().get());
|
||||||
for (PatchLineComment c : deleteComments) {
|
byte[] data = e.getValue().build(commentsUtil);
|
||||||
allComments.put(c.getRevId(), new ArrayList<PatchLineComment>());
|
if (data.length == 0) {
|
||||||
updatedRevs.add(c.getRevId());
|
rnm.noteMap.remove(id);
|
||||||
updatedKeys.add(c.getKey());
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
for (PatchLineComment c : upsertComments) {
|
|
||||||
hasComments = true;
|
|
||||||
addCommentToMap(allComments, c);
|
|
||||||
updatedRevs.add(c.getRevId());
|
|
||||||
updatedKeys.add(c.getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-add old comments for updated revisions so the new note contents
|
|
||||||
// includes both old and new comments merged in the right order.
|
|
||||||
//
|
|
||||||
// writeCommentsToNoteMap doesn't touch notes for SHA-1s that are not
|
|
||||||
// mentioned in the input map, so by omitting comments for those revisions,
|
|
||||||
// we avoid the work of having to re-serialize identical comment data for
|
|
||||||
// those revisions.
|
|
||||||
ListMultimap<RevId, PatchLineComment> existing =
|
|
||||||
draftNotes.getComments();
|
|
||||||
for (Map.Entry<RevId, PatchLineComment> e : existing.entries()) {
|
|
||||||
PatchLineComment c = e.getValue();
|
|
||||||
if (updatedRevs.contains(c.getRevId())
|
|
||||||
&& !updatedKeys.contains(c.getKey())) {
|
|
||||||
hasComments = true;
|
hasComments = true;
|
||||||
addCommentToMap(allComments, e.getValue());
|
ObjectId dataBlob = ins.insert(OBJ_BLOB, data);
|
||||||
|
rnm.noteMap.set(id, dataBlob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we touched every revision and there are no comments left, set the flag
|
// If we touched every revision and there are no comments left, tell the
|
||||||
// for the caller to delete the entire ref.
|
// caller to delete the entire ref.
|
||||||
boolean touchedAllRevs = updatedRevs.equals(existing.keySet());
|
boolean touchedAllRevs = updatedRevs.equals(rnm.revisionNotes.keySet());
|
||||||
if (touchedAllRevs && !hasComments) {
|
if (touchedAllRevs && !hasComments) {
|
||||||
removedAllComments.set(touchedAllRevs && !hasComments);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
commentsUtil.writeCommentsToNoteMap(noteMap, allComments, inserter);
|
return rnm.noteMap.writeTree(ins);
|
||||||
return noteMap.writeTree(inserter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RevCommit commit() throws IOException {
|
private RevisionNoteMap getRevisionNoteMap(RevWalk rw, ObjectId curr)
|
||||||
BatchMetaDataUpdate batch = openUpdate();
|
throws ConfigInvalidException, OrmException, IOException {
|
||||||
try {
|
if (migration.readChanges()) {
|
||||||
writeCommit(batch);
|
// If reading from changes is enabled, then the old DraftCommentNotes
|
||||||
return batch.commit();
|
// already parsed the revision notes. We can reuse them as long as the ref
|
||||||
} catch (OrmException e) {
|
// hasn't advanced.
|
||||||
throw new IOException(e);
|
DraftCommentNotes draftNotes =
|
||||||
} finally {
|
ctl.getNotes().load().getDraftCommentNotes();
|
||||||
batch.close();
|
if (draftNotes != null) {
|
||||||
|
ObjectId idFromNotes =
|
||||||
|
firstNonNull(draftNotes.getRevision(), ObjectId.zeroId());
|
||||||
|
if (idFromNotes.equals(curr)) {
|
||||||
|
return checkNotNull(ctl.getNotes().revisionNoteMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
NoteMap noteMap;
|
||||||
|
if (!curr.equals(ObjectId.zeroId())) {
|
||||||
|
noteMap = NoteMap.read(rw.getObjectReader(), rw.parseCommit(curr));
|
||||||
|
} else {
|
||||||
|
noteMap = NoteMap.newEmptyMap();
|
||||||
|
}
|
||||||
|
// Even though reading from changes might not be enabled, we need to
|
||||||
|
// parse any existing revision notes so we can merge them.
|
||||||
|
return RevisionNoteMap.parse(
|
||||||
|
ctl.getId(), rw.getObjectReader(), noteMap, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeCommit(BatchMetaDataUpdate batch)
|
protected CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins,
|
||||||
throws OrmException, IOException {
|
ObjectId curr) throws OrmException, IOException {
|
||||||
CommitBuilder builder = new CommitBuilder();
|
CommitBuilder cb = new CommitBuilder();
|
||||||
if (migration.writeChanges()) {
|
cb.setMessage("Update draft comments");
|
||||||
AtomicBoolean removedAllComments = new AtomicBoolean();
|
try {
|
||||||
ObjectId treeId = storeCommentsInNotes(removedAllComments);
|
ObjectId treeId = storeCommentsInNotes(rw, ins, curr);
|
||||||
if (removedAllComments.get()) {
|
if (treeId == null) {
|
||||||
batch.removeRef(getRefName());
|
return null; // Delete ref.
|
||||||
} else if (treeId != null) {
|
|
||||||
builder.setTreeId(treeId);
|
|
||||||
batch.write(builder);
|
|
||||||
}
|
}
|
||||||
|
cb.setTreeId(checkNotNull(treeId));
|
||||||
|
} catch (ConfigInvalidException e) {
|
||||||
|
throw new OrmException(e);
|
||||||
}
|
}
|
||||||
|
return cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -276,21 +225,9 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
|
|||||||
return RefNames.refsDraftComments(accountId, ctl.getId());
|
return RefNames.refsDraftComments(accountId, ctl.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("Update draft comments");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return deleteComments.isEmpty()
|
return delete.isEmpty()
|
||||||
&& upsertComments.isEmpty();
|
&& put.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@ import com.google.common.collect.ImmutableSortedMap;
|
|||||||
import com.google.common.collect.ImmutableSortedSet;
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.collect.Multimaps;
|
||||||
import com.google.common.collect.Ordering;
|
import com.google.common.collect.Ordering;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import com.google.common.util.concurrent.AsyncFunction;
|
import com.google.common.util.concurrent.AsyncFunction;
|
||||||
@ -70,7 +72,6 @@ import org.eclipse.jgit.lib.ObjectId;
|
|||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.notes.NoteMap;
|
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -398,10 +399,9 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
private ImmutableListMultimap<RevId, PatchLineComment> comments;
|
private ImmutableListMultimap<RevId, PatchLineComment> comments;
|
||||||
private ImmutableSet<String> hashtags;
|
private ImmutableSet<String> hashtags;
|
||||||
|
|
||||||
// Mutable note map state, only used by ChangeUpdate to make in-place editing
|
// Parsed note map state, used by ChangeUpdate to make in-place editing of
|
||||||
// of notes easier.
|
// notes easier.
|
||||||
NoteMap noteMap;
|
RevisionNoteMap revisionNoteMap;
|
||||||
Map<RevId, RevisionNote> revisionNotes;
|
|
||||||
|
|
||||||
private final AllUsersName allUsers;
|
private final AllUsersName allUsers;
|
||||||
private DraftCommentNotes draftCommentNotes;
|
private DraftCommentNotes draftCommentNotes;
|
||||||
@ -477,7 +477,25 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
public ImmutableListMultimap<RevId, PatchLineComment> getDraftComments(
|
public ImmutableListMultimap<RevId, PatchLineComment> getDraftComments(
|
||||||
Account.Id author) throws OrmException {
|
Account.Id author) throws OrmException {
|
||||||
loadDraftComments(author);
|
loadDraftComments(author);
|
||||||
return draftCommentNotes.getComments();
|
final Multimap<RevId, PatchLineComment> published = comments;
|
||||||
|
// Filter out any draft comments that also exist in the published map, in
|
||||||
|
// case the update to All-Users to delete them during the publish operation
|
||||||
|
// failed.
|
||||||
|
Multimap<RevId, PatchLineComment> filtered = Multimaps.filterEntries(
|
||||||
|
draftCommentNotes.getComments(),
|
||||||
|
new Predicate<Map.Entry<RevId, PatchLineComment>>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Map.Entry<RevId, PatchLineComment> in) {
|
||||||
|
for (PatchLineComment c : published.get(in.getKey())) {
|
||||||
|
if (c.getKey().equals(in.getValue().getKey())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ImmutableListMultimap.copyOf(
|
||||||
|
filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -518,15 +536,6 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the NoteMap */
|
|
||||||
NoteMap getNoteMap() {
|
|
||||||
return noteMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<RevId, RevisionNote> getRevisionNotes() {
|
|
||||||
return revisionNotes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getRefName() {
|
protected String getRefName() {
|
||||||
return ChangeNoteUtil.changeRefName(getChangeId());
|
return ChangeNoteUtil.changeRefName(getChangeId());
|
||||||
@ -557,8 +566,7 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
changeMessagesByPatchSet = parser.buildMessagesByPatchSet();
|
changeMessagesByPatchSet = parser.buildMessagesByPatchSet();
|
||||||
allChangeMessages = parser.buildAllMessages();
|
allChangeMessages = parser.buildAllMessages();
|
||||||
comments = ImmutableListMultimap.copyOf(parser.comments);
|
comments = ImmutableListMultimap.copyOf(parser.comments);
|
||||||
noteMap = parser.noteMap;
|
revisionNoteMap = parser.revisionNoteMap;
|
||||||
revisionNotes = parser.revisionNotes;
|
|
||||||
change.setKey(new Change.Key(parser.changeId));
|
change.setKey(new Change.Key(parser.changeId));
|
||||||
change.setDest(new Branch.NameKey(project, parser.branch));
|
change.setDest(new Branch.NameKey(project, parser.branch));
|
||||||
change.setTopic(Strings.emptyToNull(parser.topic));
|
change.setTopic(Strings.emptyToNull(parser.topic));
|
||||||
|
@ -69,7 +69,6 @@ import org.eclipse.jgit.lib.ObjectId;
|
|||||||
import org.eclipse.jgit.lib.ObjectReader;
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.notes.Note;
|
|
||||||
import org.eclipse.jgit.notes.NoteMap;
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
import org.eclipse.jgit.revwalk.FooterKey;
|
import org.eclipse.jgit.revwalk.FooterKey;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
@ -101,7 +100,6 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
final Multimap<RevId, PatchLineComment> comments;
|
final Multimap<RevId, PatchLineComment> comments;
|
||||||
final TreeMap<PatchSet.Id, PatchSet> patchSets;
|
final TreeMap<PatchSet.Id, PatchSet> patchSets;
|
||||||
final Map<PatchSet.Id, PatchSetState> patchSetStates;
|
final Map<PatchSet.Id, PatchSetState> patchSetStates;
|
||||||
final Map<RevId, RevisionNote> revisionNotes;
|
|
||||||
|
|
||||||
String branch;
|
String branch;
|
||||||
Change.Status status;
|
Change.Status status;
|
||||||
@ -115,7 +113,7 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
String originalSubject;
|
String originalSubject;
|
||||||
String submissionId;
|
String submissionId;
|
||||||
PatchSet.Id currentPatchSetId;
|
PatchSet.Id currentPatchSetId;
|
||||||
NoteMap noteMap;
|
RevisionNoteMap revisionNoteMap;
|
||||||
|
|
||||||
private final Change.Id id;
|
private final Change.Id id;
|
||||||
private final ObjectId tip;
|
private final ObjectId tip;
|
||||||
@ -142,7 +140,6 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
comments = ArrayListMultimap.create();
|
comments = ArrayListMultimap.create();
|
||||||
patchSets = Maps.newTreeMap(ReviewDbUtil.intKeyOrdering());
|
patchSets = Maps.newTreeMap(ReviewDbUtil.intKeyOrdering());
|
||||||
patchSetStates = Maps.newHashMap();
|
patchSetStates = Maps.newHashMap();
|
||||||
revisionNotes = Maps.newHashMap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -217,7 +214,9 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Account.Id accountId = parseIdent(commit);
|
Account.Id accountId = parseIdent(commit);
|
||||||
ownerId = accountId;
|
if (accountId != null) {
|
||||||
|
ownerId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
if (changeId == null) {
|
if (changeId == null) {
|
||||||
changeId = parseChangeId(commit);
|
changeId = parseChangeId(commit);
|
||||||
@ -339,6 +338,10 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
|
|
||||||
private void parsePatchSet(PatchSet.Id psId, ObjectId rev,
|
private void parsePatchSet(PatchSet.Id psId, ObjectId rev,
|
||||||
Account.Id accountId, Timestamp ts) throws ConfigInvalidException {
|
Account.Id accountId, Timestamp ts) throws ConfigInvalidException {
|
||||||
|
if (accountId == null) {
|
||||||
|
throw parseException(
|
||||||
|
"patch set %s requires an identified user as uploader", psId.get());
|
||||||
|
}
|
||||||
PatchSet ps = patchSets.get(psId);
|
PatchSet ps = patchSets.get(psId);
|
||||||
if (ps == null) {
|
if (ps == null) {
|
||||||
ps = new PatchSet(psId);
|
ps = new PatchSet(psId);
|
||||||
@ -497,19 +500,18 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
throws IOException, ConfigInvalidException {
|
throws IOException, ConfigInvalidException {
|
||||||
ObjectReader reader = walk.getObjectReader();
|
ObjectReader reader = walk.getObjectReader();
|
||||||
RevCommit tipCommit = walk.parseCommit(tip);
|
RevCommit tipCommit = walk.parseCommit(tip);
|
||||||
noteMap = NoteMap.read(reader, tipCommit);
|
revisionNoteMap = RevisionNoteMap.parse(
|
||||||
|
id, reader, NoteMap.read(reader, tipCommit), false);
|
||||||
|
Map<RevId, RevisionNote> rns = revisionNoteMap.revisionNotes;
|
||||||
|
|
||||||
for (Note note : noteMap) {
|
for (Map.Entry<RevId, RevisionNote> e : rns.entrySet()) {
|
||||||
RevisionNote rn = new RevisionNote(id, reader, note.getData());
|
for (PatchLineComment plc : e.getValue().comments) {
|
||||||
RevId revId = new RevId(note.name());
|
comments.put(e.getKey(), plc);
|
||||||
revisionNotes.put(revId, rn);
|
|
||||||
for (PatchLineComment plc : rn.comments) {
|
|
||||||
comments.put(revId, plc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (PatchSet ps : patchSets.values()) {
|
for (PatchSet ps : patchSets.values()) {
|
||||||
RevisionNote rn = revisionNotes.get(ps.getRevision());
|
RevisionNote rn = rns.get(ps.getRevision());
|
||||||
if (rn != null && rn.pushCert != null) {
|
if (rn != null && rn.pushCert != null) {
|
||||||
ps.setPushCertificate(rn.pushCert);
|
ps.setPushCertificate(rn.pushCert);
|
||||||
}
|
}
|
||||||
@ -518,6 +520,10 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
|
|
||||||
private void parseApproval(PatchSet.Id psId, Account.Id accountId,
|
private void parseApproval(PatchSet.Id psId, Account.Id accountId,
|
||||||
Timestamp ts, String line) throws ConfigInvalidException {
|
Timestamp ts, String line) throws ConfigInvalidException {
|
||||||
|
if (accountId == null) {
|
||||||
|
throw parseException(
|
||||||
|
"patch set %s requires an identified user as uploader", psId.get());
|
||||||
|
}
|
||||||
if (line.startsWith("-")) {
|
if (line.startsWith("-")) {
|
||||||
parseRemoveApproval(psId, accountId, line);
|
parseRemoveApproval(psId, accountId, line);
|
||||||
} else {
|
} else {
|
||||||
@ -665,6 +671,14 @@ class ChangeNotesParser implements AutoCloseable {
|
|||||||
|
|
||||||
private Account.Id parseIdent(RevCommit commit)
|
private Account.Id parseIdent(RevCommit commit)
|
||||||
throws ConfigInvalidException {
|
throws ConfigInvalidException {
|
||||||
|
// Check if the author name/email is the same as the committer name/email,
|
||||||
|
// i.e. was the server ident at the time this commit was made.
|
||||||
|
PersonIdent a = commit.getAuthorIdent();
|
||||||
|
PersonIdent c = commit.getCommitterIdent();
|
||||||
|
if (a.getName().equals(c.getName())
|
||||||
|
&& a.getEmailAddress().equals(c.getEmailAddress())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return parseIdent(commit.getAuthorIdent());
|
return parseIdent(commit.getAuthorIdent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,22 +40,23 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.client.StarredChange;
|
import com.google.gerrit.reviewdb.client.StarredChange;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.InternalUser;
|
||||||
import com.google.gerrit.server.PatchLineCommentsUtil;
|
import com.google.gerrit.server.PatchLineCommentsUtil;
|
||||||
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
|
import com.google.gerrit.server.git.ChainedReceiveCommands;
|
||||||
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.patch.PatchListCache;
|
import com.google.gerrit.server.patch.PatchListCache;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.gwtorm.server.SchemaFactory;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.util.Providers;
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
|
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectInserter;
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
|
||||||
import org.eclipse.jgit.lib.RefUpdate;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
@ -78,61 +79,75 @@ public class ChangeRebuilder {
|
|||||||
private static final long TS_WINDOW_MS =
|
private static final long TS_WINDOW_MS =
|
||||||
TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
|
TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
private final Provider<ReviewDb> dbProvider;
|
private final SchemaFactory<ReviewDb> schemaFactory;
|
||||||
|
private final GitRepositoryManager repoManager;
|
||||||
private final ChangeControl.GenericFactory controlFactory;
|
private final ChangeControl.GenericFactory controlFactory;
|
||||||
private final IdentifiedUser.GenericFactory userFactory;
|
private final IdentifiedUser.GenericFactory userFactory;
|
||||||
|
private final InternalUser.Factory internalUserFactory;
|
||||||
private final PatchListCache patchListCache;
|
private final PatchListCache patchListCache;
|
||||||
private final ChangeUpdate.Factory updateFactory;
|
private final ChangeUpdate.Factory updateFactory;
|
||||||
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
||||||
|
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ChangeRebuilder(Provider<ReviewDb> dbProvider,
|
ChangeRebuilder(SchemaFactory<ReviewDb> schemaFactory,
|
||||||
|
GitRepositoryManager repoManager,
|
||||||
ChangeControl.GenericFactory controlFactory,
|
ChangeControl.GenericFactory controlFactory,
|
||||||
IdentifiedUser.GenericFactory userFactory,
|
IdentifiedUser.GenericFactory userFactory,
|
||||||
|
InternalUser.Factory internalUserFactory,
|
||||||
PatchListCache patchListCache,
|
PatchListCache patchListCache,
|
||||||
ChangeUpdate.Factory updateFactory,
|
ChangeUpdate.Factory updateFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory) {
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
this.dbProvider = dbProvider;
|
NoteDbUpdateManager.Factory updateManagerFactory) {
|
||||||
|
this.schemaFactory = schemaFactory;
|
||||||
|
this.repoManager = repoManager;
|
||||||
this.controlFactory = controlFactory;
|
this.controlFactory = controlFactory;
|
||||||
this.userFactory = userFactory;
|
this.userFactory = userFactory;
|
||||||
|
this.internalUserFactory = internalUserFactory;
|
||||||
this.patchListCache = patchListCache;
|
this.patchListCache = patchListCache;
|
||||||
this.updateFactory = updateFactory;
|
this.updateFactory = updateFactory;
|
||||||
this.draftUpdateFactory = draftUpdateFactory;
|
this.draftUpdateFactory = draftUpdateFactory;
|
||||||
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<?> rebuildAsync(final Change change,
|
public ListenableFuture<?> rebuildAsync(final Change.Id id,
|
||||||
ListeningExecutorService executor, final BatchRefUpdate bru,
|
ListeningExecutorService executor) {
|
||||||
final BatchRefUpdate bruForDrafts, final Repository changeRepo,
|
|
||||||
final Repository allUsersRepo) {
|
|
||||||
return executor.submit(new Callable<Void>() {
|
return executor.submit(new Callable<Void>() {
|
||||||
@Override
|
@Override
|
||||||
public Void call() throws Exception {
|
public Void call() throws Exception {
|
||||||
rebuild(change, bru, bruForDrafts, changeRepo, allUsersRepo);
|
try (ReviewDb db = schemaFactory.open()) {
|
||||||
|
rebuild(db, id);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rebuild(Change change, BatchRefUpdate bru,
|
public void rebuild(ReviewDb db, Change.Id changeId)
|
||||||
BatchRefUpdate bruAllUsers, Repository changeRepo,
|
throws NoSuchChangeException, IOException, OrmException {
|
||||||
Repository allUsersRepo) throws NoSuchChangeException, IOException,
|
Change change = db.changes().get(changeId);
|
||||||
OrmException {
|
if (change == null) {
|
||||||
ReviewDb db = dbProvider.get();
|
return;
|
||||||
Change.Id changeId = change.getId();
|
}
|
||||||
|
NoteDbUpdateManager manager =
|
||||||
|
updateManagerFactory.create(change.getProject());
|
||||||
|
|
||||||
// We will rebuild all events, except for draft comments, in buckets based
|
// We will rebuild all events, except for draft comments, in buckets based
|
||||||
// on author and timestamp. However, all draft comments for a given change
|
// on author and timestamp.
|
||||||
// and author will be written as one commit in the notedb.
|
|
||||||
List<Event> events = Lists.newArrayList();
|
List<Event> events = Lists.newArrayList();
|
||||||
Multimap<Account.Id, PatchLineCommentEvent> draftCommentEvents =
|
Multimap<Account.Id, PatchLineCommentEvent> draftCommentEvents =
|
||||||
ArrayListMultimap.create();
|
ArrayListMultimap.create();
|
||||||
|
|
||||||
try (RevWalk rw = new RevWalk(changeRepo)) {
|
Repository changeMetaRepo = manager.getChangeRepo();
|
||||||
events.addAll(getHashtagsEvents(change, changeRepo, rw));
|
events.addAll(getHashtagsEvents(change, manager));
|
||||||
|
|
||||||
deleteRef(change, changeRepo);
|
// Delete ref only after hashtags have been read
|
||||||
|
deleteRef(change, changeMetaRepo, manager.getChangeCommands());
|
||||||
|
|
||||||
|
try (Repository codeRepo = repoManager.openRepository(change.getProject());
|
||||||
|
RevWalk codeRw = new RevWalk(codeRepo)) {
|
||||||
for (PatchSet ps : db.patchSets().byChange(changeId)) {
|
for (PatchSet ps : db.patchSets().byChange(changeId)) {
|
||||||
events.add(new PatchSetEvent(change, ps, rw));
|
events.add(new PatchSetEvent(change, ps, codeRw));
|
||||||
for (PatchLineComment c : db.patchComments().byPatchSet(ps.getId())) {
|
for (PatchLineComment c : db.patchComments().byPatchSet(ps.getId())) {
|
||||||
PatchLineCommentEvent e =
|
PatchLineCommentEvent e =
|
||||||
new PatchLineCommentEvent(c, change, ps, patchListCache);
|
new PatchLineCommentEvent(c, change, ps, patchListCache);
|
||||||
@ -156,63 +171,51 @@ public class ChangeRebuilder {
|
|||||||
|
|
||||||
Collections.sort(events);
|
Collections.sort(events);
|
||||||
events.add(new FinalUpdatesEvent(change, notedbChange));
|
events.add(new FinalUpdatesEvent(change, notedbChange));
|
||||||
BatchMetaDataUpdate batch = null;
|
|
||||||
ChangeUpdate update = null;
|
ChangeUpdate update = null;
|
||||||
for (Event e : events) {
|
for (Event e : events) {
|
||||||
if (!sameUpdate(e, update)) {
|
if (!sameUpdate(e, update)) {
|
||||||
writeToBatch(batch, update, changeRepo);
|
if (update != null) {
|
||||||
IdentifiedUser user = userFactory.create(dbProvider, e.who);
|
manager.add(update);
|
||||||
|
}
|
||||||
|
CurrentUser user = e.who != null
|
||||||
|
? userFactory.create(Providers.of(db), e.who)
|
||||||
|
: internalUserFactory.create();
|
||||||
update = updateFactory.create(
|
update = updateFactory.create(
|
||||||
controlFactory.controlFor(db, change, user), e.when);
|
controlFactory.controlFor(db, change, user), e.when);
|
||||||
update.setPatchSetId(e.psId);
|
update.setPatchSetId(e.psId);
|
||||||
if (batch == null) {
|
|
||||||
batch = update.openUpdateInBatch(bru);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
e.apply(update);
|
e.apply(update);
|
||||||
}
|
}
|
||||||
if (batch != null) {
|
manager.add(update);
|
||||||
writeToBatch(batch, update, changeRepo);
|
|
||||||
|
|
||||||
// Since the BatchMetaDataUpdates generated by all ChangeRebuilders on a
|
|
||||||
// given project are backed by the same BatchRefUpdate, we need to
|
|
||||||
// synchronize on the BatchRefUpdate. Therefore, since commit on a
|
|
||||||
// BatchMetaDataUpdate is the only method that modifies a BatchRefUpdate,
|
|
||||||
// we can just synchronize this call.
|
|
||||||
synchronized (bru) {
|
|
||||||
batch.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Account.Id author : draftCommentEvents.keys()) {
|
for (Account.Id author : draftCommentEvents.keys()) {
|
||||||
IdentifiedUser user = userFactory.create(dbProvider, author);
|
IdentifiedUser user = userFactory.create(Providers.of(db), author);
|
||||||
ChangeDraftUpdate draftUpdate = null;
|
ChangeDraftUpdate draftUpdate = null;
|
||||||
BatchMetaDataUpdate batchForDrafts = null;
|
|
||||||
for (PatchLineCommentEvent e : draftCommentEvents.get(author)) {
|
for (PatchLineCommentEvent e : draftCommentEvents.get(author)) {
|
||||||
if (draftUpdate == null) {
|
if (!sameUpdate(e, draftUpdate)) {
|
||||||
|
if (draftUpdate != null) {
|
||||||
|
manager.add(draftUpdate);
|
||||||
|
}
|
||||||
draftUpdate = draftUpdateFactory.create(
|
draftUpdate = draftUpdateFactory.create(
|
||||||
controlFactory.controlFor(db, change, user), e.when);
|
controlFactory.controlFor(db, change, user), e.when);
|
||||||
draftUpdate.setPatchSetId(e.psId);
|
draftUpdate.setPatchSetId(e.psId);
|
||||||
batchForDrafts = draftUpdate.openUpdateInBatch(bruAllUsers);
|
|
||||||
}
|
}
|
||||||
e.applyDraft(draftUpdate);
|
e.applyDraft(draftUpdate);
|
||||||
}
|
}
|
||||||
writeToBatch(batchForDrafts, draftUpdate, allUsersRepo);
|
manager.add(draftUpdate);
|
||||||
synchronized(bruAllUsers) {
|
|
||||||
batchForDrafts.commit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createStarredChangesRefs(changeId, bruAllUsers, allUsersRepo);
|
createStarredChangesRefs(db, changeId, manager.getAllUsersCommands(),
|
||||||
|
manager.getAllUsersRepo());
|
||||||
|
manager.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createStarredChangesRefs(Change.Id changeId,
|
private void createStarredChangesRefs(ReviewDb db, Change.Id changeId,
|
||||||
BatchRefUpdate bruAllUsers, Repository allUsersRepo)
|
ChainedReceiveCommands allUsersCmds, Repository allUsersRepo)
|
||||||
throws IOException, OrmException {
|
throws IOException, OrmException {
|
||||||
ObjectId emptyTree = emptyTree(allUsersRepo);
|
ObjectId emptyTree = emptyTree(allUsersRepo);
|
||||||
for (StarredChange starred : dbProvider.get().starredChanges()
|
for (StarredChange starred : db.starredChanges().byChange(changeId)) {
|
||||||
.byChange(changeId)) {
|
allUsersCmds.add(new ReceiveCommand(ObjectId.zeroId(), emptyTree,
|
||||||
bruAllUsers.addCommand(new ReceiveCommand(ObjectId.zeroId(), emptyTree,
|
|
||||||
RefNames.refsStarredChanges(starred.getAccountId(), changeId)));
|
RefNames.refsStarredChanges(starred.getAccountId(), changeId)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,16 +229,18 @@ public class ChangeRebuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<HashtagsEvent> getHashtagsEvents(Change change,
|
private List<HashtagsEvent> getHashtagsEvents(Change change,
|
||||||
Repository changeRepo, RevWalk rw) throws IOException {
|
NoteDbUpdateManager manager) throws IOException {
|
||||||
String refName = ChangeNoteUtil.changeRefName(change.getId());
|
String refName = ChangeNoteUtil.changeRefName(change.getId());
|
||||||
Ref ref = changeRepo.exactRef(refName);
|
ObjectId old = manager.getChangeCommands()
|
||||||
if (ref == null) {
|
.getObjectId(manager.getChangeRepo(), refName);
|
||||||
|
if (old == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RevWalk rw = manager.getChangeRevWalk();
|
||||||
List<HashtagsEvent> events = new ArrayList<>();
|
List<HashtagsEvent> events = new ArrayList<>();
|
||||||
rw.reset();
|
rw.reset();
|
||||||
rw.markStart(rw.parseCommit(ref.getObjectId()));
|
rw.markStart(rw.parseCommit(old));
|
||||||
for (RevCommit commit : rw) {
|
for (RevCommit commit : rw) {
|
||||||
Account.Id authorId =
|
Account.Id authorId =
|
||||||
ChangeNoteUtil.parseIdent(commit.getAuthorIdent());
|
ChangeNoteUtil.parseIdent(commit.getAuthorIdent());
|
||||||
@ -277,40 +282,12 @@ public class ChangeRebuilder {
|
|||||||
return new PatchSet.Id(change.getId(), psId);
|
return new PatchSet.Id(change.getId(), psId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteRef(Change change, Repository changeRepo)
|
private void deleteRef(Change change, Repository repo,
|
||||||
throws IOException {
|
ChainedReceiveCommands cmds) throws IOException {
|
||||||
String refName = ChangeNoteUtil.changeRefName(change.getId());
|
String refName = ChangeNoteUtil.changeRefName(change.getId());
|
||||||
RefUpdate ru = changeRepo.updateRef(refName, true);
|
ObjectId old = cmds.getObjectId(repo, refName);
|
||||||
ru.setForceUpdate(true);
|
if (old != null) {
|
||||||
RefUpdate.Result result = ru.delete();
|
cmds.add(new ReceiveCommand(old, ObjectId.zeroId(), refName));
|
||||||
switch (result) {
|
|
||||||
case FORCED:
|
|
||||||
case NEW:
|
|
||||||
case NO_CHANGE:
|
|
||||||
break;
|
|
||||||
case FAST_FORWARD:
|
|
||||||
case IO_FAILURE:
|
|
||||||
case LOCK_FAILURE:
|
|
||||||
case NOT_ATTEMPTED:
|
|
||||||
case REJECTED:
|
|
||||||
case REJECTED_CURRENT_BRANCH:
|
|
||||||
case RENAMED:
|
|
||||||
default:
|
|
||||||
throw new IOException(
|
|
||||||
String.format("Failed to delete ref %s: %s", refName, result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeToBatch(BatchMetaDataUpdate batch,
|
|
||||||
AbstractChangeUpdate update, Repository repo) throws IOException,
|
|
||||||
OrmException {
|
|
||||||
if (update == null || update.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (ObjectInserter inserter = repo.newObjectInserter()) {
|
|
||||||
update.setInserter(inserter);
|
|
||||||
update.writeCommit(batch);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +295,7 @@ public class ChangeRebuilder {
|
|||||||
return when.getTime() / TS_WINDOW_MS;
|
return when.getTime() / TS_WINDOW_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean sameUpdate(Event event, ChangeUpdate update) {
|
private static boolean sameUpdate(Event event, AbstractChangeUpdate update) {
|
||||||
return update != null
|
return update != null
|
||||||
&& round(event.when) == round(update.getWhen())
|
&& round(event.when) == round(update.getWhen())
|
||||||
&& event.who.equals(update.getUser().getAccountId())
|
&& event.who.equals(update.getUser().getAccountId())
|
||||||
@ -441,14 +418,14 @@ public class ChangeRebuilder {
|
|||||||
if (c.getRevId() == null) {
|
if (c.getRevId() == null) {
|
||||||
setCommentRevId(c, cache, change, ps);
|
setCommentRevId(c, cache, change, ps);
|
||||||
}
|
}
|
||||||
update.insertComment(c);
|
update.putComment(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException {
|
void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException {
|
||||||
if (c.getRevId() == null) {
|
if (c.getRevId() == null) {
|
||||||
setCommentRevId(c, cache, change, ps);
|
setCommentRevId(c, cache, change, ps);
|
||||||
}
|
}
|
||||||
draftUpdate.insertComment(c);
|
draftUpdate.putComment(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.notedb;
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
@ -46,14 +47,12 @@ import com.google.gerrit.common.data.SubmitRecord;
|
|||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.account.AccountCache;
|
import com.google.gerrit.server.account.AccountCache;
|
||||||
import com.google.gerrit.server.config.AnonymousCowardName;
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.project.ProjectCache;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
import com.google.gerrit.server.util.LabelVote;
|
import com.google.gerrit.server.util.LabelVote;
|
||||||
@ -61,8 +60,10 @@ import com.google.gwtorm.server.OrmException;
|
|||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
import com.google.inject.assistedinject.AssistedInject;
|
import com.google.inject.assistedinject.AssistedInject;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.notes.NoteMap;
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
import org.eclipse.jgit.revwalk.FooterKey;
|
import org.eclipse.jgit.revwalk.FooterKey;
|
||||||
@ -72,7 +73,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -99,27 +100,29 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final AccountCache accountCache;
|
private final AccountCache accountCache;
|
||||||
private String commitSubject;
|
private final CommentsInNotesUtil commentsUtil;
|
||||||
private String subject;
|
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
||||||
|
private final NoteDbUpdateManager.Factory updateManagerFactory;
|
||||||
|
|
||||||
private final Table<String, Account.Id, Optional<Short>> approvals;
|
private final Table<String, Account.Id, Optional<Short>> approvals;
|
||||||
private final Map<Account.Id, ReviewerStateInternal> reviewers;
|
private final Map<Account.Id, ReviewerStateInternal> reviewers;
|
||||||
|
|
||||||
|
private String commitSubject;
|
||||||
|
private String subject;
|
||||||
private String changeId;
|
private String changeId;
|
||||||
private String branch;
|
private String branch;
|
||||||
private Change.Status status;
|
private Change.Status status;
|
||||||
private List<SubmitRecord> submitRecords;
|
private List<SubmitRecord> submitRecords;
|
||||||
private String submissionId;
|
private String submissionId;
|
||||||
private final CommentsInNotesUtil commentsUtil;
|
|
||||||
private List<PatchLineComment> comments;
|
private List<PatchLineComment> comments;
|
||||||
private String topic;
|
private String topic;
|
||||||
private ObjectId commit;
|
private ObjectId commit;
|
||||||
private Set<String> hashtags;
|
private Set<String> hashtags;
|
||||||
private String changeMessage;
|
private String changeMessage;
|
||||||
private ChangeNotes notes;
|
|
||||||
private PatchSetState psState;
|
private PatchSetState psState;
|
||||||
private Iterable<String> groups;
|
private Iterable<String> groups;
|
||||||
private String pushCert;
|
private String pushCert;
|
||||||
|
|
||||||
private final ChangeDraftUpdate.Factory draftUpdateFactory;
|
|
||||||
private ChangeDraftUpdate draftUpdate;
|
private ChangeDraftUpdate draftUpdate;
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
@ -129,13 +132,13 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
MetaDataUpdate.User updateFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
CommentsInNotesUtil commentsUtil) {
|
CommentsInNotesUtil commentsUtil) {
|
||||||
this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
|
this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
|
||||||
updateFactory, draftUpdateFactory,
|
updateManagerFactory, draftUpdateFactory,
|
||||||
projectCache, ctl, serverIdent.getWhen(), commentsUtil);
|
projectCache, ctl, serverIdent.getWhen(), commentsUtil);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,14 +149,14 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
MetaDataUpdate.User updateFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
@Assisted Date when,
|
@Assisted Date when,
|
||||||
CommentsInNotesUtil commentsUtil) {
|
CommentsInNotesUtil commentsUtil) {
|
||||||
this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
|
this(serverIdent, anonymousCowardName, repoManager, migration, accountCache,
|
||||||
updateFactory, draftUpdateFactory, ctl,
|
updateManagerFactory, draftUpdateFactory, ctl,
|
||||||
when,
|
when,
|
||||||
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
|
projectCache.get(getProjectName(ctl)).getLabelTypes().nameComparator(),
|
||||||
commentsUtil);
|
commentsUtil);
|
||||||
@ -170,17 +173,19 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
GitRepositoryManager repoManager,
|
GitRepositoryManager repoManager,
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
MetaDataUpdate.User updateFactory,
|
NoteDbUpdateManager.Factory updateManagerFactory,
|
||||||
ChangeDraftUpdate.Factory draftUpdateFactory,
|
ChangeDraftUpdate.Factory draftUpdateFactory,
|
||||||
@Assisted ChangeControl ctl,
|
@Assisted ChangeControl ctl,
|
||||||
@Assisted Date when,
|
@Assisted Date when,
|
||||||
@Assisted Comparator<String> labelNameComparator,
|
@Assisted Comparator<String> labelNameComparator,
|
||||||
CommentsInNotesUtil commentsUtil) {
|
CommentsInNotesUtil commentsUtil) {
|
||||||
super(migration, repoManager, updateFactory, ctl, serverIdent,
|
super(migration, repoManager, ctl, serverIdent,
|
||||||
anonymousCowardName, when);
|
anonymousCowardName, when);
|
||||||
this.draftUpdateFactory = draftUpdateFactory;
|
|
||||||
this.accountCache = accountCache;
|
this.accountCache = accountCache;
|
||||||
this.commentsUtil = commentsUtil;
|
this.commentsUtil = commentsUtil;
|
||||||
|
this.draftUpdateFactory = draftUpdateFactory;
|
||||||
|
this.updateManagerFactory = updateManagerFactory;
|
||||||
|
|
||||||
this.approvals = TreeBasedTable.create(
|
this.approvals = TreeBasedTable.create(
|
||||||
labelNameComparator,
|
labelNameComparator,
|
||||||
Ordering.natural().onResultOf(new Function<Account.Id, Integer>() {
|
Ordering.natural().onResultOf(new Function<Account.Id, Integer>() {
|
||||||
@ -193,13 +198,19 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
this.comments = Lists.newArrayList();
|
this.comments = Lists.newArrayList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChangeId(String changeId) throws OrmException {
|
public ObjectId commit() throws IOException, OrmException {
|
||||||
if (notes == null) {
|
NoteDbUpdateManager updateManager =
|
||||||
notes = getChangeNotes().load();
|
updateManagerFactory.create(getProjectName());
|
||||||
}
|
updateManager.add(this);
|
||||||
checkArgument(notes.getChange().getKey().get().equals(changeId),
|
updateManager.execute();
|
||||||
|
return getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChangeId(String changeId) {
|
||||||
|
String old = ctl.getChange().getKey().get();
|
||||||
|
checkArgument(old.equals(changeId),
|
||||||
"The Change-Id was already set to %s, so we cannot set this Change-Id: %s",
|
"The Change-Id was already set to %s, so we cannot set this Change-Id: %s",
|
||||||
notes.getChange().getKey(), changeId);
|
old, changeId);
|
||||||
this.changeId = changeId;
|
this.changeId = changeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,120 +269,41 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
this.changeMessage = changeMessage;
|
this.changeMessage = changeMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertComment(PatchLineComment comment) throws OrmException {
|
public void putComment(PatchLineComment c) {
|
||||||
if (comment.getStatus() == Status.DRAFT) {
|
|
||||||
insertDraftComment(comment);
|
|
||||||
} else {
|
|
||||||
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);
|
verifyComment(c);
|
||||||
if (notes == null) {
|
|
||||||
notes = getChangeNotes().load();
|
|
||||||
}
|
|
||||||
if (migration.readChanges()) {
|
|
||||||
checkArgument(!notes.containsComment(c),
|
|
||||||
"A comment already exists with the same key as the following comment,"
|
|
||||||
+ " so we cannot insert this comment: %s", c);
|
|
||||||
}
|
|
||||||
comments.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertDraftComment(PatchLineComment c) throws OrmException {
|
|
||||||
createDraftUpdateIfNull();
|
createDraftUpdateIfNull();
|
||||||
draftUpdate.insertComment(c);
|
if (c.getStatus() == PatchLineComment.Status.DRAFT) {
|
||||||
|
draftUpdate.putComment(c);
|
||||||
|
} else {
|
||||||
|
comments.add(c);
|
||||||
|
// Always delete the corresponding comment from drafts. Published comments
|
||||||
|
// are immutable, meaning in normal operation we only hit this path when
|
||||||
|
// publishing a comment. It's exactly in that case that we have to delete
|
||||||
|
// the draft.
|
||||||
|
draftUpdate.deleteComment(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upsertPublishedComment(PatchLineComment c) throws OrmException {
|
public void deleteComment(PatchLineComment c) {
|
||||||
verifyComment(c);
|
verifyComment(c);
|
||||||
if (notes == null) {
|
if (c.getStatus() == PatchLineComment.Status.DRAFT) {
|
||||||
notes = getChangeNotes().load();
|
createDraftUpdateIfNull().deleteComment(c);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cannot delete published comment " + c);
|
||||||
}
|
}
|
||||||
// This could allow callers to update a published comment if migration.write
|
|
||||||
// is on and migration.readComments is off because we will not be able to
|
|
||||||
// verify that the comment didn't already exist as a published comment
|
|
||||||
// since we don't have a ReviewDb.
|
|
||||||
if (migration.readChanges()) {
|
|
||||||
checkArgument(!notes.containsCommentPublished(c),
|
|
||||||
"Cannot update a comment that has already been published and saved");
|
|
||||||
}
|
|
||||||
comments.add(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upsertDraftComment(PatchLineComment c) {
|
@VisibleForTesting
|
||||||
createDraftUpdateIfNull();
|
ChangeDraftUpdate createDraftUpdateIfNull() {
|
||||||
draftUpdate.upsertComment(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePublishedComment(PatchLineComment c) throws OrmException {
|
|
||||||
verifyComment(c);
|
|
||||||
if (notes == null) {
|
|
||||||
notes = getChangeNotes().load();
|
|
||||||
}
|
|
||||||
// See comment above in upsertPublishedComment() about potential risk with
|
|
||||||
// this check.
|
|
||||||
if (migration.readChanges()) {
|
|
||||||
checkArgument(!notes.containsCommentPublished(c),
|
|
||||||
"Cannot update a comment that has already been published and saved");
|
|
||||||
}
|
|
||||||
comments.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDraftComment(PatchLineComment c) throws OrmException {
|
|
||||||
createDraftUpdateIfNull();
|
|
||||||
draftUpdate.updateComment(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteDraftComment(PatchLineComment c) throws OrmException {
|
|
||||||
createDraftUpdateIfNull();
|
|
||||||
draftUpdate.deleteComment(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteDraftCommentIfPresent(PatchLineComment c)
|
|
||||||
throws OrmException {
|
|
||||||
createDraftUpdateIfNull();
|
|
||||||
draftUpdate.deleteCommentIfPresent(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createDraftUpdateIfNull() {
|
|
||||||
if (draftUpdate == null) {
|
if (draftUpdate == null) {
|
||||||
draftUpdate = draftUpdateFactory.create(ctl, when);
|
draftUpdate = draftUpdateFactory.create(ctl, when);
|
||||||
}
|
}
|
||||||
|
return draftUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyComment(PatchLineComment c) {
|
private void verifyComment(PatchLineComment c) {
|
||||||
checkArgument(c.getRevId() != null);
|
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()),
|
checkArgument(c.getAuthor().equals(getUser().getAccountId()),
|
||||||
"The author for the following comment does not match the author of"
|
"The author for the following comment does not match the author of"
|
||||||
+ " this ChangeDraftUpdate (%s): %s", getUser().getAccountId(), c);
|
+ " this ChangeDraftUpdate (%s): %s", getUser().getAccountId(), c);
|
||||||
@ -418,71 +350,92 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @return the tree id for the updated tree */
|
/** @return the tree id for the updated tree */
|
||||||
private ObjectId storeRevisionNotes() throws OrmException, IOException {
|
private ObjectId storeRevisionNotes(RevWalk rw, ObjectInserter inserter,
|
||||||
ChangeNotes notes = ctl.getNotes().load();
|
ObjectId curr) throws ConfigInvalidException, OrmException, IOException {
|
||||||
NoteMap noteMap = notes.getNoteMap();
|
|
||||||
if (noteMap == null) {
|
|
||||||
noteMap = NoteMap.newEmptyMap();
|
|
||||||
}
|
|
||||||
if (comments.isEmpty() && pushCert == null) {
|
if (comments.isEmpty() && pushCert == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
RevisionNoteMap rnm = getRevisionNoteMap(rw, curr);
|
||||||
|
|
||||||
Map<RevId, RevisionNoteBuilder> builders = new HashMap<>();
|
RevisionNoteBuilder.Cache cache = new RevisionNoteBuilder.Cache(rnm);
|
||||||
for (PatchLineComment c : comments) {
|
for (PatchLineComment c : comments) {
|
||||||
builder(builders, c.getRevId()).addComment(c);
|
cache.get(c.getRevId()).putComment(c);
|
||||||
}
|
}
|
||||||
if (pushCert != null) {
|
if (pushCert != null) {
|
||||||
checkState(commit != null);
|
checkState(commit != null);
|
||||||
builder(builders, new RevId(commit.name())).setPushCertificate(pushCert);
|
cache.get(new RevId(commit.name())).setPushCertificate(pushCert);
|
||||||
}
|
}
|
||||||
|
Map<RevId, RevisionNoteBuilder> builders = cache.getBuilders();
|
||||||
|
checkComments(rnm.revisionNotes, builders);
|
||||||
|
|
||||||
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
for (Map.Entry<RevId, RevisionNoteBuilder> e : builders.entrySet()) {
|
||||||
ObjectId data = inserter.insert(
|
ObjectId data = inserter.insert(
|
||||||
OBJ_BLOB, e.getValue().build(commentsUtil));
|
OBJ_BLOB, e.getValue().build(commentsUtil));
|
||||||
noteMap.set(ObjectId.fromString(e.getKey().get()), data);
|
rnm.noteMap.set(ObjectId.fromString(e.getKey().get()), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return noteMap.writeTree(inserter);
|
return rnm.noteMap.writeTree(inserter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevisionNoteBuilder builder(Map<RevId, RevisionNoteBuilder> builders,
|
private RevisionNoteMap getRevisionNoteMap(RevWalk rw, ObjectId curr)
|
||||||
RevId revId) {
|
throws ConfigInvalidException, OrmException, IOException {
|
||||||
RevisionNoteBuilder b = builders.get(revId);
|
if (migration.readChanges()) {
|
||||||
if (b == null) {
|
// If reading from changes is enabled, then the old ChangeNotes already
|
||||||
b = new RevisionNoteBuilder(
|
// parsed the revision notes. We can reuse them as long as the ref hasn't
|
||||||
getChangeNotes().getRevisionNotes().get(revId));
|
// advanced.
|
||||||
builders.put(revId, b);
|
ObjectId idFromNotes =
|
||||||
}
|
firstNonNull(ctl.getNotes().load().getRevision(), ObjectId.zeroId());
|
||||||
return b;
|
if (idFromNotes.equals(curr)) {
|
||||||
}
|
return checkNotNull(ctl.getNotes().revisionNoteMap);
|
||||||
|
|
||||||
public RevCommit commit() throws IOException {
|
|
||||||
BatchMetaDataUpdate batch = openUpdate();
|
|
||||||
try {
|
|
||||||
writeCommit(batch);
|
|
||||||
RevCommit c = batch.commit();
|
|
||||||
return c;
|
|
||||||
} catch (OrmException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
} finally {
|
|
||||||
batch.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeCommit(BatchMetaDataUpdate batch) throws OrmException,
|
|
||||||
IOException {
|
|
||||||
CommitBuilder builder = new CommitBuilder();
|
|
||||||
if (migration.writeChanges()) {
|
|
||||||
ObjectId treeId = storeRevisionNotes();
|
|
||||||
if (treeId != null) {
|
|
||||||
builder.setTreeId(treeId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
batch.write(this, builder);
|
NoteMap noteMap;
|
||||||
if (draftUpdate != null) {
|
if (!curr.equals(ObjectId.zeroId())) {
|
||||||
draftUpdate.commit();
|
noteMap = NoteMap.read(rw.getObjectReader(), rw.parseCommit(curr));
|
||||||
|
} else {
|
||||||
|
noteMap = NoteMap.newEmptyMap();
|
||||||
|
}
|
||||||
|
// Even though reading from changes might not be enabled, we need to
|
||||||
|
// parse any existing revision notes so we can merge them.
|
||||||
|
return RevisionNoteMap.parse(
|
||||||
|
ctl.getId(), rw.getObjectReader(), noteMap, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkComments(Map<RevId, RevisionNote> existingNotes,
|
||||||
|
Map<RevId, RevisionNoteBuilder> toUpdate) throws OrmException {
|
||||||
|
// Prohibit various kinds of illegal operations on comments.
|
||||||
|
Set<PatchLineComment.Key> existing = new HashSet<>();
|
||||||
|
for (RevisionNote rn : existingNotes.values()) {
|
||||||
|
for (PatchLineComment c : rn.comments) {
|
||||||
|
existing.add(c.getKey());
|
||||||
|
if (draftUpdate != null) {
|
||||||
|
// Take advantage of an existing update on All-Users to prune any
|
||||||
|
// published comments from drafts. NoteDbUpdateManager takes care of
|
||||||
|
// ensuring that this update is applied before its dependent draft
|
||||||
|
// update.
|
||||||
|
//
|
||||||
|
// Deleting aggressively in this way, combined with filtering out
|
||||||
|
// duplicate published/draft comments in ChangeNotes#getDraftComments,
|
||||||
|
// makes up for the fact that updates between the change repo and
|
||||||
|
// All-Users are not atomic.
|
||||||
|
//
|
||||||
|
// TODO(dborowitz): We might want to distinguish between deleted
|
||||||
|
// drafts that we're fixing up after the fact by putting them in a
|
||||||
|
// separate commit. But note that we don't care much about the commit
|
||||||
|
// graph of the draft ref, particularly because the ref is completely
|
||||||
|
// deleted when all drafts are gone.
|
||||||
|
draftUpdate.deleteComment(c.getRevId(), c.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (RevisionNoteBuilder b : toUpdate.values()) {
|
||||||
|
for (PatchLineComment c : b.put.values()) {
|
||||||
|
if (existing.contains(c.getKey())) {
|
||||||
|
throw new OrmException(
|
||||||
|
"Cannot update existing published comment: " + c);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,12 +445,9 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onSave(CommitBuilder cb) {
|
protected CommitBuilder applyImpl(RevWalk rw, ObjectInserter ins,
|
||||||
if (getRevision() != null && isEmpty()) {
|
ObjectId curr) throws OrmException, IOException {
|
||||||
return false;
|
CommitBuilder cb = new CommitBuilder();
|
||||||
}
|
|
||||||
cb.setAuthor(newIdent(getUser().getAccount(), when));
|
|
||||||
cb.setCommitter(new PersonIdent(serverIdent, when));
|
|
||||||
|
|
||||||
int ps = psId != null ? psId.get() : getChange().currentPatchSetId().get();
|
int ps = psId != null ? psId.get() : getChange().currentPatchSetId().get();
|
||||||
StringBuilder msg = new StringBuilder();
|
StringBuilder msg = new StringBuilder();
|
||||||
@ -599,7 +549,15 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cb.setMessage(msg.toString());
|
cb.setMessage(msg.toString());
|
||||||
return true;
|
try {
|
||||||
|
ObjectId treeId = storeRevisionNotes(rw, ins, curr);
|
||||||
|
if (treeId != null) {
|
||||||
|
cb.setTreeId(treeId);
|
||||||
|
}
|
||||||
|
} catch (ConfigInvalidException e) {
|
||||||
|
throw new OrmException(e);
|
||||||
|
}
|
||||||
|
return cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPatchSetFooter(StringBuilder sb, int ps) {
|
private void addPatchSetFooter(StringBuilder sb, int ps) {
|
||||||
@ -634,6 +592,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
&& groups == null;
|
&& groups == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChangeDraftUpdate getDraftUpdate() {
|
||||||
|
return draftUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
private static StringBuilder addFooter(StringBuilder sb, FooterKey footer) {
|
private static StringBuilder addFooter(StringBuilder sb, FooterKey footer) {
|
||||||
return sb.append(footer.getName()).append(": ");
|
return sb.append(footer.getName()).append(": ");
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,12 @@
|
|||||||
package com.google.gerrit.server.notedb;
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.gerrit.server.PatchLineCommentsUtil.PLC_ORDER;
|
|
||||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
|
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
|
||||||
import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
|
import static com.google.gerrit.server.notedb.ChangeNotes.parseException;
|
||||||
import static com.google.gerrit.server.notedb.RevisionNote.MAX_NOTE_SZ;
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
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 com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
@ -42,16 +38,7 @@ import com.google.inject.Inject;
|
|||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
|
||||||
import org.eclipse.jgit.lib.ObjectInserter;
|
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
import org.eclipse.jgit.notes.Note;
|
|
||||||
import org.eclipse.jgit.notes.NoteMap;
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
|
||||||
import org.eclipse.jgit.util.GitDateFormatter;
|
import org.eclipse.jgit.util.GitDateFormatter;
|
||||||
import org.eclipse.jgit.util.GitDateFormatter.Format;
|
import org.eclipse.jgit.util.GitDateFormatter.Format;
|
||||||
import org.eclipse.jgit.util.GitDateParser;
|
import org.eclipse.jgit.util.GitDateParser;
|
||||||
@ -60,18 +47,14 @@ import org.eclipse.jgit.util.QuotedString;
|
|||||||
import org.eclipse.jgit.util.RawParseUtils;
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility functions to parse PatchLineComments out of a note byte array and
|
* Utility functions to parse PatchLineComments out of a note byte array and
|
||||||
@ -89,33 +72,6 @@ public class CommentsInNotesUtil {
|
|||||||
private static final String REVISION = "Revision";
|
private static final String REVISION = "Revision";
|
||||||
private static final String UUID = "UUID";
|
private static final String UUID = "UUID";
|
||||||
|
|
||||||
public static NoteMap parseCommentsFromNotes(Repository repo, String refName,
|
|
||||||
RevWalk walk, Change.Id changeId,
|
|
||||||
Multimap<RevId, PatchLineComment> comments,
|
|
||||||
Status status)
|
|
||||||
throws IOException, ConfigInvalidException {
|
|
||||||
Ref ref = repo.getRefDatabase().exactRef(refName);
|
|
||||||
if (ref == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectReader reader = walk.getObjectReader();
|
|
||||||
RevCommit commit = walk.parseCommit(ref.getObjectId());
|
|
||||||
NoteMap noteMap = NoteMap.read(reader, commit);
|
|
||||||
|
|
||||||
for (Note note : noteMap) {
|
|
||||||
byte[] bytes =
|
|
||||||
reader.open(note.getData(), OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
|
|
||||||
List<PatchLineComment> result =
|
|
||||||
parseNote(bytes, new MutableInteger(), changeId, status);
|
|
||||||
if (result == null || result.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
comments.putAll(new RevId(note.name()), result);
|
|
||||||
}
|
|
||||||
return noteMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<PatchLineComment> parseNote(byte[] note, MutableInteger p,
|
public static List<PatchLineComment> parseNote(byte[] note, MutableInteger p,
|
||||||
Change.Id changeId, Status status) throws ConfigInvalidException {
|
Change.Id changeId, Status status) throws ConfigInvalidException {
|
||||||
if (p.value >= note.length) {
|
if (p.value >= note.length) {
|
||||||
@ -432,7 +388,7 @@ public class CommentsInNotesUtil {
|
|||||||
* same side and must share the same patch set ID.
|
* same side and must share the same patch set ID.
|
||||||
* @param out output stream to write to.
|
* @param out output stream to write to.
|
||||||
*/
|
*/
|
||||||
public void buildNote(List<PatchLineComment> comments, OutputStream out) {
|
void buildNote(List<PatchLineComment> comments, OutputStream out) {
|
||||||
if (comments.isEmpty()) {
|
if (comments.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -514,51 +470,4 @@ public class CommentsInNotesUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write comments for multiple revisions to a note map.
|
|
||||||
* <p>
|
|
||||||
* Mutates the map in-place. only notes for SHA-1s found as keys in the map
|
|
||||||
* are modified; all other notes are left untouched.
|
|
||||||
*
|
|
||||||
* @param noteMap note map to modify.
|
|
||||||
* @param allComments map of revision to all comments for that revision;
|
|
||||||
* callers are responsible for reading the original comments and applying
|
|
||||||
* any changes. Differs from a multimap in that present-but-empty values
|
|
||||||
* are significant, and indicate the note for that SHA-1 should be
|
|
||||||
* deleted.
|
|
||||||
* @param inserter object inserter for writing notes.
|
|
||||||
* @throws IOException if an error occurred.
|
|
||||||
*/
|
|
||||||
public void writeCommentsToNoteMap(NoteMap noteMap,
|
|
||||||
Map<RevId, List<PatchLineComment>> allComments, ObjectInserter inserter)
|
|
||||||
throws IOException {
|
|
||||||
for (Map.Entry<RevId, List<PatchLineComment>> e : allComments.entrySet()) {
|
|
||||||
List<PatchLineComment> comments = e.getValue();
|
|
||||||
ObjectId commit = ObjectId.fromString(e.getKey().get());
|
|
||||||
if (comments.isEmpty()) {
|
|
||||||
noteMap.remove(commit);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Collections.sort(comments, PLC_ORDER);
|
|
||||||
// We allow comments for multiple commits to be written in the same
|
|
||||||
// update, even though the rest of the metadata update is associated with
|
|
||||||
// a single patch set.
|
|
||||||
byte[] note = buildNote(comments);
|
|
||||||
if (note != null && note.length > 0) {
|
|
||||||
noteMap.set(commit, inserter.insert(OBJ_BLOB, note));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addCommentToMap(Map<RevId, List<PatchLineComment>> map,
|
|
||||||
PatchLineComment c) {
|
|
||||||
List<PatchLineComment> list = map.get(c.getRevId());
|
|
||||||
if (list == null) {
|
|
||||||
list = new ArrayList<>();
|
|
||||||
map.put(c.getRevId(), list);
|
|
||||||
}
|
|
||||||
list.add(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
package com.google.gerrit.server.notedb;
|
package com.google.gerrit.server.notedb;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.ImmutableListMultimap;
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
@ -31,6 +33,7 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
|
|||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.notes.NoteMap;
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -66,7 +69,7 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
private final Account.Id author;
|
private final Account.Id author;
|
||||||
|
|
||||||
private ImmutableListMultimap<RevId, PatchLineComment> comments;
|
private ImmutableListMultimap<RevId, PatchLineComment> comments;
|
||||||
private NoteMap noteMap;
|
private RevisionNoteMap revisionNoteMap;
|
||||||
|
|
||||||
DraftCommentNotes(GitRepositoryManager repoManager, NotesMigration migration,
|
DraftCommentNotes(GitRepositoryManager repoManager, NotesMigration migration,
|
||||||
AllUsersName draftsProject, Change.Id changeId, Account.Id author) {
|
AllUsersName draftsProject, Change.Id changeId, Account.Id author) {
|
||||||
@ -75,8 +78,8 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
this.author = author;
|
this.author = author;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NoteMap getNoteMap() {
|
RevisionNoteMap getRevisionNoteMap() {
|
||||||
return noteMap;
|
return revisionNoteMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Account.Id getAuthor() {
|
public Account.Id getAuthor() {
|
||||||
@ -110,13 +113,17 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (RevWalk walk = new RevWalk(reader);
|
try (RevWalk walk = new RevWalk(reader)) {
|
||||||
DraftCommentNotesParser parser = new DraftCommentNotesParser(
|
RevCommit tipCommit = walk.parseCommit(rev);
|
||||||
getChangeId(), walk, rev, repoManager, draftsProject, author)) {
|
revisionNoteMap = RevisionNoteMap.parse(
|
||||||
parser.parseDraftComments();
|
getChangeId(), reader, NoteMap.read(reader, tipCommit), true);
|
||||||
|
Multimap<RevId, PatchLineComment> cs = ArrayListMultimap.create();
|
||||||
comments = ImmutableListMultimap.copyOf(parser.comments);
|
for (RevisionNote rn : revisionNoteMap.revisionNotes.values()) {
|
||||||
noteMap = parser.noteMap;
|
for (PatchLineComment c : rn.comments) {
|
||||||
|
cs.put(c.getRevId(), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comments = ImmutableListMultimap.copyOf(cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,4 +143,9 @@ public class DraftCommentNotes extends AbstractChangeNotes<DraftCommentNotes> {
|
|||||||
public Project.NameKey getProjectName() {
|
public Project.NameKey getProjectName() {
|
||||||
return draftsProject;
|
return draftsProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
NoteMap getNoteMap() {
|
||||||
|
return revisionNoteMap != null ? revisionNoteMap.noteMap : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
// 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 com.google.common.collect.ArrayListMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
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.RevId;
|
|
||||||
import com.google.gerrit.server.config.AllUsersName;
|
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
|
||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
|
||||||
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;
|
|
||||||
|
|
||||||
class DraftCommentNotesParser implements AutoCloseable {
|
|
||||||
final Multimap<RevId, PatchLineComment> comments;
|
|
||||||
NoteMap noteMap;
|
|
||||||
|
|
||||||
private final Change.Id changeId;
|
|
||||||
private final ObjectId tip;
|
|
||||||
private final RevWalk walk;
|
|
||||||
private final Repository repo;
|
|
||||||
private final Account.Id author;
|
|
||||||
|
|
||||||
DraftCommentNotesParser(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.openMetadataRepository(draftsProject);
|
|
||||||
this.author = author;
|
|
||||||
|
|
||||||
comments = ArrayListMultimap.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
repo.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseDraftComments() throws IOException, ConfigInvalidException {
|
|
||||||
walk.markStart(walk.parseCommit(tip));
|
|
||||||
noteMap = CommentsInNotesUtil.parseCommentsFromNotes(repo,
|
|
||||||
RefNames.refsDraftComments(author, changeId),
|
|
||||||
walk, changeId, comments, PatchLineComment.Status.DRAFT);
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,5 +21,6 @@ public class NoteDbModule extends FactoryModule {
|
|||||||
public void configure() {
|
public void configure() {
|
||||||
factory(ChangeUpdate.Factory.class);
|
factory(ChangeUpdate.Factory.class);
|
||||||
factory(ChangeDraftUpdate.Factory.class);
|
factory(ChangeDraftUpdate.Factory.class);
|
||||||
|
factory(NoteDbUpdateManager.Factory.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,265 @@
|
|||||||
|
// Copyright (C) 2016 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.MoreObjects.firstNonNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
|
import com.google.gerrit.server.git.ChainedReceiveCommands;
|
||||||
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import com.google.inject.assistedinject.AssistedInject;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.BatchRefUpdate;
|
||||||
|
import org.eclipse.jgit.lib.NullProgressMonitor;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.transport.ReceiveCommand;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class NoteDbUpdateManager {
|
||||||
|
public interface Factory {
|
||||||
|
NoteDbUpdateManager create(Project.NameKey projectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OpenRepo implements AutoCloseable {
|
||||||
|
final Repository repo;
|
||||||
|
final RevWalk rw;
|
||||||
|
final ObjectInserter ins;
|
||||||
|
final ChainedReceiveCommands cmds;
|
||||||
|
final boolean close;
|
||||||
|
|
||||||
|
OpenRepo(Repository repo, RevWalk rw, ObjectInserter ins,
|
||||||
|
ChainedReceiveCommands cmds, boolean close) {
|
||||||
|
this.repo = checkNotNull(repo);
|
||||||
|
this.rw = checkNotNull(rw);
|
||||||
|
this.ins = checkNotNull(ins);
|
||||||
|
this.cmds = checkNotNull(cmds);
|
||||||
|
this.close = close;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (close) {
|
||||||
|
ins.close();
|
||||||
|
rw.close();
|
||||||
|
repo.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final GitRepositoryManager repoManager;
|
||||||
|
private final NotesMigration migration;
|
||||||
|
private final AllUsersName allUsersName;
|
||||||
|
private final Project.NameKey projectName;
|
||||||
|
private final ListMultimap<String, ChangeUpdate> changeUpdates;
|
||||||
|
private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
|
||||||
|
|
||||||
|
private OpenRepo changeRepo;
|
||||||
|
private OpenRepo allUsersRepo;
|
||||||
|
|
||||||
|
@AssistedInject
|
||||||
|
NoteDbUpdateManager(GitRepositoryManager repoManager,
|
||||||
|
NotesMigration migration,
|
||||||
|
AllUsersName allUsersName,
|
||||||
|
@Assisted Project.NameKey projectName) {
|
||||||
|
this.repoManager = repoManager;
|
||||||
|
this.migration = migration;
|
||||||
|
this.allUsersName = allUsersName;
|
||||||
|
this.projectName = projectName;
|
||||||
|
changeUpdates = ArrayListMultimap.create();
|
||||||
|
draftUpdates = ArrayListMultimap.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoteDbUpdateManager setChangeRepo(Repository repo, RevWalk rw,
|
||||||
|
ObjectInserter ins, ChainedReceiveCommands cmds) {
|
||||||
|
checkState(changeRepo == null, "change repo already initialized");
|
||||||
|
changeRepo = new OpenRepo(repo, rw, ins, cmds, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Repository getChangeRepo() throws IOException {
|
||||||
|
initChangeRepo();
|
||||||
|
return changeRepo.repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
RevWalk getChangeRevWalk() throws IOException {
|
||||||
|
initChangeRepo();
|
||||||
|
return changeRepo.rw;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedReceiveCommands getChangeCommands() throws IOException {
|
||||||
|
initChangeRepo();
|
||||||
|
return changeRepo.cmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoteDbUpdateManager setAllUsersRepo(Repository repo, RevWalk rw,
|
||||||
|
ObjectInserter ins, ChainedReceiveCommands cmds) {
|
||||||
|
checkState(allUsersRepo == null, "allUsers repo already initialized");
|
||||||
|
allUsersRepo = new OpenRepo(repo, rw, ins, cmds, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Repository getAllUsersRepo() throws IOException {
|
||||||
|
initAllUsersRepo();
|
||||||
|
return allUsersRepo.repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedReceiveCommands getAllUsersCommands() throws IOException {
|
||||||
|
initAllUsersRepo();
|
||||||
|
return allUsersRepo.cmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initChangeRepo() throws IOException {
|
||||||
|
if (changeRepo == null) {
|
||||||
|
changeRepo = openRepo(projectName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAllUsersRepo() throws IOException {
|
||||||
|
if (allUsersRepo == null) {
|
||||||
|
allUsersRepo = openRepo(allUsersName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OpenRepo openRepo(Project.NameKey p) throws IOException {
|
||||||
|
Repository repo = repoManager.openMetadataRepository(p);
|
||||||
|
ObjectInserter ins = repo.newObjectInserter();
|
||||||
|
return new OpenRepo(repo, new RevWalk(ins.newReader()), ins,
|
||||||
|
new ChainedReceiveCommands(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmpty() {
|
||||||
|
if (!migration.writeChanges()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return changeUpdates.isEmpty()
|
||||||
|
&& draftUpdates.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an update to the list of updates to execute.
|
||||||
|
* <p>
|
||||||
|
* Updates should only be added to the manager after all mutations have been
|
||||||
|
* made, as this method may eagerly access the update.
|
||||||
|
*
|
||||||
|
* @param update the update to add.
|
||||||
|
*/
|
||||||
|
public void add(ChangeUpdate update) {
|
||||||
|
checkArgument(update.getProjectName().equals(projectName),
|
||||||
|
"update for project %s cannot be added to manager for project %s",
|
||||||
|
update.getProjectName(), projectName);
|
||||||
|
changeUpdates.put(update.getRefName(), update);
|
||||||
|
ChangeDraftUpdate du = update.getDraftUpdate();
|
||||||
|
if (du != null) {
|
||||||
|
draftUpdates.put(du.getRefName(), du);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(ChangeDraftUpdate draftUpdate) {
|
||||||
|
draftUpdates.put(draftUpdate.getRefName(), draftUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() throws OrmException, IOException {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
initChangeRepo();
|
||||||
|
if (!draftUpdates.isEmpty()) {
|
||||||
|
initAllUsersRepo();
|
||||||
|
}
|
||||||
|
addCommands();
|
||||||
|
|
||||||
|
// ChangeUpdates must execute before ChangeDraftUpdates.
|
||||||
|
//
|
||||||
|
// ChangeUpdate will automatically delete draft comments for any published
|
||||||
|
// comments, but the updates to the two repos don't happen atomically.
|
||||||
|
// Thus if the change meta update succeeds and the All-Users update fails,
|
||||||
|
// we may have stale draft comments. Doing it in this order allows stale
|
||||||
|
// comments to be filtered out by ChangeNotes, reflecting the fact that
|
||||||
|
// comments can only go from DRAFT to PUBLISHED, not vice versa.
|
||||||
|
execute(changeRepo);
|
||||||
|
execute(allUsersRepo);
|
||||||
|
} finally {
|
||||||
|
if (allUsersRepo != null) {
|
||||||
|
allUsersRepo.close();
|
||||||
|
}
|
||||||
|
if (changeRepo != null) {
|
||||||
|
changeRepo.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void execute(OpenRepo or) throws IOException {
|
||||||
|
if (or == null || or.cmds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
or.ins.flush();
|
||||||
|
BatchRefUpdate bru = or.repo.getRefDatabase().newBatchUpdate();
|
||||||
|
or.cmds.addTo(bru);
|
||||||
|
bru.execute(or.rw, NullProgressMonitor.INSTANCE);
|
||||||
|
for (ReceiveCommand cmd : bru.getCommands()) {
|
||||||
|
if (cmd.getResult() != ReceiveCommand.Result.OK) {
|
||||||
|
throw new IOException("Update failed: " + bru);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCommands() throws OrmException, IOException {
|
||||||
|
if (isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkState(changeRepo != null, "must set change repo");
|
||||||
|
if (!draftUpdates.isEmpty()) {
|
||||||
|
checkState(allUsersRepo != null, "must set all users repo");
|
||||||
|
}
|
||||||
|
addUpdates(changeUpdates, changeRepo);
|
||||||
|
if (!draftUpdates.isEmpty()) {
|
||||||
|
addUpdates(draftUpdates, allUsersRepo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addUpdates(
|
||||||
|
ListMultimap<String, ? extends AbstractChangeUpdate> updates, OpenRepo or)
|
||||||
|
throws OrmException, IOException {
|
||||||
|
for (String refName : updates.keySet()) {
|
||||||
|
ObjectId old = firstNonNull(
|
||||||
|
or.cmds.getObjectId(or.repo, refName), ObjectId.zeroId());
|
||||||
|
ObjectId curr = old;
|
||||||
|
|
||||||
|
for (AbstractChangeUpdate u : updates.get(refName)) {
|
||||||
|
ObjectId next = u.apply(or.rw, or.ins, curr);
|
||||||
|
if (next == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
if (!old.equals(curr)) {
|
||||||
|
or.cmds.add(new ReceiveCommand(old, curr, refName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -63,14 +63,21 @@ class RevisionNote {
|
|||||||
final ImmutableList<PatchLineComment> comments;
|
final ImmutableList<PatchLineComment> comments;
|
||||||
final String pushCert;
|
final String pushCert;
|
||||||
|
|
||||||
RevisionNote(Change.Id changeId, ObjectReader reader, ObjectId noteId)
|
RevisionNote(Change.Id changeId, ObjectReader reader, ObjectId noteId,
|
||||||
throws ConfigInvalidException, IOException {
|
boolean draftsOnly) throws ConfigInvalidException, IOException {
|
||||||
byte[] bytes = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
|
byte[] bytes = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
|
||||||
MutableInteger p = new MutableInteger();
|
MutableInteger p = new MutableInteger();
|
||||||
trimLeadingEmptyLines(bytes, p);
|
trimLeadingEmptyLines(bytes, p);
|
||||||
pushCert = parsePushCert(changeId, bytes, p);
|
if (!draftsOnly) {
|
||||||
trimLeadingEmptyLines(bytes, p);
|
pushCert = parsePushCert(changeId, bytes, p);
|
||||||
comments = ImmutableList.copyOf(CommentsInNotesUtil.parseNote(
|
trimLeadingEmptyLines(bytes, p);
|
||||||
bytes, p, changeId, PatchLineComment.Status.PUBLISHED));
|
} else {
|
||||||
|
pushCert = null;
|
||||||
|
}
|
||||||
|
PatchLineComment.Status status = draftsOnly
|
||||||
|
? PatchLineComment.Status.DRAFT
|
||||||
|
: PatchLineComment.Status.PUBLISHED;
|
||||||
|
comments = ImmutableList.copyOf(
|
||||||
|
CommentsInNotesUtil.parseNote(bytes, p, changeId, status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,35 +14,77 @@
|
|||||||
|
|
||||||
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.PatchLineCommentsUtil.PLC_ORDER;
|
import static com.google.gerrit.server.PatchLineCommentsUtil.PLC_ORDER;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||||
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
class RevisionNoteBuilder {
|
class RevisionNoteBuilder {
|
||||||
private final Map<PatchLineComment.Key, PatchLineComment> comments;
|
static class Cache {
|
||||||
|
private final RevisionNoteMap revisionNoteMap;
|
||||||
|
private final Map<RevId, RevisionNoteBuilder> builders;
|
||||||
|
|
||||||
|
Cache(RevisionNoteMap revisionNoteMap) {
|
||||||
|
this.revisionNoteMap = revisionNoteMap;
|
||||||
|
this.builders = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
RevisionNoteBuilder get(RevId revId) {
|
||||||
|
RevisionNoteBuilder b = builders.get(revId);
|
||||||
|
if (b == null) {
|
||||||
|
b = new RevisionNoteBuilder(
|
||||||
|
revisionNoteMap.revisionNotes.get(revId));
|
||||||
|
builders.put(revId, b);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<RevId, RevisionNoteBuilder> getBuilders() {
|
||||||
|
return Collections.unmodifiableMap(builders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PatchLineComment> baseComments;
|
||||||
|
final Map<PatchLineComment.Key, PatchLineComment> put;
|
||||||
|
final Set<PatchLineComment.Key> delete;
|
||||||
|
|
||||||
private String pushCert;
|
private String pushCert;
|
||||||
|
|
||||||
RevisionNoteBuilder(RevisionNote base) {
|
RevisionNoteBuilder(RevisionNote base) {
|
||||||
if (base != null) {
|
if (base != null) {
|
||||||
comments = Maps.newHashMapWithExpectedSize(base.comments.size());
|
baseComments = base.comments;
|
||||||
for (PatchLineComment c : base.comments) {
|
put = Maps.newHashMapWithExpectedSize(base.comments.size());
|
||||||
addComment(c);
|
|
||||||
}
|
|
||||||
pushCert = base.pushCert;
|
pushCert = base.pushCert;
|
||||||
} else {
|
} else {
|
||||||
comments = new HashMap<>();
|
baseComments = Collections.emptyList();
|
||||||
|
put = new HashMap<>();
|
||||||
pushCert = null;
|
pushCert = null;
|
||||||
}
|
}
|
||||||
|
delete = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addComment(PatchLineComment comment) {
|
void putComment(PatchLineComment comment) {
|
||||||
comments.put(comment.getKey(), comment);
|
checkArgument(!delete.contains(comment.getKey()),
|
||||||
|
"cannot both delete and put %s", comment.getKey());
|
||||||
|
put.put(comment.getKey(), comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteComment(PatchLineComment.Key key) {
|
||||||
|
checkArgument(!put.containsKey(key), "cannot both delete and put %s", key);
|
||||||
|
delete.add(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPushCertificate(String pushCert) {
|
void setPushCertificate(String pushCert) {
|
||||||
@ -56,7 +98,16 @@ class RevisionNoteBuilder {
|
|||||||
out.write(certBytes, 0, trimTrailingNewlines(certBytes));
|
out.write(certBytes, 0, trimTrailingNewlines(certBytes));
|
||||||
out.write('\n');
|
out.write('\n');
|
||||||
}
|
}
|
||||||
commentsUtil.buildNote(PLC_ORDER.sortedCopy(comments.values()), out);
|
|
||||||
|
List<PatchLineComment> all =
|
||||||
|
new ArrayList<>(baseComments.size() + put.size());
|
||||||
|
for (PatchLineComment c : Iterables.concat(baseComments, put.values())) {
|
||||||
|
if (!delete.contains(c.getKey())) {
|
||||||
|
all.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(all, PLC_ORDER);
|
||||||
|
commentsUtil.buildNote(all, out);
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2016 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.common.collect.ImmutableMap;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.notes.Note;
|
||||||
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class RevisionNoteMap {
|
||||||
|
final NoteMap noteMap;
|
||||||
|
final ImmutableMap<RevId, RevisionNote> revisionNotes;
|
||||||
|
|
||||||
|
static RevisionNoteMap parse(Change.Id changeId, ObjectReader reader,
|
||||||
|
NoteMap noteMap, boolean draftsOnly)
|
||||||
|
throws ConfigInvalidException, IOException {
|
||||||
|
Map<RevId, RevisionNote> result = new HashMap<>();
|
||||||
|
for (Note note : noteMap) {
|
||||||
|
RevisionNote rn =
|
||||||
|
new RevisionNote(changeId, reader, note.getData(), draftsOnly);
|
||||||
|
result.put(new RevId(note.name()), rn);
|
||||||
|
}
|
||||||
|
return new RevisionNoteMap(noteMap, ImmutableMap.copyOf(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RevisionNoteMap(NoteMap noteMap,
|
||||||
|
ImmutableMap<RevId, RevisionNote> revisionNotes) {
|
||||||
|
this.noteMap = noteMap;
|
||||||
|
this.revisionNotes = revisionNotes;
|
||||||
|
}
|
||||||
|
}
|
@ -29,8 +29,10 @@ import com.google.gerrit.reviewdb.client.PatchLineComment;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.InternalUser;
|
||||||
import com.google.gerrit.server.StarredChangesUtil;
|
import com.google.gerrit.server.StarredChangesUtil;
|
||||||
import com.google.gerrit.server.account.AccountCache;
|
import com.google.gerrit.server.account.AccountCache;
|
||||||
import com.google.gerrit.server.account.CapabilityControl;
|
import com.google.gerrit.server.account.CapabilityControl;
|
||||||
@ -67,7 +69,6 @@ import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
|
|||||||
import org.eclipse.jgit.junit.TestRepository;
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -91,17 +92,23 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
|
|||||||
protected InMemoryRepository repo;
|
protected InMemoryRepository repo;
|
||||||
protected InMemoryRepositoryManager repoManager;
|
protected InMemoryRepositoryManager repoManager;
|
||||||
protected PersonIdent serverIdent;
|
protected PersonIdent serverIdent;
|
||||||
|
protected InternalUser internalUser;
|
||||||
protected Project.NameKey project;
|
protected Project.NameKey project;
|
||||||
protected RevWalk rw;
|
protected RevWalk rw;
|
||||||
protected TestRepository<InMemoryRepository> tr;
|
protected TestRepository<InMemoryRepository> tr;
|
||||||
|
|
||||||
@Inject protected IdentifiedUser.GenericFactory userFactory;
|
@Inject
|
||||||
|
protected IdentifiedUser.GenericFactory userFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected NoteDbUpdateManager.Factory updateManagerFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected AllUsersName allUsers;
|
||||||
|
|
||||||
private Injector injector;
|
private Injector injector;
|
||||||
private String systemTimeZone;
|
private String systemTimeZone;
|
||||||
|
|
||||||
@Inject private AllUsersName allUsers;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
setTimeForTesting();
|
setTimeForTesting();
|
||||||
@ -128,6 +135,7 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
|
|||||||
@Override
|
@Override
|
||||||
public void configure() {
|
public void configure() {
|
||||||
install(new GitModule());
|
install(new GitModule());
|
||||||
|
factory(NoteDbUpdateManager.Factory.class);
|
||||||
bind(AllUsersName.class).toProvider(AllUsersNameProvider.class);
|
bind(AllUsersName.class).toProvider(AllUsersNameProvider.class);
|
||||||
bind(NotesMigration.class).toInstance(MIGRATION);
|
bind(NotesMigration.class).toInstance(MIGRATION);
|
||||||
bind(GitRepositoryManager.class).toInstance(repoManager);
|
bind(GitRepositoryManager.class).toInstance(repoManager);
|
||||||
@ -159,6 +167,7 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
|
|||||||
changeOwner = userFactory.create(co.getId());
|
changeOwner = userFactory.create(co.getId());
|
||||||
otherUser = userFactory.create(ou.getId());
|
otherUser = userFactory.create(ou.getId());
|
||||||
otherUserId = otherUser.getAccountId();
|
otherUserId = otherUser.getAccountId();
|
||||||
|
internalUser = new InternalUser(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTimeForTesting() {
|
private void setTimeForTesting() {
|
||||||
@ -181,13 +190,10 @@ public abstract class AbstractChangeNotesTest extends GerritBaseTests {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ChangeUpdate newUpdate(Change c, IdentifiedUser user)
|
protected ChangeUpdate newUpdate(Change c, CurrentUser user)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
ChangeUpdate update = TestChanges.newUpdate(
|
ChangeUpdate update = TestChanges.newUpdate(
|
||||||
injector, repoManager, MIGRATION, c, allUsers, user);
|
injector, repoManager, MIGRATION, c, allUsers, user);
|
||||||
try (Repository repo = repoManager.openMetadataRepository(c.getProject())) {
|
|
||||||
update.load(repo);
|
|
||||||
}
|
|
||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,6 +381,39 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
|
|||||||
+ "Groups: d,e,f\n");
|
+ "Groups: d,e,f\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseServerIdent() throws Exception {
|
||||||
|
String msg = "Update change\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "Patch-set: 1\n"
|
||||||
|
+ "Branch: refs/heads/master\n"
|
||||||
|
+ "Change-id: I577fb248e474018276351785930358ec0450e9f7\n"
|
||||||
|
+ "Subject: Change subject\n";
|
||||||
|
assertParseSucceeds(msg);
|
||||||
|
assertParseSucceeds(writeCommit(msg, serverIdent));
|
||||||
|
|
||||||
|
msg = "Update change\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "With a message."
|
||||||
|
+ "\n"
|
||||||
|
+ "Patch-set: 1\n"
|
||||||
|
+ "Branch: refs/heads/master\n"
|
||||||
|
+ "Change-id: I577fb248e474018276351785930358ec0450e9f7\n"
|
||||||
|
+ "Subject: Change subject\n";
|
||||||
|
assertParseSucceeds(msg);
|
||||||
|
assertParseSucceeds(writeCommit(msg, serverIdent));
|
||||||
|
|
||||||
|
msg = "Update change\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "Patch-set: 1\n"
|
||||||
|
+ "Branch: refs/heads/master\n"
|
||||||
|
+ "Change-id: I577fb248e474018276351785930358ec0450e9f7\n"
|
||||||
|
+ "Subject: Change subject\n"
|
||||||
|
+ "Label: Label1=+1\n";
|
||||||
|
assertParseSucceeds(msg);
|
||||||
|
assertParseFails(writeCommit(msg, serverIdent));
|
||||||
|
}
|
||||||
|
|
||||||
private RevCommit writeCommit(String body) throws Exception {
|
private RevCommit writeCommit(String body) throws Exception {
|
||||||
return writeCommit(body, ChangeNoteUtil.newIdent(
|
return writeCommit(body, ChangeNoteUtil.newIdent(
|
||||||
changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent,
|
changeOwner.getAccount(), TimeUtil.nowTs(), serverIdent,
|
||||||
@ -404,7 +437,11 @@ public class ChangeNotesParserTest extends AbstractChangeNotesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertParseSucceeds(String body) throws Exception {
|
private void assertParseSucceeds(String body) throws Exception {
|
||||||
try (ChangeNotesParser parser = newParser(writeCommit(body))) {
|
assertParseSucceeds(writeCommit(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertParseSucceeds(RevCommit commit) throws Exception {
|
||||||
|
try (ChangeNotesParser parser = newParser(commit)) {
|
||||||
parser.parseAll();
|
parser.parseAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,19 +43,19 @@ import com.google.gerrit.reviewdb.client.PatchLineComment;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
|
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.BatchRefUpdate;
|
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.NullProgressMonitor;
|
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.notes.Note;
|
import org.eclipse.jgit.notes.Note;
|
||||||
import org.eclipse.jgit.notes.NoteMap;
|
import org.eclipse.jgit.notes.NoteMap;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.transport.ReceiveCommand;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
@ -65,6 +65,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ChangeNotesTest extends AbstractChangeNotesTest {
|
public class ChangeNotesTest extends AbstractChangeNotesTest {
|
||||||
|
@Inject
|
||||||
|
private DraftCommentNotes.Factory draftNotesFactory;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void approvalsOnePatchSet() throws Exception {
|
public void approvalsOnePatchSet() throws Exception {
|
||||||
Change c = newChange();
|
Change c = newChange();
|
||||||
@ -410,17 +413,16 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
@Test
|
@Test
|
||||||
public void emptyChangeUpdate() throws Exception {
|
public void emptyChangeUpdate() throws Exception {
|
||||||
Change c = newChange();
|
Change c = newChange();
|
||||||
|
Ref initial = repo.exactRef(ChangeNoteUtil.changeRefName(c.getId()));
|
||||||
|
assertThat(initial).isNotNull();
|
||||||
|
|
||||||
// The initial empty update creates a commit which is needed to track the
|
// Empty update doesn't create a new commit.
|
||||||
// creation time of the change.
|
|
||||||
ChangeUpdate update = newUpdate(c, changeOwner);
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
ObjectId revision = update.getRevision();
|
|
||||||
assertThat(revision).isNotNull();
|
|
||||||
|
|
||||||
// Any further empty update doesn't create a new commit.
|
|
||||||
update = newUpdate(c, changeOwner);
|
|
||||||
update.commit();
|
update.commit();
|
||||||
assertThat(update.getRevision()).isEqualTo(revision);
|
assertThat(update.getResult()).isNull();
|
||||||
|
|
||||||
|
Ref updated = repo.exactRef(ChangeNoteUtil.changeRefName(c.getId()));
|
||||||
|
assertThat(updated.getObjectId()).isEqualTo(initial.getObjectId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -433,7 +435,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
update.setHashtags(hashtags);
|
update.setHashtags(hashtags);
|
||||||
update.commit();
|
update.commit();
|
||||||
try (RevWalk walk = new RevWalk(repo)) {
|
try (RevWalk walk = new RevWalk(repo)) {
|
||||||
RevCommit commit = walk.parseCommit(update.getRevision());
|
RevCommit commit = walk.parseCommit(update.getResult());
|
||||||
walk.parseBody(commit);
|
walk.parseBody(commit);
|
||||||
assertThat(commit.getFullMessage()).endsWith("Hashtags: tag1,tag2\n");
|
assertThat(commit.getFullMessage()).endsWith("Hashtags: tag1,tag2\n");
|
||||||
}
|
}
|
||||||
@ -712,7 +714,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
update.setPatchSetState(PatchSetState.DRAFT);
|
update.setPatchSetState(PatchSetState.DRAFT);
|
||||||
update.putApproval("Code-Review", (short) 1);
|
update.putApproval("Code-Review", (short) 1);
|
||||||
update.setChangeMessage("This is a message");
|
update.setChangeMessage("This is a message");
|
||||||
update.insertComment(newPublishedComment(c.currentPatchSetId(), "a.txt",
|
update.putComment(newPublishedComment(c.currentPatchSetId(), "a.txt",
|
||||||
"uuid1", new CommentRange(1, 2, 3, 4), 1, changeOwner, null,
|
"uuid1", new CommentRange(1, 2, 3, 4), 1, changeOwner, null,
|
||||||
TimeUtil.nowTs(), "Comment", (short) 1, commit.name()));
|
TimeUtil.nowTs(), "Comment", (short) 1, commit.name()));
|
||||||
update.commit();
|
update.commit();
|
||||||
@ -810,7 +812,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
update = newUpdate(c, changeOwner);
|
update = newUpdate(c, changeOwner);
|
||||||
update.setPatchSetId(psId2);
|
update.setPatchSetId(psId2);
|
||||||
Timestamp ts = TimeUtil.nowTs();
|
Timestamp ts = TimeUtil.nowTs();
|
||||||
update.insertComment(newPublishedComment(psId2, "a.txt",
|
update.putComment(newPublishedComment(psId2, "a.txt",
|
||||||
"uuid1", new CommentRange(1, 2, 3, 4), 1, changeOwner, null, ts,
|
"uuid1", new CommentRange(1, 2, 3, 4), 1, changeOwner, null, ts,
|
||||||
"Comment", (short) 1, commit.name()));
|
"Comment", (short) 1, commit.name()));
|
||||||
update.commit();
|
update.commit();
|
||||||
@ -840,12 +842,11 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
public void emptyExceptSubject() throws Exception {
|
public void emptyExceptSubject() throws Exception {
|
||||||
ChangeUpdate update = newUpdate(newChange(), changeOwner);
|
ChangeUpdate update = newUpdate(newChange(), changeOwner);
|
||||||
update.setSubjectForCommit("Create change");
|
update.setSubjectForCommit("Create change");
|
||||||
update.commit();
|
assertThat(update.commit()).isNotNull();
|
||||||
assertThat(update.getRevision()).isNotNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void multipleUpdatesInBatch() throws Exception {
|
public void multipleUpdatesInManager() throws Exception {
|
||||||
Change c = newChange();
|
Change c = newChange();
|
||||||
ChangeUpdate update1 = newUpdate(c, changeOwner);
|
ChangeUpdate update1 = newUpdate(c, changeOwner);
|
||||||
update1.putApproval("Verified", (short) 1);
|
update1.putApproval("Verified", (short) 1);
|
||||||
@ -853,14 +854,10 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
ChangeUpdate update2 = newUpdate(c, otherUser);
|
ChangeUpdate update2 = newUpdate(c, otherUser);
|
||||||
update2.putApproval("Code-Review", (short) 2);
|
update2.putApproval("Code-Review", (short) 2);
|
||||||
|
|
||||||
BatchMetaDataUpdate batch = update1.openUpdate();
|
NoteDbUpdateManager updateManager = updateManagerFactory.create(project);
|
||||||
try {
|
updateManager.add(update1);
|
||||||
update1.writeCommit(batch);
|
updateManager.add(update2);
|
||||||
update2.writeCommit(batch);
|
updateManager.execute();
|
||||||
batch.commit();
|
|
||||||
} finally {
|
|
||||||
batch.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
List<PatchSetApproval> psas =
|
List<PatchSetApproval> psas =
|
||||||
@ -887,29 +884,24 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
CommentRange range1 = new CommentRange(1, 1, 2, 1);
|
CommentRange range1 = new CommentRange(1, 1, 2, 1);
|
||||||
Timestamp time1 = TimeUtil.nowTs();
|
Timestamp time1 = TimeUtil.nowTs();
|
||||||
PatchSet.Id psId = c.currentPatchSetId();
|
PatchSet.Id psId = c.currentPatchSetId();
|
||||||
BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
|
NoteDbUpdateManager updateManager = updateManagerFactory.create(project);
|
||||||
BatchMetaDataUpdate batch = update1.openUpdateInBatch(bru);
|
|
||||||
PatchLineComment comment1 = newPublishedComment(psId, "file1",
|
PatchLineComment comment1 = newPublishedComment(psId, "file1",
|
||||||
uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
|
uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
|
||||||
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
update1.setPatchSetId(psId);
|
update1.setPatchSetId(psId);
|
||||||
update1.upsertComment(comment1);
|
update1.putComment(comment1);
|
||||||
update1.writeCommit(batch);
|
updateManager.add(update1);
|
||||||
|
|
||||||
ChangeUpdate update2 = newUpdate(c, otherUser);
|
ChangeUpdate update2 = newUpdate(c, otherUser);
|
||||||
update2.putApproval("Code-Review", (short) 2);
|
update2.putApproval("Code-Review", (short) 2);
|
||||||
update2.writeCommit(batch);
|
updateManager.add(update2);
|
||||||
|
|
||||||
RevCommit tipCommit;
|
RevCommit tipCommit;
|
||||||
try (RevWalk rw = new RevWalk(repo)) {
|
updateManager.execute();
|
||||||
batch.commit();
|
|
||||||
bru.execute(rw, NullProgressMonitor.INSTANCE);
|
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
ObjectId tip = notes.getRevision();
|
ObjectId tip = notes.getRevision();
|
||||||
tipCommit = rw.parseCommit(tip);
|
tipCommit = rw.parseCommit(tip);
|
||||||
} finally {
|
|
||||||
batch.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
RevCommit commitWithApprovals = tipCommit;
|
RevCommit commitWithApprovals = tipCommit;
|
||||||
assertThat(commitWithApprovals).isNotNull();
|
assertThat(commitWithApprovals).isNotNull();
|
||||||
@ -949,43 +941,30 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
ChangeUpdate update2 = newUpdate(c2, otherUser);
|
ChangeUpdate update2 = newUpdate(c2, otherUser);
|
||||||
update2.putApproval("Code-Review", (short) 2);
|
update2.putApproval("Code-Review", (short) 2);
|
||||||
|
|
||||||
BatchMetaDataUpdate batch1 = null;
|
Ref initial1 = repo.exactRef(update1.getRefName());
|
||||||
BatchMetaDataUpdate batch2 = null;
|
assertThat(initial1).isNotNull();
|
||||||
|
Ref initial2 = repo.exactRef(update2.getRefName());
|
||||||
|
assertThat(initial2).isNotNull();
|
||||||
|
|
||||||
BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
|
NoteDbUpdateManager updateManager = updateManagerFactory.create(project);
|
||||||
try {
|
updateManager.add(update1);
|
||||||
batch1 = update1.openUpdateInBatch(bru);
|
updateManager.add(update2);
|
||||||
update1.writeCommit(batch1);
|
updateManager.execute();
|
||||||
batch1.commit();
|
|
||||||
assertThat(repo.exactRef(update1.getRefName())).isNotNull();
|
|
||||||
|
|
||||||
batch2 = update2.openUpdateInBatch(bru);
|
Ref ref1 = repo.exactRef(update1.getRefName());
|
||||||
update2.writeCommit(batch2);
|
assertThat(ref1.getObjectId()).isEqualTo(update1.getResult());
|
||||||
batch2.commit();
|
assertThat(ref1.getObjectId()).isNotEqualTo(initial1.getObjectId());
|
||||||
assertThat(repo.exactRef(update2.getRefName())).isNotNull();
|
Ref ref2 = repo.exactRef(update2.getRefName());
|
||||||
} finally {
|
assertThat(ref2.getObjectId()).isEqualTo(update2.getResult());
|
||||||
if (batch1 != null) {
|
assertThat(ref2.getObjectId()).isNotEqualTo(initial2.getObjectId());
|
||||||
batch1.close();
|
|
||||||
}
|
|
||||||
if (batch2 != null) {
|
|
||||||
batch2.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ReceiveCommand> cmds = bru.getCommands();
|
PatchSetApproval approval1 = newNotes(c1).getApprovals()
|
||||||
assertThat(cmds).hasSize(2);
|
.get(c1.currentPatchSetId()).iterator().next();
|
||||||
assertThat(cmds.get(0).getRefName()).isEqualTo(update1.getRefName());
|
assertThat(approval1.getLabel()).isEqualTo("Verified");
|
||||||
assertThat(cmds.get(1).getRefName()).isEqualTo(update2.getRefName());
|
|
||||||
|
|
||||||
try (RevWalk rw = new RevWalk(repo)) {
|
PatchSetApproval approval2 = newNotes(c2).getApprovals()
|
||||||
bru.execute(rw, NullProgressMonitor.INSTANCE);
|
.get(c2.currentPatchSetId()).iterator().next();
|
||||||
}
|
assertThat(approval2.getLabel()).isEqualTo("Code-Review");
|
||||||
|
|
||||||
assertThat(cmds.get(0).getResult()).isEqualTo(ReceiveCommand.Result.OK);
|
|
||||||
assertThat(cmds.get(1).getResult()).isEqualTo(ReceiveCommand.Result.OK);
|
|
||||||
|
|
||||||
assertThat(repo.exactRef(update1.getRefName())).isNotNull();
|
|
||||||
assertThat(repo.exactRef(update2.getRefName())).isNotNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -1150,7 +1129,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
|
uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
|
||||||
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
@ -1159,7 +1138,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
|
uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
|
||||||
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
@ -1168,14 +1147,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid3, range3, range3.getEndLine(), otherUser, null, time3, message3,
|
uuid3, range3, range3.getEndLine(), otherUser, null, time3, message3,
|
||||||
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
(short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment3);
|
update.putComment(comment3);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
|
|
||||||
try (RevWalk walk = new RevWalk(repo)) {
|
try (RevWalk walk = new RevWalk(repo)) {
|
||||||
ArrayList<Note> notesInTree =
|
ArrayList<Note> notesInTree =
|
||||||
Lists.newArrayList(notes.getNoteMap().iterator());
|
Lists.newArrayList(notes.revisionNoteMap.noteMap.iterator());
|
||||||
Note note = Iterables.getOnlyElement(notesInTree);
|
Note note = Iterables.getOnlyElement(notesInTree);
|
||||||
|
|
||||||
byte[] bytes =
|
byte[] bytes =
|
||||||
@ -1229,7 +1208,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
|
uuid1, range1, range1.getEndLine(), otherUser, null, time1, message1,
|
||||||
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
@ -1238,14 +1217,14 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
|
uuid2, range2, range2.getEndLine(), otherUser, null, time2, message2,
|
||||||
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
(short) 0, "abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
|
|
||||||
try (RevWalk walk = new RevWalk(repo)) {
|
try (RevWalk walk = new RevWalk(repo)) {
|
||||||
ArrayList<Note> notesInTree =
|
ArrayList<Note> notesInTree =
|
||||||
Lists.newArrayList(notes.getNoteMap().iterator());
|
Lists.newArrayList(notes.revisionNoteMap.noteMap.iterator());
|
||||||
Note note = Iterables.getOnlyElement(notesInTree);
|
Note note = Iterables.getOnlyElement(notesInTree);
|
||||||
|
|
||||||
byte[] bytes =
|
byte[] bytes =
|
||||||
@ -1293,7 +1272,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
range, range.getEndLine(), otherUser, null, now, messageForBase,
|
range, range.getEndLine(), otherUser, null, now, messageForBase,
|
||||||
(short) 0, rev1);
|
(short) 0, rev1);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(commentForBase);
|
update.putComment(commentForBase);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
@ -1302,7 +1281,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
range, range.getEndLine(), otherUser, null, now, messageForPS,
|
range, range.getEndLine(), otherUser, null, now, messageForPS,
|
||||||
(short) 1, rev2);
|
(short) 1, rev2);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(commentForPS);
|
update.putComment(commentForPS);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
||||||
@ -1329,7 +1308,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid1, range, range.getEndLine(), otherUser, null, timeForComment1,
|
uuid1, range, range.getEndLine(), otherUser, null, timeForComment1,
|
||||||
"comment 1", side, rev);
|
"comment 1", side, rev);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
@ -1337,7 +1316,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid2, range, range.getEndLine(), otherUser, null, timeForComment2,
|
uuid2, range, range.getEndLine(), otherUser, null, timeForComment2,
|
||||||
"comment 2", side, rev);
|
"comment 2", side, rev);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
||||||
@ -1364,7 +1343,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid, range, range.getEndLine(), otherUser, null, now, "comment 1",
|
uuid, range, range.getEndLine(), otherUser, null, now, "comment 1",
|
||||||
side, rev);
|
side, rev);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
@ -1372,7 +1351,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid, range, range.getEndLine(), otherUser, null, now, "comment 2",
|
uuid, range, range.getEndLine(), otherUser, null, now, "comment 2",
|
||||||
side, rev);
|
side, rev);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
||||||
@ -1398,7 +1377,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
|
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
|
||||||
side, rev1);
|
side, rev1);
|
||||||
update.setPatchSetId(ps1);
|
update.setPatchSetId(ps1);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
incrementPatchSet(c);
|
incrementPatchSet(c);
|
||||||
@ -1410,7 +1389,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
|
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
|
||||||
side, rev2);
|
side, rev2);
|
||||||
update.setPatchSetId(ps2);
|
update.setPatchSetId(ps2);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
||||||
@ -1435,7 +1414,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
range.getEndLine(), otherUser, null, now, "comment on ps1", side,
|
range.getEndLine(), otherUser, null, now, "comment on ps1", side,
|
||||||
rev, Status.DRAFT);
|
rev, Status.DRAFT);
|
||||||
update.setPatchSetId(ps1);
|
update.setPatchSetId(ps1);
|
||||||
update.insertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
@ -1446,7 +1425,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
comment1.setStatus(Status.PUBLISHED);
|
comment1.setStatus(Status.PUBLISHED);
|
||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
update.setPatchSetId(ps1);
|
update.setPatchSetId(ps1);
|
||||||
update.updateComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
notes = newNotes(c);
|
notes = newNotes(c);
|
||||||
@ -1478,8 +1457,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
PatchLineComment comment2 = newComment(psId, filename, uuid2,
|
PatchLineComment comment2 = newComment(psId, filename, uuid2,
|
||||||
range2, range2.getEndLine(), otherUser, null, now, "other on ps1",
|
range2, range2.getEndLine(), otherUser, null, now, "other on ps1",
|
||||||
side, rev, Status.DRAFT);
|
side, rev, Status.DRAFT);
|
||||||
update.insertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.insertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
@ -1493,7 +1472,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
update = newUpdate(c, otherUser);
|
update = newUpdate(c, otherUser);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
comment1.setStatus(Status.PUBLISHED);
|
comment1.setStatus(Status.PUBLISHED);
|
||||||
update.updateComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
notes = newNotes(c);
|
notes = newNotes(c);
|
||||||
@ -1527,8 +1506,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
range2, range2.getEndLine(), otherUser, null, now, "comment on ps",
|
range2, range2.getEndLine(), otherUser, null, now, "comment on ps",
|
||||||
(short) 1, rev2, Status.DRAFT);
|
(short) 1, rev2, Status.DRAFT);
|
||||||
|
|
||||||
update.insertComment(baseComment);
|
update.putComment(baseComment);
|
||||||
update.insertComment(psComment);
|
update.putComment(psComment);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
@ -1544,8 +1523,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
|
|
||||||
baseComment.setStatus(Status.PUBLISHED);
|
baseComment.setStatus(Status.PUBLISHED);
|
||||||
psComment.setStatus(Status.PUBLISHED);
|
psComment.setStatus(Status.PUBLISHED);
|
||||||
update.updateComment(baseComment);
|
update.putComment(baseComment);
|
||||||
update.updateComment(psComment);
|
update.putComment(psComment);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
notes = newNotes(c);
|
notes = newNotes(c);
|
||||||
@ -1573,7 +1552,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
range.getEndLine(), otherUser, null, now, "comment on ps1", side,
|
range.getEndLine(), otherUser, null, now, "comment on ps1", side,
|
||||||
rev, Status.DRAFT);
|
rev, Status.DRAFT);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment);
|
update.putComment(comment);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
@ -1612,7 +1591,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
|
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps1",
|
||||||
side, rev1, Status.DRAFT);
|
side, rev1, Status.DRAFT);
|
||||||
update.setPatchSetId(ps1);
|
update.setPatchSetId(ps1);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
incrementPatchSet(c);
|
incrementPatchSet(c);
|
||||||
@ -1624,7 +1603,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
|
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
|
||||||
side, rev2, Status.DRAFT);
|
side, rev2, Status.DRAFT);
|
||||||
update.setPatchSetId(ps2);
|
update.setPatchSetId(ps2);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
@ -1657,7 +1636,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
psId, "filename", uuid, null, 0, otherUser, null, now, messageForBase,
|
psId, "filename", uuid, null, 0, otherUser, null, now, messageForBase,
|
||||||
(short) 0, rev);
|
(short) 0, rev);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment);
|
update.putComment(comment);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
||||||
@ -1678,7 +1657,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
psId, "filename", uuid, null, 1, otherUser, null, now, messageForBase,
|
psId, "filename", uuid, null, 1, otherUser, null, now, messageForBase,
|
||||||
(short) 0, rev);
|
(short) 0, rev);
|
||||||
update.setPatchSetId(psId);
|
update.setPatchSetId(psId);
|
||||||
update.upsertComment(comment);
|
update.putComment(comment);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
assertThat(newNotes(c).getComments()).containsExactlyEntriesIn(
|
||||||
@ -1686,7 +1665,7 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateCommentsForMultipleRevisions() throws Exception {
|
public void putCommentsForMultipleRevisions() throws Exception {
|
||||||
Change c = newChange();
|
Change c = newChange();
|
||||||
String uuid = "uuid";
|
String uuid = "uuid";
|
||||||
String rev1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
|
String rev1 = "abcd1234abcd1234abcd1234abcd1234abcd1234";
|
||||||
@ -1708,8 +1687,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
PatchLineComment comment2 = newComment(ps2, filename,
|
PatchLineComment comment2 = newComment(ps2, filename,
|
||||||
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
|
uuid, range, range.getEndLine(), otherUser, null, now, "comment on ps2",
|
||||||
side, rev2, Status.DRAFT);
|
side, rev2, Status.DRAFT);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
ChangeNotes notes = newNotes(c);
|
ChangeNotes notes = newNotes(c);
|
||||||
@ -1720,8 +1699,8 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
update.setPatchSetId(ps2);
|
update.setPatchSetId(ps2);
|
||||||
comment1.setStatus(Status.PUBLISHED);
|
comment1.setStatus(Status.PUBLISHED);
|
||||||
comment2.setStatus(Status.PUBLISHED);
|
comment2.setStatus(Status.PUBLISHED);
|
||||||
update.upsertComment(comment1);
|
update.putComment(comment1);
|
||||||
update.upsertComment(comment2);
|
update.putComment(comment2);
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
notes = newNotes(c);
|
notes = newNotes(c);
|
||||||
@ -1729,9 +1708,170 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
assertThat(notes.getComments()).hasSize(2);
|
assertThat(notes.getComments()).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void publishSubsetOfCommentsOnRevision() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
RevId rev1 = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
|
CommentRange range = new CommentRange(1, 1, 2, 1);
|
||||||
|
PatchSet.Id ps1 = c.currentPatchSetId();
|
||||||
|
short side = (short) 1;
|
||||||
|
|
||||||
|
ChangeUpdate update = newUpdate(c, otherUser);
|
||||||
|
update.setPatchSetId(ps1);
|
||||||
|
Timestamp now = TimeUtil.nowTs();
|
||||||
|
PatchLineComment comment1 = newComment(ps1, "file1",
|
||||||
|
"uuid1", range, range.getEndLine(), otherUser, null, now, "comment1",
|
||||||
|
side, rev1.get(), Status.DRAFT);
|
||||||
|
PatchLineComment comment2 = newComment(ps1, "file2",
|
||||||
|
"uuid2", range, range.getEndLine(), otherUser, null, now, "comment2",
|
||||||
|
side, rev1.get(), Status.DRAFT);
|
||||||
|
update.putComment(comment1);
|
||||||
|
update.putComment(comment2);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getDraftComments(otherUserId).get(rev1))
|
||||||
|
.containsExactly(comment1, comment2);
|
||||||
|
assertThat(notes.getComments()).isEmpty();
|
||||||
|
|
||||||
|
update = newUpdate(c, otherUser);
|
||||||
|
update.setPatchSetId(ps1);
|
||||||
|
comment2.setStatus(Status.PUBLISHED);
|
||||||
|
update.putComment(comment2);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
notes = newNotes(c);
|
||||||
|
assertThat(notes.getDraftComments(otherUserId).get(rev1))
|
||||||
|
.containsExactly(comment1);
|
||||||
|
assertThat(notes.getComments().get(rev1)).containsExactly(comment2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateWithServerIdent() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, internalUser);
|
||||||
|
update.setChangeMessage("A message.");
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
ChangeMessage msg = Iterables.getLast(newNotes(c).getChangeMessages());
|
||||||
|
assertThat(msg.getMessage()).isEqualTo("A message.");
|
||||||
|
assertThat(msg.getAuthor()).isNull();
|
||||||
|
|
||||||
|
update = newUpdate(c, internalUser);
|
||||||
|
exception.expect(UnsupportedOperationException.class);
|
||||||
|
update.putApproval("Code-Review", (short) 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterOutAndFixUpZombieDraftComments() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
RevId rev1 = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
|
||||||
|
CommentRange range = new CommentRange(1, 1, 2, 1);
|
||||||
|
PatchSet.Id ps1 = c.currentPatchSetId();
|
||||||
|
short side = (short) 1;
|
||||||
|
|
||||||
|
ChangeUpdate update = newUpdate(c, otherUser);
|
||||||
|
Timestamp now = TimeUtil.nowTs();
|
||||||
|
PatchLineComment comment1 = newComment(ps1, "file1",
|
||||||
|
"uuid1", range, range.getEndLine(), otherUser, null, now, "comment on ps1",
|
||||||
|
side, rev1.get(), Status.DRAFT);
|
||||||
|
PatchLineComment comment2 = newComment(ps1, "file2",
|
||||||
|
"uuid2", range, range.getEndLine(), otherUser, null, now, "another comment",
|
||||||
|
side, rev1.get(), Status.DRAFT);
|
||||||
|
update.putComment(comment1);
|
||||||
|
update.putComment(comment2);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
|
||||||
|
String refName = RefNames.refsDraftComments(otherUserId, c.getId());
|
||||||
|
ObjectId oldDraftId = exactRefAllUsers(refName);
|
||||||
|
|
||||||
|
update = newUpdate(c, otherUser);
|
||||||
|
update.setPatchSetId(ps1);
|
||||||
|
comment2.setStatus(Status.PUBLISHED);
|
||||||
|
update.putComment(comment2);
|
||||||
|
update.commit();
|
||||||
|
assertThat(exactRefAllUsers(refName)).isNotNull();
|
||||||
|
assertThat(exactRefAllUsers(refName)).isNotEqualTo(oldDraftId);
|
||||||
|
|
||||||
|
// Re-add draft version of comment2 back to draft ref without updating
|
||||||
|
// change ref. Simulates the case where deleting the draft failed
|
||||||
|
// non-atomically after adding the published comment succeeded.
|
||||||
|
ChangeDraftUpdate draftUpdate =
|
||||||
|
newUpdate(c, otherUser).createDraftUpdateIfNull();
|
||||||
|
comment2.setStatus(Status.DRAFT);
|
||||||
|
draftUpdate.putComment(comment2);
|
||||||
|
NoteDbUpdateManager manager = updateManagerFactory.create(c.getProject());
|
||||||
|
manager.add(draftUpdate);
|
||||||
|
manager.execute();
|
||||||
|
|
||||||
|
// Looking at drafts directly shows the zombie comment.
|
||||||
|
DraftCommentNotes draftNotes =
|
||||||
|
draftNotesFactory.create(c.getId(), otherUserId);
|
||||||
|
assertThat(draftNotes.load().getComments().get(rev1))
|
||||||
|
.containsExactly(comment1, comment2);
|
||||||
|
|
||||||
|
comment2.setStatus(Status.PUBLISHED); // Reset for later assertions.
|
||||||
|
|
||||||
|
// Zombie comment is filtered out of drafts via ChangeNotes.
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getDraftComments(otherUserId).get(rev1))
|
||||||
|
.containsExactly(comment1);
|
||||||
|
assertThat(notes.getComments().get(rev1))
|
||||||
|
.containsExactly(comment2);
|
||||||
|
|
||||||
|
update = newUpdate(c, otherUser);
|
||||||
|
update.setPatchSetId(ps1);
|
||||||
|
comment1.setStatus(Status.PUBLISHED);
|
||||||
|
update.putComment(comment1);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
// Updating an unrelated comment causes the zombie comment to get fixed up.
|
||||||
|
assertThat(exactRefAllUsers(refName)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateCommentsInSequentialUpdates() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
CommentRange range = new CommentRange(1, 1, 2, 1);
|
||||||
|
String rev = "abcd1234abcd1234abcd1234abcd1234abcd1234";
|
||||||
|
|
||||||
|
ChangeUpdate update1 = newUpdate(c, otherUser);
|
||||||
|
PatchLineComment comment1 = newComment(c.currentPatchSetId(), "filename",
|
||||||
|
"uuid1", range, range.getEndLine(), otherUser, null,
|
||||||
|
new Timestamp(update1.getWhen().getTime()), "comment 1", (short) 1, rev,
|
||||||
|
Status.PUBLISHED);
|
||||||
|
update1.putComment(comment1);
|
||||||
|
|
||||||
|
ChangeUpdate update2 = newUpdate(c, otherUser);
|
||||||
|
PatchLineComment comment2 = newComment(c.currentPatchSetId(), "filename",
|
||||||
|
"uuid2", range, range.getEndLine(), otherUser, null,
|
||||||
|
new Timestamp(update2.getWhen().getTime()), "comment 2", (short) 1, rev,
|
||||||
|
Status.PUBLISHED);
|
||||||
|
update2.putComment(comment2);
|
||||||
|
|
||||||
|
NoteDbUpdateManager manager = updateManagerFactory.create(project);
|
||||||
|
manager.add(update1);
|
||||||
|
manager.add(update2);
|
||||||
|
manager.execute();
|
||||||
|
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
List<PatchLineComment> comments = notes.getComments().get(new RevId(rev));
|
||||||
|
assertThat(comments).hasSize(2);
|
||||||
|
assertThat(comments.get(0).getMessage()).isEqualTo("comment 1");
|
||||||
|
assertThat(comments.get(1).getMessage()).isEqualTo("comment 2");
|
||||||
|
}
|
||||||
|
|
||||||
private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception {
|
private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception {
|
||||||
ObjectId dataId = notes.getNoteMap().getNote(noteId).getData();
|
ObjectId dataId = notes.revisionNoteMap.noteMap.getNote(noteId).getData();
|
||||||
return new String(
|
return new String(
|
||||||
rw.getObjectReader().open(dataId, OBJ_BLOB).getCachedBytes(), UTF_8);
|
rw.getObjectReader().open(dataId, OBJ_BLOB).getCachedBytes(), UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ObjectId exactRefAllUsers(String refName) throws Exception {
|
||||||
|
try (Repository allUsersRepo = repoManager.openRepository(allUsers)) {
|
||||||
|
Ref ref = allUsersRepo.exactRef(refName);
|
||||||
|
return ref != null ? ref.getObjectId() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
update.commit();
|
update.commit();
|
||||||
assertThat(update.getRefName()).isEqualTo("refs/changes/01/1/meta");
|
assertThat(update.getRefName()).isEqualTo("refs/changes/01/1/meta");
|
||||||
|
|
||||||
RevCommit commit = parseCommit(update.getRevision());
|
RevCommit commit = parseCommit(update.getResult());
|
||||||
assertBodyEquals("Update patch set 1\n"
|
assertBodyEquals("Update patch set 1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Patch-set: 1\n"
|
+ "Patch-set: 1\n"
|
||||||
@ -93,7 +93,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
+ "Subject: Change subject\n"
|
+ "Subject: Change subject\n"
|
||||||
+ "Branch: refs/heads/master\n"
|
+ "Branch: refs/heads/master\n"
|
||||||
+ "Commit: " + update.getCommit().name() + "\n",
|
+ "Commit: " + update.getCommit().name() + "\n",
|
||||||
update.getRevision());
|
update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -115,7 +115,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
+ "Subject: Subject\n"
|
+ "Subject: Subject\n"
|
||||||
+ "Branch: refs/heads/master\n"
|
+ "Branch: refs/heads/master\n"
|
||||||
+ "Commit: " + commit.name() + "\n",
|
+ "Commit: " + commit.name() + "\n",
|
||||||
update.getRevision());
|
update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -129,7 +129,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Patch-set: 1\n"
|
+ "Patch-set: 1\n"
|
||||||
+ "Label: -Code-Review\n",
|
+ "Label: -Code-Review\n",
|
||||||
update.getRevision());
|
update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -147,7 +147,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
submitLabel("Alternative-Code-Review", "NEED", null))));
|
submitLabel("Alternative-Code-Review", "NEED", null))));
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
RevCommit commit = parseCommit(update.getRevision());
|
RevCommit commit = parseCommit(update.getResult());
|
||||||
assertBodyEquals("Submit patch set 1\n"
|
assertBodyEquals("Submit patch set 1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Patch-set: 1\n"
|
+ "Patch-set: 1\n"
|
||||||
@ -185,7 +185,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
update.setChangeMessage("Comment on the change.");
|
update.setChangeMessage("Comment on the change.");
|
||||||
update.commit();
|
update.commit();
|
||||||
|
|
||||||
RevCommit commit = parseCommit(update.getRevision());
|
RevCommit commit = parseCommit(update.getResult());
|
||||||
assertBodyEquals("Update patch set 1\n"
|
assertBodyEquals("Update patch set 1\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Comment on the change.\n"
|
+ "Comment on the change.\n"
|
||||||
@ -214,7 +214,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
+ "Status: merged\n"
|
+ "Status: merged\n"
|
||||||
+ "Submission-id: 1-1453387607626-96fabc25\n"
|
+ "Submission-id: 1-1453387607626-96fabc25\n"
|
||||||
+ "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
|
+ "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
|
||||||
update.getRevision());
|
update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -228,7 +228,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Patch-set: 1\n"
|
+ "Patch-set: 1\n"
|
||||||
+ "Reviewer: Change Owner <1@gerrit>\n",
|
+ "Reviewer: Change Owner <1@gerrit>\n",
|
||||||
update.getRevision());
|
update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -246,7 +246,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
+ "\n"
|
+ "\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Patch-set: 1\n",
|
+ "Patch-set: 1\n",
|
||||||
update.getRevision());
|
update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -269,7 +269,7 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
+ "Testing paragraph 3\n"
|
+ "Testing paragraph 3\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Patch-set: 1\n",
|
+ "Patch-set: 1\n",
|
||||||
update.getRevision());
|
update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit parseCommit(ObjectId id) throws Exception {
|
private RevCommit parseCommit(ObjectId id) throws Exception {
|
||||||
|
@ -27,7 +27,7 @@ import com.google.gerrit.reviewdb.client.PatchSet;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.config.AllUsersName;
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.notedb.ChangeDraftUpdate;
|
import com.google.gerrit.server.notedb.ChangeDraftUpdate;
|
||||||
@ -88,14 +88,14 @@ public class TestChanges {
|
|||||||
|
|
||||||
public static ChangeUpdate newUpdate(Injector injector,
|
public static ChangeUpdate newUpdate(Injector injector,
|
||||||
GitRepositoryManager repoManager, NotesMigration migration, Change c,
|
GitRepositoryManager repoManager, NotesMigration migration, Change c,
|
||||||
final AllUsersName allUsers, final IdentifiedUser user)
|
final AllUsersName allUsers, final CurrentUser user)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
ChangeUpdate update = injector.createChildInjector(new FactoryModule() {
|
ChangeUpdate update = injector.createChildInjector(new FactoryModule() {
|
||||||
@Override
|
@Override
|
||||||
public void configure() {
|
public void configure() {
|
||||||
factory(ChangeUpdate.Factory.class);
|
factory(ChangeUpdate.Factory.class);
|
||||||
factory(ChangeDraftUpdate.Factory.class);
|
factory(ChangeDraftUpdate.Factory.class);
|
||||||
bind(IdentifiedUser.class).toInstance(user);
|
bind(CurrentUser.class).toInstance(user);
|
||||||
}
|
}
|
||||||
}).getInstance(ChangeUpdate.Factory.class).create(
|
}).getInstance(ChangeUpdate.Factory.class).create(
|
||||||
stubChangeControl(repoManager, migration, c, allUsers, user),
|
stubChangeControl(repoManager, migration, c, allUsers, user),
|
||||||
@ -112,8 +112,8 @@ public class TestChanges {
|
|||||||
// first patch set, so create one.
|
// first patch set, so create one.
|
||||||
try (Repository repo = repoManager.openRepository(c.getProject())) {
|
try (Repository repo = repoManager.openRepository(c.getProject())) {
|
||||||
TestRepository<Repository> tr = new TestRepository<>(repo);
|
TestRepository<Repository> tr = new TestRepository<>(repo);
|
||||||
PersonIdent ident =
|
PersonIdent ident = user.asIdentifiedUser()
|
||||||
user.newCommitterIdent(update.getWhen(), TimeZone.getDefault());
|
.newCommitterIdent(update.getWhen(), TimeZone.getDefault());
|
||||||
TestRepository<Repository>.CommitBuilder cb = tr.commit()
|
TestRepository<Repository>.CommitBuilder cb = tr.commit()
|
||||||
.author(ident)
|
.author(ident)
|
||||||
.committer(ident)
|
.committer(ident)
|
||||||
@ -132,7 +132,7 @@ public class TestChanges {
|
|||||||
private static ChangeControl stubChangeControl(
|
private static ChangeControl stubChangeControl(
|
||||||
GitRepositoryManager repoManager, NotesMigration migration,
|
GitRepositoryManager repoManager, NotesMigration migration,
|
||||||
Change c, AllUsersName allUsers,
|
Change c, AllUsersName allUsers,
|
||||||
IdentifiedUser user) throws OrmException {
|
CurrentUser user) throws OrmException {
|
||||||
ChangeControl ctl = EasyMock.createMock(ChangeControl.class);
|
ChangeControl ctl = EasyMock.createMock(ChangeControl.class);
|
||||||
expect(ctl.getChange()).andStubReturn(c);
|
expect(ctl.getChange()).andStubReturn(c);
|
||||||
expect(ctl.getProject()).andStubReturn(new Project(c.getProject()));
|
expect(ctl.getProject()).andStubReturn(new Project(c.getProject()));
|
||||||
|
Loading…
Reference in New Issue
Block a user