Merge changes Iee55a0f0,I3cf28f16

* changes:
  Merge branch 'stable-2.15'
  Update JGit to 4.9.2.201712150930-r.175-gd8a24ac1c
This commit is contained in:
David Pursehouse
2017-12-21 23:00:38 +00:00
committed by Gerrit Code Review
34 changed files with 490 additions and 62 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

@@ -1,6 +1,6 @@
load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_LOCAL", "MAVEN_CENTRAL", "maven_jar")
_JGIT_VERS = "4.9.2.201712150930-r.171-gfdbaa25db"
_JGIT_VERS = "4.9.2.201712150930-r.175-gd8a24ac1c"
_DOC_VERS = "4.9.2.201712150930-r" # Set to _JGIT_VERS unless using a snapshot
@@ -26,28 +26,28 @@ def jgit_maven_repos():
name = "jgit_lib",
artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
repository = _JGIT_REPO,
sha1 = "29b822410b29286a09df728f8379e5cb8b1a486e",
src_sha1 = "5106b81910a057470cfd2584d9cb3502bcbebbc2",
sha1 = "4286555f5851fbfcf0ff89ec884f7f806b0c7e37",
src_sha1 = "5e38b7e7936ebbd778914dc4f9d76d245a5a4518",
unsign = True,
)
maven_jar(
name = "jgit_servlet",
artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
repository = _JGIT_REPO,
sha1 = "01f6718f6b629e28caad38e00190811b38574e74",
sha1 = "da0d2c7a048cc213274cd06a5baf277c85ea152e",
unsign = True,
)
maven_jar(
name = "jgit_archive",
artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
repository = _JGIT_REPO,
sha1 = "9c9e9332e7dc724dbe1837e21feccd98bc25e6b4",
sha1 = "1cd91bedf8b591626d341c2d896181ddba5f9aa9",
)
maven_jar(
name = "jgit_junit",
artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
repository = _JGIT_REPO,
sha1 = "4154c70b78b62035dad446332b24f7816b7a2a1b",
sha1 = "5b7cc1aa0ba062ad587b6daa64743b704b997f74",
unsign = True,
)

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,