Merge branch 'stable-2.15'

* stable-2.15:
  NoteDbMigrator: Set checkExisting(false) on PackInserter
  ChangeRebuilderImpl: Fail when new NoteDb state is null
  PrimaryStorageMigrator: Log individual migrations at debug
  Document the deprecation of old change IDs
  Update JGit to get PackInserter fix
  AbstractChangeNotes: Never open repo when NoteDb is off
  MigrateToNoteDb: Only reindex change index
  PolyGerrit: Add background-repeat to gr-main-header
  Revert "Add privateByDefault config to /config/server/info"
  Private state changed event
  WIP state changed event
  Populate ChangeAttribute with WIP and private status.
  Fix invalid json example in POST access endpoint
  Add default values in create project dialog and group config
  Update JGit to 4.9.2.201712150930-r
  gerrit_plugin: Don't add Implementation-Vendor manifest entry
  Fix schema_154 migration from version 2.13 and older
  Default to writing comments in JSON format

Change-Id: Iee55a0f00ce2430cd400c64491acde3651a0390d
This commit is contained in:
Dave Borowitz 2017-12-21 12:05:24 -05:00
commit 3619a8ffb9
33 changed files with 484 additions and 56 deletions

View File

@ -261,6 +261,33 @@ oldTopic:: Topic name before it was changed.
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
=== Work In Progress State Changed
Sent when the the link:intro-user.html#wip[WIP] state of the change has changed.
type:: wip-state-changed
change:: link:json.html#change[change attribute]
changer:: link:json.html#account[account attribute]
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
=== Private State Changed
Sent when the the link:intro-user.html#private-changes[private] state of the
change has changed.
type:: private-state-changed
change:: link:json.html#change[change attribute]
changer:: link:json.html#account[account attribute]
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
=== Vote Deleted
Sent when a vote was removed from a change.

View File

@ -45,6 +45,12 @@ status:: Current state of this change.
ABANDONED;; Change was abandoned by its owner or administrator.
private:: Boolean indicating if the change is
link:intro-user.html#private-changes[private].
wip:: Boolean indicating if the change is
link:intro-user.html#wip[work in progress].
comments:: All inline/file comments for this change in <<message,message attributes>>.
trackingIds:: Issue tracking system links in

View File

@ -1386,9 +1386,6 @@ allowed].
|`large_change` ||
link:config-gerrit.html#change.largeChange[Number of changed lines from
which on a change is considered as a large change].
|`private_by_default` |not set if `false`|
Returns true if changes are by default created as private.
See link:config-gerrit.html#change.privateByDefault[privateByDefault]
|`reply_label` ||
link:config-gerrit.html#change.replyTooltip[Label name for the reply
button].

View File

@ -1165,12 +1165,14 @@ As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
{
"remove": [
"refs/*": {
"permissions": {
"read": {
"rules": {
"c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
"action": "ALLOW"
{
"refs/*": {
"permissions": {
"read": {
"rules": {
"c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
"action": "ALLOW"
}
}
}
}

View File

@ -19,7 +19,6 @@ public class ChangeConfigInfo {
public Boolean showAssigneeInChangesTable;
public Boolean allowDrafts;
public int largeChange;
public Boolean privateByDefault;
public String replyLabel;
public String replyTooltip;
public int updateDelay;

View File

@ -0,0 +1,21 @@
// Copyright (C) 2017 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.extensions.events;
public interface PrivateStateChangedListener {
interface Event extends ChangeEvent {}
void onPrivateStateChanged(Event event);
}

View File

@ -0,0 +1,21 @@
// Copyright (C) 2017 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.extensions.events;
public interface WorkInProgressStateChangedListener {
interface Event extends ChangeEvent {}
void onWorkInProgressStateChanged(Event event);
}

View File

@ -31,6 +31,7 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.index.DummyIndexModule;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator;
import com.google.inject.Inject;
import com.google.inject.Injector;
@ -145,7 +146,12 @@ public class MigrateToNoteDb extends SiteProgram {
// their server is offline.
List<String> reindexArgs =
ImmutableList.of(
"--site-path", getSitePath().toString(), "--threads", Integer.toString(threads));
"--site-path",
getSitePath().toString(),
"--threads",
Integer.toString(threads),
"--index",
ChangeSchemaDefinitions.NAME);
System.out.println("Migration complete, reindexing changes with:");
System.out.println(" reindex " + reindexArgs.stream().collect(joining(" ")));
Reindex reindexPgm = new Reindex();

View File

@ -40,17 +40,20 @@ public class DeletePrivate
private final ChangeMessagesUtil cmUtil;
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
private final SetPrivateOp.Factory setPrivateOpFactory;
@Inject
DeletePrivate(
Provider<ReviewDb> dbProvider,
RetryHelper retryHelper,
ChangeMessagesUtil cmUtil,
PermissionBackend permissionBackend) {
PermissionBackend permissionBackend,
SetPrivateOp.Factory setPrivateOpFactory) {
super(retryHelper);
this.dbProvider = dbProvider;
this.cmUtil = cmUtil;
this.permissionBackend = permissionBackend;
this.setPrivateOpFactory = setPrivateOpFactory;
}
@Override
@ -65,7 +68,7 @@ public class DeletePrivate
throw new ResourceConflictException("change is not private");
}
SetPrivateOp op = new SetPrivateOp(cmUtil, false, input);
SetPrivateOp op = setPrivateOpFactory.create(cmUtil, false, input);
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(), rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {

View File

@ -32,8 +32,9 @@ public class DeletePrivateByPost extends DeletePrivate implements UiAction<Chang
Provider<ReviewDb> dbProvider,
RetryHelper retryHelper,
ChangeMessagesUtil cmUtil,
PermissionBackend permissionBackend) {
super(dbProvider, retryHelper, cmUtil, permissionBackend);
PermissionBackend permissionBackend,
SetPrivateOp.Factory setPrivateOpFactory) {
super(dbProvider, retryHelper, cmUtil, permissionBackend, setPrivateOpFactory);
}
@Override

View File

@ -177,6 +177,7 @@ public class Module extends RestApiModule {
factory(ReviewerResource.Factory.class);
factory(SetAssigneeOp.Factory.class);
factory(SetHashtagsOp.Factory.class);
factory(SetPrivateOp.Factory.class);
factory(WorkInProgressOp.Factory.class);
}
}

View File

@ -43,17 +43,20 @@ public class PostPrivate
private final ChangeMessagesUtil cmUtil;
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
private final SetPrivateOp.Factory setPrivateOpFactory;
@Inject
PostPrivate(
Provider<ReviewDb> dbProvider,
RetryHelper retryHelper,
ChangeMessagesUtil cmUtil,
PermissionBackend permissionBackend) {
PermissionBackend permissionBackend,
SetPrivateOp.Factory setPrivateOpFactory) {
super(retryHelper);
this.dbProvider = dbProvider;
this.cmUtil = cmUtil;
this.permissionBackend = permissionBackend;
this.setPrivateOpFactory = setPrivateOpFactory;
}
@Override
@ -68,7 +71,7 @@ public class PostPrivate
return Response.ok("");
}
SetPrivateOp op = new SetPrivateOp(cmUtil, true, input);
SetPrivateOp op = setPrivateOpFactory.create(cmUtil, true, input);
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(), rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {

View File

@ -19,10 +19,14 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.extensions.events.PrivateStateChanged;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.Context;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
public class SetPrivateOp implements BatchUpdateOp {
public static class Input {
@ -35,19 +39,32 @@ public class SetPrivateOp implements BatchUpdateOp {
}
}
public interface Factory {
SetPrivateOp create(ChangeMessagesUtil cmUtil, boolean isPrivate, Input input);
}
private final ChangeMessagesUtil cmUtil;
private final boolean isPrivate;
private final Input input;
private final PrivateStateChanged privateStateChanged;
SetPrivateOp(ChangeMessagesUtil cmUtil, boolean isPrivate, Input input) {
private Change change;
@Inject
SetPrivateOp(
PrivateStateChanged privateStateChanged,
@Assisted ChangeMessagesUtil cmUtil,
@Assisted boolean isPrivate,
@Assisted Input input) {
this.cmUtil = cmUtil;
this.isPrivate = isPrivate;
this.input = input;
this.privateStateChanged = privateStateChanged;
}
@Override
public boolean updateChange(ChangeContext ctx) throws ResourceConflictException, OrmException {
Change change = ctx.getChange();
change = ctx.getChange();
ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
change.setPrivate(isPrivate);
change.setLastUpdatedOn(ctx.getWhen());
@ -56,6 +73,11 @@ public class SetPrivateOp implements BatchUpdateOp {
return true;
}
@Override
public void postUpdate(Context ctx) {
privateStateChanged.fire(change, ctx.getAccount(), ctx.getWhen());
}
private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
Change c = ctx.getChange();
StringBuilder buf = new StringBuilder(c.isPrivate() ? "Set private" : "Unset private");

View File

@ -25,6 +25,7 @@ import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
@ -58,6 +59,7 @@ public class WorkInProgressOp implements BatchUpdateOp {
private final boolean workInProgress;
private final Input in;
private final NotifyHandling notify;
private final WorkInProgressStateChanged stateChanged;
private Change change;
private ChangeNotes notes;
@ -69,11 +71,13 @@ public class WorkInProgressOp implements BatchUpdateOp {
ChangeMessagesUtil cmUtil,
EmailReviewComments.Factory email,
PatchSetUtil psUtil,
WorkInProgressStateChanged stateChanged,
@Assisted boolean workInProgress,
@Assisted Input in) {
this.cmUtil = cmUtil;
this.email = email;
this.psUtil = psUtil;
this.stateChanged = stateChanged;
this.workInProgress = workInProgress;
this.in = in;
notify =
@ -121,6 +125,7 @@ public class WorkInProgressOp implements BatchUpdateOp {
@Override
public void postUpdate(Context ctx) {
stateChanged.fire(change, ctx.getAccount(), ctx.getWhen());
if (workInProgress || notify.ordinal() < NotifyHandling.OWNER_REVIEWERS.ordinal()) {
return;
}

View File

@ -46,6 +46,7 @@ import com.google.gerrit.extensions.events.HeadUpdatedListener;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.events.PluginEventListener;
import com.google.gerrit.extensions.events.PrivateStateChangedListener;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.gerrit.extensions.events.ProjectIndexedListener;
import com.google.gerrit.extensions.events.ReviewerAddedListener;
@ -54,6 +55,7 @@ import com.google.gerrit.extensions.events.RevisionCreatedListener;
import com.google.gerrit.extensions.events.TopicEditedListener;
import com.google.gerrit.extensions.events.UsageDataPublishedListener;
import com.google.gerrit.extensions.events.VoteDeletedListener;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
@ -326,9 +328,11 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), ChangeRestoredListener.class);
DynamicSet.setOf(binder(), ChangeRevertedListener.class);
DynamicSet.setOf(binder(), PrivateStateChangedListener.class);
DynamicSet.setOf(binder(), ReviewerAddedListener.class);
DynamicSet.setOf(binder(), ReviewerDeletedListener.class);
DynamicSet.setOf(binder(), VoteDeletedListener.class);
DynamicSet.setOf(binder(), WorkInProgressStateChangedListener.class);
DynamicSet.setOf(binder(), RevisionCreatedListener.class);
DynamicSet.setOf(binder(), TopicEditedListener.class);
DynamicSet.setOf(binder(), AgreementSignupListener.class);

View File

@ -16,6 +16,7 @@ package com.google.gerrit.server.data;
import com.google.gerrit.extensions.common.PluginDefinedInfo;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class ChangeAttribute {
@ -35,6 +36,10 @@ public class ChangeAttribute {
public Boolean open;
public Change.Status status;
public List<MessageAttribute> comments;
public Boolean wip;
@SerializedName("private")
public Boolean isPrivate;
public List<TrackingIdAttribute> trackingIds;
public PatchSetAttribute currentPatchSet;

View File

@ -158,6 +158,8 @@ public class EventFactory {
a.assignee = asAccountAttribute(change.getAssignee());
a.status = change.getStatus();
a.createdOn = change.getCreatedOn().getTime() / 1000L;
a.wip = change.isWorkInProgress() ? true : null;
a.isPrivate = change.isPrivate() ? true : null;
return a;
}

View File

@ -30,6 +30,7 @@ public class EventTypes {
register(CommitReceivedEvent.TYPE, CommitReceivedEvent.class);
register(HashtagsChangedEvent.TYPE, HashtagsChangedEvent.class);
register(PatchSetCreatedEvent.TYPE, PatchSetCreatedEvent.class);
register(PrivateStateChangedEvent.TYPE, PrivateStateChangedEvent.class);
register(ProjectCreatedEvent.TYPE, ProjectCreatedEvent.class);
register(RefReceivedEvent.TYPE, RefReceivedEvent.class);
register(RefUpdatedEvent.TYPE, RefUpdatedEvent.class);
@ -37,6 +38,7 @@ public class EventTypes {
register(ReviewerDeletedEvent.TYPE, ReviewerDeletedEvent.class);
register(TopicChangedEvent.TYPE, TopicChangedEvent.class);
register(VoteDeletedEvent.TYPE, VoteDeletedEvent.class);
register(WorkInProgressStateChangedEvent.TYPE, WorkInProgressStateChangedEvent.class);
}
/**

View File

@ -0,0 +1,28 @@
// Copyright (C) 2017 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.events;
import com.google.common.base.Supplier;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class PrivateStateChangedEvent extends ChangeEvent {
static final String TYPE = "private-state-changed";
public Supplier<AccountAttribute> changer;
protected PrivateStateChangedEvent(Change change) {
super(TYPE, change);
}
}

View File

@ -31,11 +31,13 @@ import com.google.gerrit.extensions.events.CommentAddedListener;
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.HashtagsEditedListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.events.PrivateStateChangedListener;
import com.google.gerrit.extensions.events.ReviewerAddedListener;
import com.google.gerrit.extensions.events.ReviewerDeletedListener;
import com.google.gerrit.extensions.events.RevisionCreatedListener;
import com.google.gerrit.extensions.events.TopicEditedListener;
import com.google.gerrit.extensions.events.VoteDeletedListener;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
@ -76,6 +78,8 @@ public class StreamEventsApiListener
ChangeAbandonedListener,
ChangeMergedListener,
ChangeRestoredListener,
WorkInProgressStateChangedListener,
PrivateStateChangedListener,
CommentAddedListener,
GitReferenceUpdatedListener,
HashtagsEditedListener,
@ -99,11 +103,15 @@ public class StreamEventsApiListener
.to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), HashtagsEditedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), PrivateStateChangedListener.class)
.to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ReviewerAddedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ReviewerDeletedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), RevisionCreatedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), TopicEditedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), VoteDeletedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), WorkInProgressStateChangedListener.class)
.to(StreamEventsApiListener.class);
}
}
@ -461,6 +469,36 @@ public class StreamEventsApiListener
}
}
@Override
public void onWorkInProgressStateChanged(WorkInProgressStateChangedListener.Event ev) {
try {
Change change = getChange(ev.getChange());
WorkInProgressStateChangedEvent event = new WorkInProgressStateChangedEvent(change);
event.change = changeAttributeSupplier(change);
event.changer = accountAttributeSupplier(ev.getWho());
dispatcher.get().postEvent(change, event);
} catch (OrmException | PermissionBackendException e) {
log.error("Failed to dispatch event", e);
}
}
@Override
public void onPrivateStateChanged(PrivateStateChangedListener.Event ev) {
try {
Change change = getChange(ev.getChange());
PrivateStateChangedEvent event = new PrivateStateChangedEvent(change);
event.change = changeAttributeSupplier(change);
event.changer = accountAttributeSupplier(ev.getWho());
dispatcher.get().postEvent(change, event);
} catch (OrmException | PermissionBackendException e) {
log.error("Failed to dispatch event", e);
}
}
@Override
public void onVoteDeleted(VoteDeletedListener.Event ev) {
try {

View File

@ -0,0 +1,28 @@
// Copyright (C) 2017 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.events;
import com.google.common.base.Supplier;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.data.AccountAttribute;
public class WorkInProgressStateChangedEvent extends ChangeEvent {
static final String TYPE = "wip-state-changed";
public Supplier<AccountAttribute> changer;
protected WorkInProgressStateChangedEvent(Change change) {
super(TYPE, change);
}
}

View File

@ -0,0 +1,68 @@
// Copyright (C) 2017 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.extensions.events;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.events.PrivateStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.sql.Timestamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrivateStateChanged {
private static final Logger log = LoggerFactory.getLogger(PrivateStateChanged.class);
private final DynamicSet<PrivateStateChangedListener> listeners;
private final EventUtil util;
@Inject
PrivateStateChanged(DynamicSet<PrivateStateChangedListener> listeners, EventUtil util) {
this.listeners = listeners;
this.util = util;
}
public void fire(Change change, Account account, Timestamp when) {
if (!listeners.iterator().hasNext()) {
return;
}
try {
Event event = new Event(util.changeInfo(change), util.accountInfo(account), when);
for (PrivateStateChangedListener l : listeners) {
try {
l.onPrivateStateChanged(event);
} catch (Exception e) {
util.logEventListenerError(event, l, e);
}
}
} catch (OrmException e) {
log.error("Couldn't fire event", e);
}
}
private static class Event extends AbstractChangeEvent
implements PrivateStateChangedListener.Event {
protected Event(ChangeInfo change, AccountInfo who, Timestamp when) {
super(change, who, when, NotifyHandling.ALL);
}
}
}

View File

@ -0,0 +1,69 @@
// Copyright (C) 2017 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.extensions.events;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.sql.Timestamp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WorkInProgressStateChanged {
private static final Logger log = LoggerFactory.getLogger(WorkInProgressStateChanged.class);
private final DynamicSet<WorkInProgressStateChangedListener> listeners;
private final EventUtil util;
@Inject
WorkInProgressStateChanged(
DynamicSet<WorkInProgressStateChangedListener> listeners, EventUtil util) {
this.listeners = listeners;
this.util = util;
}
public void fire(Change change, Account account, Timestamp when) {
if (!listeners.iterator().hasNext()) {
return;
}
try {
Event event = new Event(util.changeInfo(change), util.accountInfo(account), when);
for (WorkInProgressStateChangedListener l : listeners) {
try {
l.onWorkInProgressStateChanged(event);
} catch (Exception e) {
util.logEventListenerError(event, l, e);
}
}
} catch (OrmException e) {
log.error("Couldn't fire event", e);
}
}
private static class Event extends AbstractChangeEvent
implements WorkInProgressStateChangedListener.Event {
protected Event(ChangeInfo change, AccountInfo who, Timestamp when) {
super(change, who, when, NotifyHandling.ALL);
}
}
}

View File

@ -147,7 +147,10 @@ public abstract class AbstractChangeNotes<T> {
throw new OrmException("NoteDb is required to read change " + changeId);
}
boolean readOrWrite = read || args.migration.rawWriteChangesSetting();
if (!readOrWrite && !autoRebuild) {
if (!readOrWrite) {
// Don't even open the repo if we neither write to nor read from NoteDb. It's possible that
// there is some garbage in the noteDbState field and/or the repo, but at this point NoteDb is
// completely off so it's none of our business.
loadDefaults();
return self();
}

View File

@ -737,6 +737,9 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
if (state == null) {
return super.openHandle(repo, id);
} else if (shouldExist) {
// TODO(dborowitz): This means we have a state recorded in noteDbState but the ref doesn't
// exist for whatever reason. Doesn't this mean we should trigger an auto-rebuild, rather
// than throwing?
throw new NoSuchChangeException(getChangeId());
}
}

View File

@ -265,7 +265,7 @@ public class PrimaryStorageMigrator {
// the primary storage to NoteDb.
setPrimaryStorageNoteDb(id, rebuiltState);
log.info("Migrated change {} to NoteDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
log.debug("Migrated change {} to NoteDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
}
private Change setReadOnlyInReviewDb(Change.Id id) throws OrmException {
@ -399,7 +399,7 @@ public class PrimaryStorageMigrator {
rebuilder.rebuildReviewDb(db(), project, id);
setPrimaryStorageReviewDb(id, newMetaId);
releaseReadOnlyLeaseInNoteDb(project, id);
log.info("Migrated change {} to ReviewDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
log.debug("Migrated change {} to ReviewDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
}
private ObjectId setReadOnlyInNoteDb(Project.NameKey project, Change.Id id)

View File

@ -15,6 +15,7 @@
package com.google.gerrit.server.notedb.rebuild;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
@ -228,9 +229,16 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
throw new NoSuchChangeException(changeId);
}
final String oldNoteDbState = change.getNoteDbState();
String oldNoteDbStateStr = change.getNoteDbState();
Result r = manager.stageAndApplyDelta(change);
final String newNoteDbState = change.getNoteDbState();
String newNoteDbStateStr = change.getNoteDbState();
if (newNoteDbStateStr == null) {
throw new OrmException(
"Rebuilding change %s produced no writes to NoteDb: "
+ bundleReader.fromReviewDb(db, changeId));
}
NoteDbChangeState newNoteDbState =
checkNotNull(NoteDbChangeState.parse(changeId, newNoteDbStateStr));
try {
db.changes()
.atomicUpdate(
@ -241,15 +249,15 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
if (checkReadOnly) {
NoteDbChangeState.checkNotReadOnly(change, skewMs);
}
String currNoteDbState = change.getNoteDbState();
if (Objects.equals(currNoteDbState, newNoteDbState)) {
String currNoteDbStateStr = change.getNoteDbState();
if (Objects.equals(currNoteDbStateStr, newNoteDbStateStr)) {
// Another thread completed the same rebuild we were about to.
throw new AbortUpdateException();
} else if (!Objects.equals(oldNoteDbState, currNoteDbState)) {
} else if (!Objects.equals(oldNoteDbStateStr, currNoteDbStateStr)) {
// Another thread updated the state to something else.
throw new ConflictingUpdateRuntimeException(change, oldNoteDbState);
throw new ConflictingUpdateRuntimeException(change, oldNoteDbStateStr);
}
change.setNoteDbState(newNoteDbState);
change.setNoteDbState(newNoteDbStateStr);
return change;
}
});
@ -259,10 +267,9 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
// rebuild had executed before the other thread.
throw new ConflictingUpdateException(e);
} catch (AbortUpdateException e) {
if (NoteDbChangeState.parse(changeId, newNoteDbState)
.isUpToDate(
manager.getChangeRepo().cmds.getRepoRefCache(),
manager.getAllUsersRepo().cmds.getRepoRefCache())) {
if (newNoteDbState.isUpToDate(
manager.getChangeRepo().cmds.getRepoRefCache(),
manager.getAllUsersRepo().cmds.getRepoRefCache())) {
// If the state in ReviewDb matches NoteDb at this point, it means another thread
// successfully completed this rebuild. It's ok to not execute the update in this case,
// since the object referenced in the Result was flushed to the repo by whatever thread won

View File

@ -91,6 +91,7 @@ import java.util.function.Predicate;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.internal.storage.file.PackInserter;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
@ -748,9 +749,12 @@ public class NoteDbMigrator implements AutoCloseable {
}
private static ObjectInserter newPackInserter(Repository repo) {
return repo instanceof FileRepository
? ((FileRepository) repo).getObjectDatabase().newPackInserter()
: repo.newObjectInserter();
if (!(repo instanceof FileRepository)) {
return repo.newObjectInserter();
}
PackInserter ins = ((FileRepository) repo).getObjectDatabase().newPackInserter();
ins.checkExisting(false);
return ins;
}
private boolean rebuildProject(

View File

@ -239,7 +239,6 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
toBoolean(
cfg.getBoolean("change", "showAssigneeInChangesTable", false) && hasAssigneeInIndex);
info.largeChange = cfg.getInt("change", "largeChange", 500);
info.privateByDefault = toBoolean(cfg.getBoolean("change", "privateByDefault", false));
info.replyTooltip =
Optional.ofNullable(cfg.getString("change", null, "replyTooltip")).orElse("Reply and score")
+ " (Shortcut: a)";

View File

@ -14,6 +14,9 @@
package com.google.gerrit.server.schema;
import static java.util.stream.Collectors.toMap;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@ -22,23 +25,40 @@ import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Migrate accounts to NoteDb. */
public class Schema_154 extends SchemaVersion {
private static final Logger log = LoggerFactory.getLogger(Schema_154.class);
private static final String TABLE = "accounts";
private static final Map<String, AccountSetter> ACCOUNT_FIELDS_MAP =
ImmutableMap.<String, AccountSetter>builder()
.put("full_name", (a, rs, field) -> a.setFullName(rs.getString(field)))
.put("preferred_email", (a, rs, field) -> a.setPreferredEmail(rs.getString(field)))
.put("status", (a, rs, field) -> a.setStatus(rs.getString(field)))
.put("inactive", (a, rs, field) -> a.setActive(rs.getString(field).equals("N")))
.build();
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
private final Provider<PersonIdent> serverIdent;
@ -76,23 +96,24 @@ public class Schema_154 extends SchemaVersion {
}
private Set<Account> scanAccounts(ReviewDb db, ProgressMonitor pm) throws SQLException {
Map<String, AccountSetter> fields = getFields(db);
if (fields.isEmpty()) {
log.warn("Only account_id and registered_on fields are migrated for accounts");
}
List<String> queryFields = new ArrayList<>();
queryFields.add("account_id");
queryFields.add("registered_on");
queryFields.addAll(fields.keySet());
String query = "SELECT " + String.join(", ", queryFields) + String.format(" FROM %s", TABLE);
try (Statement stmt = newStatement(db);
ResultSet rs =
stmt.executeQuery(
"SELECT account_id,"
+ " registered_on,"
+ " full_name, "
+ " preferred_email,"
+ " status,"
+ " inactive"
+ " FROM accounts")) {
ResultSet rs = stmt.executeQuery(query)) {
Set<Account> s = new HashSet<>();
while (rs.next()) {
Account a = new Account(new Account.Id(rs.getInt(1)), rs.getTimestamp(2));
a.setFullName(rs.getString(3));
a.setPreferredEmail(rs.getString(4));
a.setStatus(rs.getString(5));
a.setActive(rs.getString(6).equals("N"));
for (Map.Entry<String, AccountSetter> field : fields.entrySet()) {
field.getValue().set(a, rs, field.getKey());
}
s.add(a);
pm.update(1);
}
@ -100,6 +121,17 @@ public class Schema_154 extends SchemaVersion {
}
}
private Map<String, AccountSetter> getFields(ReviewDb db) throws SQLException {
JdbcSchema schema = (JdbcSchema) db;
Connection connection = schema.getConnection();
Set<String> columns = schema.getDialect().listColumns(connection, TABLE);
return ACCOUNT_FIELDS_MAP
.entrySet()
.stream()
.filter(e -> columns.contains(e.getKey()))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}
private void updateAccountInNoteDb(Repository allUsersRepo, Account account)
throws IOException, ConfigInvalidException {
MetaDataUpdate md =
@ -112,4 +144,9 @@ public class Schema_154 extends SchemaVersion {
accountConfig.setAccount(account);
accountConfig.commit(md);
}
@FunctionalInterface
private interface AccountSetter {
void set(Account a, ResultSet rs, String field) throws SQLException;
}
}

View File

@ -56,7 +56,6 @@ public class ServerInfoIT extends AbstractDaemonTest {
// change
@GerritConfig(name = "change.allowDrafts", value = "false")
@GerritConfig(name = "change.largeChange", value = "300")
@GerritConfig(name = "change.privateByDefault", value = "true")
@GerritConfig(name = "change.replyTooltip", value = "Publish votes and draft comments")
@GerritConfig(name = "change.replyLabel", value = "Vote")
@GerritConfig(name = "change.updateDelay", value = "50s")
@ -101,7 +100,6 @@ public class ServerInfoIT extends AbstractDaemonTest {
// change
assertThat(i.change.allowDrafts).isNull();
assertThat(i.change.largeChange).isEqualTo(300);
assertThat(i.change.privateByDefault).isTrue();
assertThat(i.change.replyTooltip).startsWith("Publish votes and draft comments");
assertThat(i.change.replyLabel).isEqualTo("Vote\u2026");
assertThat(i.change.updateDelay).isEqualTo(50);

View File

@ -1287,6 +1287,28 @@ public class ChangeRebuilderIT extends AbstractDaemonTest {
assertThat(newPs3.getCreatedOn()).isGreaterThan(ps1.getCreatedOn());
}
@Test
public void ignoreNoteDbStateWithNoCorrespondingRefWhenWritesAndReadsDisabled() throws Exception {
PushOneCommit.Result r = createChange();
Change.Id id = r.getChange().getId();
ReviewDb db = getUnwrappedDb();
Change c = db.changes().get(id);
c.setNoteDbState("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
db.changes().update(Collections.singleton(c));
c = db.changes().get(id);
String refName = RefNames.changeMetaRef(id);
assertThat(getMetaRef(project, refName)).isNull();
ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
assertThat(notes.getChange().getRowVersion()).isEqualTo(c.getRowVersion());
notes = notesFactory.createChecked(dbProvider.get(), project, id);
assertThat(notes.getChange().getRowVersion()).isEqualTo(c.getRowVersion());
assertThat(getMetaRef(project, refName)).isNull();
}
private void assertChangesReadOnly(RestApiException e) throws Exception {
Throwable cause = e.getCause();
assertThat(cause).isInstanceOf(UpdateException.class);

View File

@ -45,10 +45,7 @@ def gerrit_plugin(
native.java_binary(
name = '%s__non_stamped' % name,
deploy_manifest_lines = manifest_entries + [
"Gerrit-ApiType: plugin",
"Implementation-Vendor: Gerrit Code Review",
],
deploy_manifest_lines = manifest_entries + ["Gerrit-ApiType: plugin"],
main_class = 'Dummy',
runtime_deps = [
':%s__plugin' % name,