Replace all transactions with single row updates

We now try to perform writes in a reasonably sane order, but only
update one record at a time (or do a batch against the same table
but not in a transaction).

This matches with eventually consistent databases like Cassandra
where we can't rely upon a transaction being available for any
sort of update we make.

Change-Id: Ib8cdff05f08467dfe3258f03b72d5cabb5b2641f
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-12-31 14:25:56 -08:00
parent b1dac0a73b
commit 113860696a
24 changed files with 858 additions and 882 deletions

View File

@@ -41,7 +41,6 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.gwtorm.client.Transaction;
import com.google.gwtorm.client.impl.ListResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -402,8 +401,8 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
public VoidResult run(final ReviewDb db) throws OrmException {
final Account.Id me = getAccountId();
final Set<Change.Id> existing = currentUser.get().getStarredChanges();
final ArrayList<StarredChange> add = new ArrayList<StarredChange>();
final ArrayList<StarredChange> remove = new ArrayList<StarredChange>();
List<StarredChange> add = new ArrayList<StarredChange>();
List<StarredChange.Key> remove = new ArrayList<StarredChange.Key>();
if (req.getAddSet() != null) {
for (final Change.Id id : req.getAddSet()) {
@@ -415,18 +414,12 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
if (req.getRemoveSet() != null) {
for (final Change.Id id : req.getRemoveSet()) {
if (existing.contains(id)) {
remove.add(new StarredChange(new StarredChange.Key(me, id)));
}
remove.add(new StarredChange.Key(me, id));
}
}
if (!add.isEmpty() || !remove.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.starredChanges().insert(add);
db.starredChanges().delete(remove);
txn.commit();
}
db.starredChanges().insert(add);
db.starredChanges().deleteKeys(remove);
return VoidResult.INSTANCE;
}
});

View File

@@ -48,7 +48,6 @@ import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtjsonrpc.server.ValidToken;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -152,13 +151,8 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
throw new Failure(new NoSuchEntityException());
}
final List<AccountSshKey> k = db.accountSshKeys().get(ids).toList();
if (!k.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountSshKeys().delete(k, txn);
txn.commit();
uncacheSshKeys();
}
db.accountSshKeys().deleteKeys(ids);
uncacheSshKeys();
return VoidResult.INSTANCE;
}

View File

@@ -31,7 +31,6 @@ import com.google.gerrit.server.project.ProjectControl;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -153,14 +152,7 @@ class AccountServiceImpl extends BaseServiceImplementation implements
throw new Failure(new NoSuchEntityException());
}
final List<AccountProjectWatch> k =
db.accountProjectWatches().get(keys).toList();
if (!k.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountProjectWatches().delete(k, txn);
txn.commit();
}
db.accountProjectWatches().deleteKeys(keys);
return VoidResult.INSTANCE;
}
});

View File

@@ -21,7 +21,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -73,9 +72,7 @@ class DeleteExternalIds extends Handler<Set<AccountExternalId.Key>> {
}
if (!toDelete.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountExternalIds().delete(toDelete, txn);
txn.commit();
db.accountExternalIds().delete(toDelete);
accountCache.evict(user.getAccountId());
for (AccountExternalId e : toDelete) {
byEmailCache.evict(e.getEmailAddress());

View File

@@ -35,7 +35,6 @@ import com.google.gerrit.server.account.Realm;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -231,12 +230,10 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
AccountGroupMember m = db.accountGroupMembers().get(key);
if (m == null) {
m = new AccountGroupMember(key);
final Transaction txn = db.beginTransaction();
db.accountGroupMembers().insert(Collections.singleton(m), txn);
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(m,
getAccountId())), txn);
txn.commit();
getAccountId())));
db.accountGroupMembers().insert(Collections.singleton(m));
accountCache.evict(m.getAccountId());
}
@@ -279,19 +276,18 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
}
}
final Transaction txn = db.beginTransaction();
db.accountGroupMembers().delete(Collections.singleton(m), txn);
if (audit != null) {
audit.removed(me);
db.accountGroupMembersAudit().update(
Collections.singleton(audit), txn);
db.accountGroupMembersAudit()
.update(Collections.singleton(audit));
} else {
audit = new AccountGroupMemberAudit(m, me);
audit.removedLegacy();
db.accountGroupMembersAudit().insert(
Collections.singleton(audit), txn);
db.accountGroupMembersAudit()
.insert(Collections.singleton(audit));
}
txn.commit();
db.accountGroupMembers().delete(Collections.singleton(m));
accountCache.evict(m.getAccountId());
}
}

View File

@@ -30,6 +30,7 @@ import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.Transaction;
@@ -79,7 +80,7 @@ class AbandonChange extends Handler<ChangeDetail> {
if (!control.canAbandon()) {
throw new NoSuchChangeException(changeId);
}
final Change change = control.getChange();
Change change = control.getChange();
final PatchSet patch = db.patchSets().get(patchSetId);
if (patch == null) {
throw new NoSuchChangeException(changeId);
@@ -89,22 +90,37 @@ class AbandonChange extends Handler<ChangeDetail> {
new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
.messageUUID(db)), currentUser.getAccountId());
final StringBuilder msgBuf =
new StringBuilder("Patch Set " + change.currentPatchSetId().get()
+ ": Abandoned");
new StringBuilder("Patch Set " + patchSetId.get() + ": Abandoned");
if (message != null && message.length() > 0) {
msgBuf.append("\n\n");
msgBuf.append(message);
}
cmsg.setMessage(msgBuf.toString());
Boolean dbSuccess = db.run(new OrmRunnable<Boolean, ReviewDb>() {
public Boolean run(ReviewDb db, Transaction txn, boolean retry)
throws OrmException {
return doAbandonChange(message, change, patchSetId, cmsg, db, txn);
change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()
&& change.currentPatchSetId().equals(patchSetId)) {
change.setStatus(Change.Status.ABANDONED);
ChangeUtil.updated(change);
return change;
} else {
return null;
}
}
});
if (dbSuccess) {
if (change != null) {
db.changeMessages().insert(Collections.singleton(cmsg));
final List<PatchSetApproval> approvals =
db.patchSetApprovals().byChange(changeId).toList();
for (PatchSetApproval a : approvals) {
a.cache(change);
}
db.patchSetApprovals().update(approvals);
// Email the reviewers
final AbandonedSender cm = abandonedSenderFactory.create(change);
cm.setFrom(currentUser.getAccountId());
@@ -115,29 +131,4 @@ class AbandonChange extends Handler<ChangeDetail> {
return changeDetailFactory.create(changeId).call();
}
private Boolean doAbandonChange(final String message, final Change change,
final PatchSet.Id psid, final ChangeMessage cm, final ReviewDb db,
final Transaction txn) throws OrmException {
// Check to make sure the change status and current patchset ID haven't
// changed while the user was typing an abandon message
if (change.getStatus().isOpen() && change.currentPatchSetId().equals(psid)) {
change.setStatus(Change.Status.ABANDONED);
ChangeUtil.updated(change);
final List<PatchSetApproval> approvals =
db.patchSetApprovals().byChange(change.getId()).toList();
for (PatchSetApproval a : approvals) {
a.cache(change);
}
db.patchSetApprovals().update(approvals, txn);
db.changeMessages().insert(Collections.singleton(cm), txn);
db.changes().update(Collections.singleton(change), txn);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}

View File

@@ -32,8 +32,8 @@ import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.workflow.CategoryFunction;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -74,7 +74,8 @@ class SubmitAction extends Handler<ChangeDetail> {
public ChangeDetail call() throws OrmException, NoSuchEntityException,
IllegalStateException, PatchSetInfoNotAvailableException,
NoSuchChangeException {
final Change change = db.changes().get(patchSetId.getParentKey());
final Change.Id changeId = patchSetId.getParentKey();
Change change = db.changes().get(changeId);
if (change == null) {
throw new NoSuchEntityException();
}
@@ -84,7 +85,7 @@ class SubmitAction extends Handler<ChangeDetail> {
+ " not current");
}
if (change.getStatus().isClosed()) {
throw new IllegalStateException("Change" + change.getId() + " is closed");
throw new IllegalStateException("Change" + changeId + " is closed");
}
final List<PatchSetApproval> allApprovals =
@@ -94,10 +95,8 @@ class SubmitAction extends Handler<ChangeDetail> {
final PatchSetApproval.Key ak =
new PatchSetApproval.Key(patchSetId, user.getAccountId(), SUBMIT);
PatchSetApproval myAction = null;
boolean isnew = true;
for (final PatchSetApproval ca : allApprovals) {
if (ak.equals(ca.getKey())) {
isnew = false;
myAction = ca;
myAction.setValue((short) 1);
myAction.setGranted();
@@ -132,27 +131,23 @@ class SubmitAction extends Handler<ChangeDetail> {
+ " not permitted");
}
if (change.getStatus() == Change.Status.NEW) {
change.setStatus(Change.Status.SUBMITTED);
ChangeUtil.updated(change);
}
db.patchSetApprovals().upsert(Collections.singleton(myAction));
final Transaction txn = db.beginTransaction();
db.changes().update(Collections.singleton(change), txn);
if (change.getStatus().isClosed()) {
db.patchSetApprovals().update(fs.getDirtyChangeApprovals(), txn);
}
if (isnew) {
db.patchSetApprovals().insert(Collections.singleton(myAction), txn);
} else {
db.patchSetApprovals().update(Collections.singleton(myAction), txn);
}
txn.commit();
change = db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus() == Change.Status.NEW) {
change.setStatus(Change.Status.SUBMITTED);
ChangeUtil.updated(change);
}
return change;
}
});
if (change.getStatus() == Change.Status.SUBMITTED) {
merger.merge(change.getDest());
}
return changeDetailFactory.create(change.getId()).call();
return changeDetailFactory.create(changeId).call();
}
}

View File

@@ -30,13 +30,11 @@ import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -114,22 +112,18 @@ class AddReviewer extends Handler<AddReviewerResult> {
// Add the reviewers to the database
//
final Set<Account.Id> added = new HashSet<Account.Id>();
db.run(new OrmRunnable<Object, ReviewDb>() {
public Object run(ReviewDb db, Transaction txn, boolean retry)
throws OrmException {
final PatchSet.Id psid = control.getChange().currentPatchSetId();
for (final Account.Id reviewer : reviewerIds) {
if (!exists(psid, reviewer)) {
// This reviewer has not entered an approval for this change yet.
//
final PatchSetApproval myca = dummyApproval(psid, reviewer);
db.patchSetApprovals().insert(Collections.singleton(myca), txn);
added.add(reviewer);
}
}
return null;
final List<PatchSetApproval> toInsert = new ArrayList<PatchSetApproval>();
final PatchSet.Id psid = control.getChange().currentPatchSetId();
for (final Account.Id reviewer : reviewerIds) {
if (!exists(psid, reviewer)) {
// This reviewer has not entered an approval for this change yet.
//
final PatchSetApproval myca = dummyApproval(psid, reviewer);
toInsert.add(myca);
added.add(reviewer);
}
});
}
db.patchSetApprovals().insert(toInsert);
// Email the reviewers
//

View File

@@ -17,7 +17,6 @@ package com.google.gerrit.httpd.rpc.patch;
import com.google.gerrit.common.data.AddReviewerResult;
import com.google.gerrit.common.data.ApprovalSummary;
import com.google.gerrit.common.data.ApprovalSummarySet;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchDetailService;
@@ -25,39 +24,30 @@ import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScriptSettings;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountPatchReview;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Patch.Key;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -66,9 +56,6 @@ import java.util.Set;
class PatchDetailServiceImpl extends BaseServiceImplementation implements
PatchDetailService {
private final Logger log = LoggerFactory.getLogger(getClass());
private final CommentSender.Factory commentSenderFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final ApprovalTypes approvalTypes;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
@@ -76,14 +63,13 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
private final ChangeControl.Factory changeControlFactory;
private final CommentDetailFactory.Factory commentDetailFactory;
private final FunctionState.Factory functionStateFactory;
private final PublishComments.Factory publishCommentsFactory;
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
private final SaveDraft.Factory saveDraftFactory;
@Inject
PatchDetailServiceImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser,
final CommentSender.Factory commentSenderFactory,
final PatchSetInfoFactory patchSetInfoFactory,
final ApprovalTypes approvalTypes,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final AddReviewer.Factory addReviewerFactory,
@@ -91,10 +77,9 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
final CommentDetailFactory.Factory commentDetailFactory,
final FunctionState.Factory functionStateFactory,
final PatchScriptFactory.Factory patchScriptFactoryFactory,
final PublishComments.Factory publishCommentsFactory,
final SaveDraft.Factory saveDraftFactory) {
super(schema, currentUser);
this.patchSetInfoFactory = patchSetInfoFactory;
this.commentSenderFactory = commentSenderFactory;
this.approvalTypes = approvalTypes;
this.accountInfoCacheFactory = accountInfoCacheFactory;
@@ -103,6 +88,7 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
this.commentDetailFactory = commentDetailFactory;
this.functionStateFactory = functionStateFactory;
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
this.publishCommentsFactory = publishCommentsFactory;
this.saveDraftFactory = saveDraftFactory;
}
@@ -150,39 +136,10 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
});
}
public void publishComments(final PatchSet.Id psid, final String message,
final Set<ApprovalCategoryValue.Id> approvals,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException, Failure {
final PublishResult r;
r = db.run(new OrmRunnable<PublishResult, ReviewDb>() {
public PublishResult run(ReviewDb db, Transaction txn, boolean retry)
throws OrmException {
return doPublishComments(psid, message, approvals, db, txn);
}
});
try {
final CommentSender cm;
cm = commentSenderFactory.create(r.change);
cm.setFrom(getAccountId());
cm.setPatchSet(r.patchSet, patchSetInfoFactory.get(psid));
cm.setChangeMessage(r.message);
cm.setPatchLineComments(r.comments);
cm.setReviewDb(db);
cm.send();
} catch (EmailException e) {
log.error("Cannot send comments by email for patch set " + psid, e);
throw new Failure(e);
} catch (PatchSetInfoNotAvailableException e) {
log.error("Failed to obtain PatchSetInfo for patch set " + psid, e);
throw new Failure(e);
}
return VoidResult.INSTANCE;
}
});
public void publishComments(final PatchSet.Id psid, final String msg,
final Set<ApprovalCategoryValue.Id> tags,
final AsyncCallback<VoidResult> cb) {
Handler.wrap(publishCommentsFactory.create(psid, msg, tags)).to(cb);
}
/**
@@ -207,108 +164,6 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
});
}
private static class PublishResult {
Change change;
PatchSet patchSet;
ChangeMessage message;
List<PatchLineComment> comments;
}
private PublishResult doPublishComments(final PatchSet.Id psid,
final String messageText, final Set<ApprovalCategoryValue.Id> approvals,
final ReviewDb db, final Transaction txn) throws OrmException {
final PublishResult r = new PublishResult();
final Account.Id me = getAccountId();
r.change = db.changes().get(psid.getParentKey());
r.patchSet = db.patchSets().get(psid);
if (r.change == null || r.patchSet == null) {
throw new OrmException(new NoSuchEntityException());
}
final boolean iscurrent = psid.equals(r.change.currentPatchSetId());
r.comments = db.patchComments().draft(psid, me).toList();
for (final PatchLineComment c : r.comments) {
c.setStatus(PatchLineComment.Status.PUBLISHED);
c.updated();
}
db.patchComments().update(r.comments, txn);
final StringBuilder msgbuf = new StringBuilder();
final Map<ApprovalCategory.Id, ApprovalCategoryValue.Id> values =
new HashMap<ApprovalCategory.Id, ApprovalCategoryValue.Id>();
for (final ApprovalCategoryValue.Id v : approvals) {
values.put(v.getParentKey(), v);
}
final boolean applyApprovals = iscurrent && r.change.getStatus().isOpen();
final Map<ApprovalCategory.Id, PatchSetApproval> have =
new HashMap<ApprovalCategory.Id, PatchSetApproval>();
for (PatchSetApproval a : db.patchSetApprovals().byPatchSetUser(psid, me)) {
have.put(a.getCategoryId(), a);
}
for (final ApprovalType at : approvalTypes.getApprovalTypes()) {
final ApprovalCategoryValue.Id v = values.get(at.getCategory().getId());
if (v == null) {
continue;
}
final ApprovalCategoryValue val = at.getValue(v.get());
if (val == null) {
continue;
}
PatchSetApproval mycatpp = have.remove(v.getParentKey());
if (mycatpp == null) {
if (msgbuf.length() > 0) {
msgbuf.append("; ");
}
msgbuf.append(val.getName());
if (applyApprovals) {
mycatpp =
new PatchSetApproval(new PatchSetApproval.Key(psid, me, v
.getParentKey()), v.get());
db.patchSetApprovals().insert(Collections.singleton(mycatpp), txn);
}
} else if (mycatpp.getValue() != v.get()) {
if (msgbuf.length() > 0) {
msgbuf.append("; ");
}
msgbuf.append(val.getName());
if (applyApprovals) {
mycatpp.setValue(v.get());
mycatpp.setGranted();
db.patchSetApprovals().update(Collections.singleton(mycatpp), txn);
}
}
}
if (applyApprovals) {
db.patchSetApprovals().delete(have.values(), txn);
}
if (msgbuf.length() > 0) {
msgbuf.insert(0, "Patch Set " + psid.get() + ": ");
msgbuf.append("\n\n");
} else if (!iscurrent) {
msgbuf.append("Patch Set " + psid.get() + ":\n\n");
}
if (messageText != null) {
msgbuf.append(messageText);
}
if (msgbuf.length() > 0) {
r.message =
new ChangeMessage(new ChangeMessage.Key(r.change.getId(), ChangeUtil
.messageUUID(db)), me);
r.message.setMessage(msgbuf.toString());
db.changeMessages().insert(Collections.singleton(r.message), txn);
}
ChangeUtil.updated(r.change);
db.changes().update(Collections.singleton(r.change), txn);
return r;
}
public void addReviewers(final Change.Id id, final List<String> reviewers,
final AsyncCallback<AddReviewerResult> callback) {
addReviewerFactory.create(id, reviewers).to(callback);
@@ -317,8 +172,7 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
public void userApprovals(final Set<Change.Id> cids, final Account.Id aid,
final AsyncCallback<ApprovalSummarySet> callback) {
run(callback, new Action<ApprovalSummarySet>() {
public ApprovalSummarySet run(ReviewDb db)
throws OrmException {
public ApprovalSummarySet run(ReviewDb db) throws OrmException {
final Map<Change.Id, ApprovalSummary> approvals =
new HashMap<Change.Id, ApprovalSummary>();
final AccountInfoCacheFactory aicFactory =
@@ -350,8 +204,9 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
approvals.put(id, new ApprovalSummary(psas.values()));
} catch (NoSuchChangeException nsce) {
/* The user has no access to see this change, so we
* simply do not provide any details about it.
/*
* The user has no access to see this change, so we simply do not
* provide any details about it.
*/
}
}
@@ -363,8 +218,7 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
public void strongestApprovals(final Set<Change.Id> cids,
final AsyncCallback<ApprovalSummarySet> callback) {
run(callback, new Action<ApprovalSummarySet>() {
public ApprovalSummarySet run(ReviewDb db)
throws OrmException {
public ApprovalSummarySet run(ReviewDb db) throws OrmException {
final Map<Change.Id, ApprovalSummary> approvals =
new HashMap<Change.Id, ApprovalSummary>();
final AccountInfoCacheFactory aicFactory =
@@ -380,8 +234,7 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
final FunctionState fs =
functionStateFactory.create(change, ps_id, psas.values());
for (PatchSetApproval ca : db.patchSetApprovals()
.byPatchSet(ps_id)) {
for (PatchSetApproval ca : db.patchSetApprovals().byPatchSet(ps_id)) {
final ApprovalCategory.Id category = ca.getCategoryId();
if (change.getStatus().isOpen()) {
fs.normalize(approvalTypes.getApprovalType(category), ca);
@@ -394,9 +247,9 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
if (psas.containsKey(category)) {
final short oldValue = psas.get(category).getValue();
final short newValue = ca.getValue();
keep = (Math.abs(oldValue) < Math.abs(newValue))
|| ((Math.abs(oldValue) == Math.abs(newValue)
&& (newValue < oldValue)));
keep =
(Math.abs(oldValue) < Math.abs(newValue))
|| ((Math.abs(oldValue) == Math.abs(newValue) && (newValue < oldValue)));
}
if (keep) {
aicFactory.want(ca.getAccountId());
@@ -406,8 +259,9 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
approvals.put(id, new ApprovalSummary(psas.values()));
} catch (NoSuchChangeException nsce) {
/* The user has no access to see this change, so we
* simply do not provide any details about it.
/*
* The user has no access to see this change, so we simply do not
* provide any details about it.
*/
}
}

View File

@@ -436,8 +436,12 @@ public final class Change {
* <p>
* <b>Note: This makes the change dirty. Call update() after.</b>
*/
public PatchSet.Id newPatchSetId() {
return new PatchSet.Id(changeId, ++nbrPatchSets);
public void nextPatchSetId() {
++nbrPatchSets;
}
public PatchSet.Id currPatchSetId() {
return new PatchSet.Id(changeId, nbrPatchSets);
}
public Status getStatus() {

View File

@@ -16,11 +16,14 @@ package com.google.gerrit.server;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmConcurrencyException;
import com.google.gwtorm.client.OrmException;
import org.eclipse.jgit.util.Base64;
import org.eclipse.jgit.util.NB;
import java.util.Collections;
public class ChangeUtil {
private static int uuidPrefix;
private static int uuidSeq;
@@ -49,6 +52,16 @@ public class ChangeUtil {
NB.encodeInt32(raw, 4, uuidSeq--);
}
public static void touch(final Change change, ReviewDb db)
throws OrmException {
try {
updated(change);
db.changes().update(Collections.singleton(change));
} catch (OrmConcurrencyException e) {
// Ignore a concurrent update, we just wanted to tag it as newer.
}
}
public static void updated(final Change c) {
c.resetLastUpdatedOn();
computeSortKey(c);

View File

@@ -27,7 +27,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -131,13 +130,9 @@ public class AccountManager {
}
private void update(final ReviewDb db, final AuthRequest who,
final AccountExternalId extId) throws OrmException, AccountException {
final Transaction txn = db.beginTransaction();
final Account account = db.accounts().get(extId.getAccountId());
boolean updateAccount = false;
if (account == null) {
throw new AccountException("Account has been deleted");
}
final AccountExternalId extId) throws OrmException {
final IdentifiedUser user = userFactory.create(extId.getAccountId());
Account toUpdate = null;
// If the email address was modified by the authentication provider,
// update our records to match the changed email.
@@ -145,40 +140,51 @@ public class AccountManager {
final String newEmail = who.getEmailAddress();
final String oldEmail = extId.getEmailAddress();
if (newEmail != null && !newEmail.equals(oldEmail)) {
if (oldEmail != null && oldEmail.equals(account.getPreferredEmail())) {
updateAccount = true;
account.setPreferredEmail(newEmail);
if (oldEmail != null
&& oldEmail.equals(user.getAccount().getPreferredEmail())) {
toUpdate = load(toUpdate, user.getAccountId(), db);
toUpdate.setPreferredEmail(newEmail);
}
extId.setEmailAddress(newEmail);
db.accountExternalIds().update(Collections.singleton(extId));
}
if (!realm.allowsEdit(Account.FieldName.FULL_NAME)
&& !eq(account.getFullName(), who.getDisplayName())) {
updateAccount = true;
account.setFullName(who.getDisplayName());
}
if (!realm.allowsEdit(Account.FieldName.USER_NAME)
&& !eq(account.getUserName(), who.getUserName())) {
updateAccount = true;
account.setUserName(who.getUserName());
&& !eq(user.getAccount().getFullName(), who.getDisplayName())) {
toUpdate = load(toUpdate, user.getAccountId(), db);
toUpdate.setFullName(who.getDisplayName());
}
db.accountExternalIds().update(Collections.singleton(extId), txn);
if (updateAccount) {
db.accounts().update(Collections.singleton(account), txn);
if (!realm.allowsEdit(Account.FieldName.USER_NAME)
&& !eq(user.getUserName(), who.getUserName())) {
changeUserNameFactory.create(db, user, who.getUserName());
}
if (toUpdate != null) {
db.accounts().update(Collections.singleton(toUpdate));
}
txn.commit();
if (newEmail != null && !newEmail.equals(oldEmail)) {
byEmailCache.evict(oldEmail);
byEmailCache.evict(newEmail);
}
if (updateAccount) {
byIdCache.evict(account.getId());
if (toUpdate != null) {
byIdCache.evict(toUpdate.getId());
}
}
private Account load(Account toUpdate, Account.Id accountId, ReviewDb db)
throws OrmException {
if (toUpdate == null) {
toUpdate = db.accounts().get(accountId);
if (toUpdate == null) {
throw new OrmException("Account " + accountId + " has been deleted");
}
}
return toUpdate;
}
private static boolean eq(final String a, final String b) {
return (a == null && b == null) || (a != null && a.equals(b));
}
@@ -223,10 +229,8 @@ public class AccountManager {
if (openId.size() == 1) {
final AccountExternalId oldId = openId.get(0);
final Transaction txn = db.beginTransaction();
db.accountExternalIds().delete(Collections.singleton(oldId), txn);
db.accountExternalIds().insert(Collections.singleton(newId), txn);
txn.commit();
db.accountExternalIds().upsert(Collections.singleton(newId));
db.accountExternalIds().delete(Collections.singleton(oldId));
} else {
db.accountExternalIds().insert(Collections.singleton(newId));
}
@@ -241,10 +245,8 @@ public class AccountManager {
final AccountExternalId newId = createId(oldId.getAccountId(), who);
newId.setEmailAddress(who.getEmailAddress());
final Transaction txn = db.beginTransaction();
db.accountExternalIds().delete(Collections.singleton(oldId), txn);
db.accountExternalIds().insert(Collections.singleton(newId), txn);
txn.commit();
db.accountExternalIds().upsert(Collections.singleton(newId));
db.accountExternalIds().delete(Collections.singleton(oldId));
return new AuthResult(newId.getAccountId(), newId.getKey(), false);
} else if (v1.size() > 1) {
@@ -260,9 +262,8 @@ public class AccountManager {
account.setFullName(who.getDisplayName());
account.setPreferredEmail(extId.getEmailAddress());
final Transaction txn = db.beginTransaction();
db.accounts().insert(Collections.singleton(account), txn);
db.accountExternalIds().insert(Collections.singleton(extId), txn);
db.accounts().insert(Collections.singleton(account));
db.accountExternalIds().insert(Collections.singleton(extId));
if (firstAccount.get() && firstAccount.compareAndSet(true, false)) {
// This is the first user account on our site. Assume this user
@@ -272,13 +273,11 @@ public class AccountManager {
final AccountGroup.Id admin = authConfig.getAdministratorsGroup();
final AccountGroupMember m =
new AccountGroupMember(new AccountGroupMember.Key(newId, admin));
db.accountGroupMembers().insert(Collections.singleton(m), txn);
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(m, newId)), txn);
Collections.singleton(new AccountGroupMemberAudit(m, newId)));
db.accountGroupMembers().insert(Collections.singleton(m));
}
txn.commit();
if (who.getUserName() != null) {
// Only set if the name hasn't been used yet, but was given to us.
//
@@ -330,19 +329,17 @@ public class AccountManager {
update(db, who, extId);
} else {
final Transaction txn = db.beginTransaction();
extId = createId(to, who);
extId.setEmailAddress(who.getEmailAddress());
db.accountExternalIds().insert(Collections.singleton(extId), txn);
db.accountExternalIds().insert(Collections.singleton(extId));
if (who.getEmailAddress() != null) {
final Account a = db.accounts().get(to);
if (a.getPreferredEmail() == null) {
a.setPreferredEmail(who.getEmailAddress());
db.accounts().update(Collections.singleton(a), txn);
db.accounts().update(Collections.singleton(a));
}
}
txn.commit();
if (who.getEmailAddress() != null) {
byEmailCache.evict(who.getEmailAddress());

View File

@@ -117,11 +117,9 @@ public class ChangeUserName implements Callable<VoidResult> {
// If we have any older user names, remove them.
//
if (!old.isEmpty()) {
db.accountExternalIds().delete(old);
for (AccountExternalId i : old) {
sshKeyCache.evict(i.getSchemeRest());
}
db.accountExternalIds().delete(old);
for (AccountExternalId i : old) {
sshKeyCache.evict(i.getSchemeRest());
}
accountCache.evict(user.getAccountId());

View File

@@ -37,11 +37,10 @@ import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.ldap.LdapModule;
import com.google.gerrit.server.cache.CachePool;
import com.google.gerrit.server.git.ChangeMergeQueue;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.git.PatchSetImporter;
import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.PushReplication;
import com.google.gerrit.server.git.ReloadSubmitQueueOp;
@@ -127,7 +126,6 @@ public class GerritGlobalModule extends FactoryModule {
FromAddressGeneratorProvider.class).in(SINGLETON);
bind(EmailSender.class).to(SmtpEmailSender.class).in(SINGLETON);
factory(PatchSetImporter.Factory.class);
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
factory(FunctionState.Factory.class);

View File

@@ -24,6 +24,7 @@ import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.servlet.RequestScoped;
@@ -47,6 +48,7 @@ public class GerritRequestModule extends FactoryModule {
//
factory(AddReviewerSender.Factory.class);
factory(CreateChangeSender.Factory.class);
factory(PublishComments.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
}
}

View File

@@ -43,15 +43,16 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.workflow.CategoryFunction;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmConcurrencyException;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.SchemaFactory;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -72,6 +73,8 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
@@ -1066,52 +1069,14 @@ public class MergeOp {
}
private void setMerged(Change c, ChangeMessage msg) {
final Change.Id changeId = c.getId();
final PatchSet.Id merged = c.currentPatchSetId();
PatchSetApproval submitter = null;
for (int attempts = 0; attempts < 10; attempts++) {
c.setStatus(Change.Status.MERGED);
ChangeUtil.updated(c);
try {
final Transaction txn = schema.beginTransaction();
// Flatten out all existing approvals based upon the current
// permissions. Once the change is closed the approvals are
// not updated at presentation view time, so we need to make.
// sure they are accurate now. This way if permissions get
// modified in the future, historical records stay accurate.
//
final List<PatchSetApproval> approvals =
schema.patchSetApprovals().byChange(c.getId()).toList();
final FunctionState fs = functionState.create(c, merged, approvals);
for (ApprovalType at : approvalTypes.getApprovalTypes()) {
CategoryFunction.forCategory(at.getCategory()).run(at, fs);
}
for (PatchSetApproval a : approvals) {
if (a.getValue() > 0
&& ApprovalCategory.SUBMIT.equals(a.getCategoryId())
&& a.getPatchSetId().equals(merged)) {
if (submitter == null
|| a.getGranted().compareTo(submitter.getGranted()) > 0) {
submitter = a;
}
}
a.cache(c);
}
schema.patchSetApprovals().update(approvals, txn);
if (msg != null) {
if (submitter != null && msg.getAuthor() == null) {
msg.setAuthor(submitter.getAccountId());
}
schema.changeMessages().insert(Collections.singleton(msg), txn);
}
schema.changes().update(Collections.singleton(c), txn);
txn.commit();
break;
} catch (OrmException e) {
final Change.Id id = c.getId();
try {
c = schema.changes().get(id);
try {
schema.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
@Override
public Change update(Change c) {
c.setStatus(Change.Status.MERGED);
if (!merged.equals(c.currentPatchSetId())) {
// Uncool; the patch set changed after we merged it.
// Go back to the patch set that was actually merged.
@@ -1122,9 +1087,54 @@ public class MergeOp {
log.error("Cannot read merged patch set " + merged, e1);
}
}
} catch (OrmException e2) {
log.error("Cannot set change " + id + " to merged " + merged, e2);
ChangeUtil.updated(c);
return c;
}
});
} catch (OrmConcurrencyException err) {
} catch (OrmException err) {
log.warn("Cannot update change status", err);
}
// Flatten out all existing approvals based upon the current
// permissions. Once the change is closed the approvals are
// not updated at presentation view time, so we need to make.
// sure they are accurate now. This way if permissions get
// modified in the future, historical records stay accurate.
//
PatchSetApproval submitter = null;
try {
c.setStatus(Change.Status.MERGED);
final List<PatchSetApproval> approvals =
schema.patchSetApprovals().byChange(changeId).toList();
final FunctionState fs = functionState.create(c, merged, approvals);
for (ApprovalType at : approvalTypes.getApprovalTypes()) {
CategoryFunction.forCategory(at.getCategory()).run(at, fs);
}
for (PatchSetApproval a : approvals) {
if (a.getValue() > 0
&& ApprovalCategory.SUBMIT.equals(a.getCategoryId())
&& a.getPatchSetId().equals(merged)) {
if (submitter == null
|| a.getGranted().compareTo(submitter.getGranted()) > 0) {
submitter = a;
}
}
a.cache(c);
}
schema.patchSetApprovals().update(approvals);
} catch (OrmException err) {
log.warn("Cannot normalize approvals for change " + changeId, err);
}
if (msg != null) {
if (submitter != null && msg.getAuthor() == null) {
msg.setAuthor(submitter.getAccountId());
}
try {
schema.changeMessages().insert(Collections.singleton(msg));
} catch (OrmException err) {
log.warn("Cannot store message on change", err);
}
}
@@ -1147,31 +1157,34 @@ public class MergeOp {
sendMergeFail(c, msg, true);
}
private void sendMergeFail(Change c, ChangeMessage msg, boolean makeNew) {
for (int attempts = 0; attempts < 10; attempts++) {
if (makeNew) {
c.setStatus(Change.Status.NEW);
}
ChangeUtil.updated(c);
private void sendMergeFail(Change c, ChangeMessage msg, final boolean makeNew) {
try {
schema.changeMessages().insert(Collections.singleton(msg));
} catch (OrmException err) {
log.warn("Cannot record merge failure message", err);
}
if (makeNew) {
try {
final Transaction txn = schema.beginTransaction();
schema.changes().update(Collections.singleton(c), txn);
if (msg != null) {
schema.changeMessages().insert(Collections.singleton(msg), txn);
}
txn.commit();
break;
} catch (OrmException e) {
try {
c = schema.changes().get(c.getId());
if (c.getStatus().isClosed()) {
// Someone else marked it close while we noticed a failure.
// That's fine, leave it closed.
//
break;
schema.changes().atomicUpdate(c.getId(), new AtomicUpdate<Change>() {
@Override
public Change update(Change c) {
if (c.getStatus().isOpen()) {
c.setStatus(Change.Status.NEW);
ChangeUtil.updated(c);
}
return c;
}
} catch (OrmException e2) {
}
});
} catch (OrmConcurrencyException err) {
} catch (OrmException err) {
log.warn("Cannot update change status", err);
}
} else {
try {
ChangeUtil.touch(c, schema);
} catch (OrmException err) {
log.warn("Cannot update change timestamp", err);
}
}

View File

@@ -1,127 +0,0 @@
// Copyright (C) 2008 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.git;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetAncestor;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.Commit;
import org.eclipse.jgit.revwalk.RevCommit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Imports a {@link PatchSet} from a {@link Commit}. */
public class PatchSetImporter {
public interface Factory {
PatchSetImporter create(ReviewDb dstDb, RevCommit srcCommit,
PatchSet dstPatchSet, boolean isNewPatchSet);
}
private final PatchSetInfoFactory patchSetInfoFactory;
private final ReviewDb db;
private final RevCommit src;
private final PatchSet dst;
private final boolean isNew;
private Transaction txn;
private PatchSetInfo info;
private final Map<Integer, PatchSetAncestor> ancestorExisting =
new HashMap<Integer, PatchSetAncestor>();
private final List<PatchSetAncestor> ancestorInsert =
new ArrayList<PatchSetAncestor>();
private final List<PatchSetAncestor> ancestorUpdate =
new ArrayList<PatchSetAncestor>();
@Inject
PatchSetImporter(final PatchSetInfoFactory psif,
@Assisted final ReviewDb dstDb, @Assisted final RevCommit srcCommit,
@Assisted final PatchSet dstPatchSet,
@Assisted final boolean isNewPatchSet) {
patchSetInfoFactory = psif;
db = dstDb;
src = srcCommit;
dst = dstPatchSet;
isNew = isNewPatchSet;
}
public void setTransaction(final Transaction t) {
txn = t;
}
public PatchSetInfo getPatchSetInfo() {
return info;
}
public void run() throws OrmException {
dst.setRevision(toRevId(src));
if (!isNew) {
for (final PatchSetAncestor a : db.patchSetAncestors().ancestorsOf(
dst.getId())) {
ancestorExisting.put(a.getPosition(), a);
}
}
info = patchSetInfoFactory.get(src, dst.getId());
importAncestors();
final boolean auto = txn == null;
if (auto) {
txn = db.beginTransaction();
}
if (isNew) {
db.patchSets().insert(Collections.singleton(dst), txn);
}
db.patchSetAncestors().insert(ancestorInsert, txn);
if (!isNew) {
db.patchSetAncestors().update(ancestorUpdate, txn);
db.patchSetAncestors().delete(ancestorExisting.values(), txn);
}
if (auto) {
txn.commit();
txn = null;
}
}
private void importAncestors() {
for (int p = 0; p < src.getParentCount(); p++) {
PatchSetAncestor a = ancestorExisting.remove(p + 1);
if (a == null) {
a = new PatchSetAncestor(new PatchSetAncestor.Id(dst.getId(), p + 1));
ancestorInsert.add(a);
} else {
ancestorUpdate.add(a);
}
a.setAncestorRevision(toRevId(src.getParent(p)));
}
}
private static RevId toRevId(final RevCommit src) {
return new RevId(src.getId().name());
}
}

View File

@@ -0,0 +1,281 @@
// Copyright (C) 2009 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.patch;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
public class PublishComments implements Callable<VoidResult> {
private static final Logger log =
LoggerFactory.getLogger(PublishComments.class);
public interface Factory {
PublishComments create(PatchSet.Id patchSetId, String messageText,
Set<ApprovalCategoryValue.Id> approvals);
}
private final ReviewDb db;
private final IdentifiedUser user;
private final ApprovalTypes types;
private final CommentSender.Factory commentSenderFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final ChangeControl.Factory changeControlFactory;
private final FunctionState.Factory functionStateFactory;
private final PatchSet.Id patchSetId;
private final String messageText;
private final Set<ApprovalCategoryValue.Id> approvals;
private Change change;
private PatchSet patchSet;
private ChangeMessage message;
private List<PatchLineComment> drafts;
@Inject
PublishComments(final ReviewDb db, final IdentifiedUser user,
final ApprovalTypes approvalTypes,
final CommentSender.Factory commentSenderFactory,
final PatchSetInfoFactory patchSetInfoFactory,
final ChangeControl.Factory changeControlFactory,
final FunctionState.Factory functionStateFactory,
@Assisted final PatchSet.Id patchSetId,
@Assisted final String messageText,
@Assisted final Set<ApprovalCategoryValue.Id> approvals) {
this.db = db;
this.user = user;
this.types = approvalTypes;
this.patchSetInfoFactory = patchSetInfoFactory;
this.commentSenderFactory = commentSenderFactory;
this.changeControlFactory = changeControlFactory;
this.functionStateFactory = functionStateFactory;
this.patchSetId = patchSetId;
this.messageText = messageText;
this.approvals = approvals;
}
@Override
public VoidResult call() throws NoSuchChangeException, OrmException {
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl ctl = changeControlFactory.validateFor(changeId);
change = ctl.getChange();
patchSet = db.patchSets().get(patchSetId);
if (patchSet == null) {
throw new NoSuchChangeException(changeId);
}
drafts = drafts();
publishDrafts();
final boolean isCurrent = patchSetId.equals(change.currentPatchSetId());
if (isCurrent && change.getStatus().isOpen()) {
publishApprovals();
} else {
publishMessageOnly();
}
touchChange();
email();
return VoidResult.INSTANCE;
}
private void publishDrafts() throws OrmException {
for (final PatchLineComment c : drafts) {
c.setStatus(PatchLineComment.Status.PUBLISHED);
c.updated();
}
db.patchComments().update(drafts);
}
private void publishApprovals() throws OrmException {
ChangeUtil.updated(change);
final Set<ApprovalCategory.Id> dirty = new HashSet<ApprovalCategory.Id>();
final List<PatchSetApproval> ins = new ArrayList<PatchSetApproval>();
final List<PatchSetApproval> upd = new ArrayList<PatchSetApproval>();
final Collection<PatchSetApproval> all =
db.patchSetApprovals().byPatchSet(patchSetId).toList();
final Map<ApprovalCategory.Id, PatchSetApproval> mine = mine(all);
// Ensure any new approvals are stored properly.
//
for (final ApprovalCategoryValue.Id want : approvals) {
PatchSetApproval a = mine.get(want.getParentKey());
if (a == null) {
a = new PatchSetApproval(new PatchSetApproval.Key(//
patchSetId, user.getAccountId(), want.getParentKey()), want.get());
a.cache(change);
ins.add(a);
all.add(a);
mine.put(a.getCategoryId(), a);
dirty.add(a.getCategoryId());
}
}
// Normalize all of the items the user is changing.
//
final FunctionState functionState =
functionStateFactory.create(change, patchSetId, all);
for (final ApprovalCategoryValue.Id want : approvals) {
final PatchSetApproval a = mine.get(want.getParentKey());
final short o = a.getValue();
a.setValue(want.get());
a.cache(change);
functionState.normalize(types.getApprovalType(a.getCategoryId()), a);
if (o != a.getValue()) {
// Value changed, ensure we update the database.
//
a.setGranted();
dirty.add(a.getCategoryId());
}
if (!ins.contains(a)) {
upd.add(a);
}
}
// Format a message explaining the actions taken.
//
final StringBuilder msgbuf = new StringBuilder();
for (final ApprovalType at : types.getApprovalTypes()) {
if (dirty.contains(at.getCategory().getId())) {
final PatchSetApproval a = mine.get(at.getCategory().getId());
final ApprovalCategoryValue val = at.getValue(a);
if (msgbuf.length() > 0) {
msgbuf.append("; ");
}
if (val != null && val.getName() != null && !val.getName().isEmpty()) {
msgbuf.append(val.getName());
} else {
msgbuf.append(at.getCategory().getName());
msgbuf.append(" ");
if (a.getValue() > 0) msgbuf.append('+');
msgbuf.append(a.getValue());
}
}
}
// Update dashboards for everyone else.
//
for (PatchSetApproval a : all) {
if (!user.getAccountId().equals(a.getAccountId())) {
a.cache(change);
upd.add(a);
}
}
db.patchSetApprovals().update(upd);
db.patchSetApprovals().insert(ins);
message(msgbuf.toString());
}
private void publishMessageOnly() throws OrmException {
message(null);
}
private void message(String actions) throws OrmException {
if ((actions == null || actions.isEmpty())
&& (messageText == null || messageText.isEmpty())) {
// They had nothing to say?
//
return;
}
final StringBuilder msgbuf = new StringBuilder();
msgbuf.append("Patch Set " + patchSetId.get() + ":");
if (actions != null && !actions.isEmpty()) {
msgbuf.append(" ");
msgbuf.append(actions);
}
msgbuf.append("\n\n");
msgbuf.append(messageText != null ? messageText : "");
message = new ChangeMessage(new ChangeMessage.Key(change.getId(),//
ChangeUtil.messageUUID(db)), user.getAccountId());
message.setMessage(msgbuf.toString());
db.changeMessages().insert(Collections.singleton(message));
}
private Map<ApprovalCategory.Id, PatchSetApproval> mine(
Collection<PatchSetApproval> all) {
Map<ApprovalCategory.Id, PatchSetApproval> r =
new HashMap<ApprovalCategory.Id, PatchSetApproval>();
for (PatchSetApproval a : all) {
if (user.getAccountId().equals(a.getAccountId())) {
r.put(a.getCategoryId(), a);
}
}
return r;
}
private void touchChange() {
try {
ChangeUtil.touch(change, db);
} catch (OrmException e) {
}
}
private List<PatchLineComment> drafts() throws OrmException {
return db.patchComments().draft(patchSetId, user.getAccountId()).toList();
}
private void email() {
try {
final CommentSender cm = commentSenderFactory.create(change);
cm.setFrom(user.getAccountId());
cm.setPatchSet(patchSet, patchSetInfoFactory.get(patchSetId));
cm.setChangeMessage(message);
cm.setPatchLineComments(drafts);
cm.setReviewDb(db);
cm.send();
} catch (EmailException e) {
log.error("Cannot send comments by email for patch set " + patchSetId, e);
} catch (PatchSetInfoNotAvailableException e) {
log.error("Failed to obtain PatchSetInfo for patch set " + patchSetId, e);
}
}
}

View File

@@ -28,7 +28,6 @@ import com.google.gerrit.server.workflow.NoOpFunction;
import com.google.gerrit.server.workflow.SubmitFunction;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.schema.sql.DialectH2;
@@ -158,7 +157,6 @@ public class SchemaCreator {
}
private void initVerifiedCategory(final ReviewDb c) throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@@ -169,14 +167,12 @@ public class SchemaCreator {
vals.add(value(cat, 1, "Verified"));
vals.add(value(cat, 0, "No score"));
vals.add(value(cat, -1, "Fails"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initCodeReviewCategory(final ReviewDb c,
final SystemConfig sConfig) throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@@ -190,9 +186,8 @@ public class SchemaCreator {
vals.add(value(cat, 0, "No score"));
vals.add(value(cat, -1, "I would prefer that you didn't submit this"));
vals.add(value(cat, -2, "Do not submit"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
final ProjectRight approve =
new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(),
@@ -203,7 +198,6 @@ public class SchemaCreator {
}
private void initOwnerCategory(final ReviewDb c) throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@@ -212,14 +206,12 @@ public class SchemaCreator {
cat.setFunctionName(NoOpFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, 1, "Administer All Settings"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initReadCategory(final ReviewDb c, final SystemConfig sConfig)
throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@@ -230,9 +222,9 @@ public class SchemaCreator {
vals.add(value(cat, 2, "Upload permission"));
vals.add(value(cat, 1, "Read access"));
vals.add(value(cat, -1, "No access"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
{
final ProjectRight read =
new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(),
@@ -260,7 +252,6 @@ public class SchemaCreator {
}
private void initSubmitCategory(final ReviewDb c) throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@@ -269,13 +260,11 @@ public class SchemaCreator {
cat.setFunctionName(SubmitFunction.NAME);
vals = new ArrayList<ApprovalCategoryValue>();
vals.add(value(cat, 1, "Submit"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initPushTagCategory(final ReviewDb c) throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@@ -287,14 +276,12 @@ public class SchemaCreator {
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED,
"Create Annotated Tag"));
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANY, "Create Any Tag"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private void initPushUpdateBranchCategory(final ReviewDb c)
throws OrmException {
final Transaction txn = c.beginTransaction();
final ApprovalCategory cat;
final ArrayList<ApprovalCategoryValue> vals;
@@ -306,9 +293,8 @@ public class SchemaCreator {
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch"));
vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE,
"Force Push Branch; Delete Branch"));
c.approvalCategories().insert(Collections.singleton(cat), txn);
c.approvalCategoryValues().insert(vals, txn);
txn.commit();
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
}
private static ApprovalCategoryValue value(final ApprovalCategory cat,

View File

@@ -21,7 +21,6 @@ import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.AccountExternalId.Key;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -60,11 +59,7 @@ class Schema_22 extends SchemaVersion {
} finally {
queryStmt.close();
}
if (!ids.isEmpty()) {
Transaction t = db.beginTransaction();
db.accountExternalIds().insert(ids, t);
t.commit();
}
db.accountExternalIds().insert(ids);
}
private Key toKey(final String userName) {

View File

@@ -18,7 +18,6 @@ import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -53,11 +52,7 @@ class Schema_23 extends SchemaVersion {
} finally {
queryStmt.close();
}
if (!names.isEmpty()) {
Transaction t = db.beginTransaction();
db.accountGroupNames().insert(names, t);
t.commit();
}
db.accountGroupNames().insert(names);
}
private AccountGroup.NameKey toKey(final String name) {

View File

@@ -26,7 +26,6 @@ import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import org.apache.sshd.server.Environment;
@@ -89,16 +88,12 @@ final class AdminCreateProject extends BaseCommand {
try {
validateParameters();
Transaction txn = db.beginTransaction();
createProject(txn);
Repository repo = repoManager.createRepository(projectName);
repo.create(true);
repo.writeSymref(Constants.HEAD, branch);
repoManager.setProjectDescription(projectName, projectDescription);
txn.commit();
createProject();
rq.replicateNewProject(new Project.NameKey(projectName), branch);
} catch (Exception e) {
@@ -111,23 +106,23 @@ final class AdminCreateProject extends BaseCommand {
});
}
private void createProject(Transaction txn) throws OrmException {
private void createProject() throws OrmException {
final Project.NameKey newProjectNameKey = new Project.NameKey(projectName);
final Project newProject = new Project(newProjectNameKey);
newProject.setDescription(projectDescription);
newProject.setSubmitType(submitType);
newProject.setUseContributorAgreements(contributorAgreements);
newProject.setUseSignedOffBy(signedOffBy);
db.projects().insert(Collections.singleton(newProject), txn);
final ProjectRight.Key prk =
new ProjectRight.Key(newProjectNameKey, ApprovalCategory.OWN, ownerId);
final ProjectRight pr = new ProjectRight(prk);
pr.setMaxValue((short) 1);
pr.setMinValue((short) 1);
db.projectRights().insert(Collections.singleton(pr), txn);
db.projectRights().insert(Collections.singleton(pr));
final Project newProject = new Project(newProjectNameKey);
newProject.setDescription(projectDescription);
newProject.setSubmitType(submitType);
newProject.setUseContributorAgreements(contributorAgreements);
newProject.setUseSignedOffBy(signedOffBy);
db.projects().insert(Collections.singleton(newProject));
}
private void validateParameters() throws Failure {

View File

@@ -38,7 +38,6 @@ import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import org.apache.sshd.server.Environment;
@@ -150,7 +149,6 @@ public class ApproveCommand extends BaseCommand {
throw error("change " + changeId + " is closed");
}
final Transaction txn = db.beginTransaction();
final StringBuffer msgBuf = new StringBuffer();
msgBuf.append("Patch Set ");
msgBuf.append(patchSetId.get());
@@ -166,11 +164,11 @@ public class ApproveCommand extends BaseCommand {
Short score = co.value();
if (score != null) {
addApproval(psaKey, score, change, co, txn);
addApproval(psaKey, score, change, co);
} else {
if (psa == null) {
score = 0;
addApproval(psaKey, score, change, co, txn);
addApproval(psaKey, score, change, co);
} else {
score = psa.getValue();
}
@@ -194,10 +192,9 @@ public class ApproveCommand extends BaseCommand {
new ChangeMessage(new ChangeMessage.Key(changeId, uuid), currentUser
.getAccountId());
cm.setMessage(msgBuf.toString());
db.changeMessages().insert(Collections.singleton(cm), txn);
ChangeUtil.updated(change);
db.changes().update(Collections.singleton(change), txn);
txn.commit();
db.changeMessages().insert(Collections.singleton(cm));
ChangeUtil.touch(change, db);
sendMail(change, change.currentPatchSetId(), cm);
}
@@ -279,16 +276,9 @@ public class ApproveCommand extends BaseCommand {
}
private void addApproval(final PatchSetApproval.Key psaKey,
final Short score, final Change c, final ApproveOption co,
final Transaction txn) throws OrmException, UnloggedFailure {
PatchSetApproval psa = db.patchSetApprovals().get(psaKey);
boolean insert = false;
if (psa == null) {
insert = true;
psa = new PatchSetApproval(psaKey, score);
}
final Short score, final Change c, final ApproveOption co)
throws OrmException, UnloggedFailure {
final PatchSetApproval psa = new PatchSetApproval(psaKey, score);
final List<PatchSetApproval> approvals = Collections.emptyList();
final FunctionState fs =
functionStateFactory.create(c, psaKey.getParentKey(), approvals);
@@ -299,12 +289,7 @@ public class ApproveCommand extends BaseCommand {
}
psa.setGranted();
if (insert) {
db.patchSetApprovals().insert(Collections.singleton(psa), txn);
} else {
db.patchSetApprovals().update(Collections.singleton(psa), txn);
}
db.patchSetApprovals().upsert(Collections.singleton(psa));
}
private void initOptionList() {

View File

@@ -36,8 +36,10 @@ import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetAncestor;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
@@ -45,16 +47,14 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.git.PatchSetImporter;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.mail.MergedSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -150,9 +150,6 @@ final class Receive extends AbstractGitCommand {
@Inject
private ReplicationQueue replication;
@Inject
private PatchSetImporter.Factory importFactory;
@Inject
private PatchSetInfoFactory patchSetInfoFactory;
@@ -186,7 +183,8 @@ final class Receive extends AbstractGitCommand {
protected void runImpl() throws IOException, Failure {
if (!canUpload()) {
final String reqName = project.getName();
throw new Failure(1, "fatal: Upload denied for project '" + reqName + "'",
throw new Failure(1,
"fatal: Upload denied for project '" + reqName + "'",
new SecurityException("Account lacks Upload permission"));
}
@@ -809,20 +807,21 @@ final class Receive extends AbstractGitCommand {
cc.remove(me);
cc.removeAll(reviewers);
final Transaction txn = db.beginTransaction();
final Change change =
new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
final PatchSet ps = new PatchSet(change.newPatchSetId());
change.nextPatchSetId();
final PatchSet ps = new PatchSet(change.currPatchSetId());
ps.setCreatedOn(change.getCreatedOn());
ps.setUploader(me);
ps.setRevision(toRevId(c));
insertAncestors(ps.getId(), c);
db.patchSets().insert(Collections.singleton(ps));
final PatchSetImporter imp = importFactory.create(db, c, ps, true);
imp.setTransaction(txn);
imp.run();
change.setCurrentPatchSet(imp.getPatchSetInfo());
final PatchSetInfo info = patchSetInfoFactory.get(c, ps.getId());
change.setCurrentPatchSet(info);
ChangeUtil.updated(change);
db.changes().insert(Collections.singleton(change), txn);
db.changes().insert(Collections.singleton(change));
final Set<Account.Id> haveApprovals = new HashSet<Account.Id>();
final List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
@@ -830,28 +829,24 @@ final class Receive extends AbstractGitCommand {
if (allTypes.size() > 0) {
final Account.Id authorId =
imp.getPatchSetInfo().getAuthor() != null ? imp.getPatchSetInfo()
.getAuthor().getAccount() : null;
info.getAuthor() != null ? info.getAuthor().getAccount() : null;
final Account.Id committerId =
imp.getPatchSetInfo().getCommitter() != null ? imp.getPatchSetInfo()
.getCommitter().getAccount() : null;
info.getCommitter() != null ? info.getCommitter().getAccount() : null;
final ApprovalCategory.Id catId =
allTypes.get(allTypes.size() - 1).getCategory().getId();
if (authorId != null && haveApprovals.add(authorId)) {
insertDummyApproval(change, ps.getId(), authorId, catId, db, txn);
insertDummyApproval(change, ps.getId(), authorId, catId, db);
}
if (committerId != null && haveApprovals.add(committerId)) {
insertDummyApproval(change, ps.getId(), committerId, catId, db, txn);
insertDummyApproval(change, ps.getId(), committerId, catId, db);
}
for (final Account.Id reviewer : reviewers) {
if (haveApprovals.add(reviewer)) {
insertDummyApproval(change, ps.getId(), reviewer, catId, db, txn);
insertDummyApproval(change, ps.getId(), reviewer, catId, db);
}
}
}
txn.commit();
final RefUpdate ru = repo.updateRef(ps.getRefName());
ru.setNewObjectId(c);
ru.disableRefLog();
@@ -867,7 +862,7 @@ final class Receive extends AbstractGitCommand {
final CreateChangeSender cm;
cm = createChangeSenderFactory.create(change);
cm.setFrom(me);
cm.setPatchSet(ps, imp.getPatchSetInfo());
cm.setPatchSet(ps, info);
cm.setReviewDb(db);
cm.addReviewers(reviewers);
cm.addExtraCC(cc);
@@ -932,221 +927,238 @@ final class Receive extends AbstractGitCommand {
cc.remove(me);
cc.removeAll(reviewers);
final ReplaceResult result;
final ReplaceResult result = new ReplaceResult();
final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
final Set<Account.Id> oldCC = new HashSet<Account.Id>();
result = db.run(new OrmRunnable<ReplaceResult, ReviewDb>() {
public ReplaceResult run(final ReviewDb db, final Transaction txn,
final boolean isRetry) throws OrmException {
final Change change = db.changes().get(request.ontoChange);
if (change == null) {
reject(request.cmd, "change " + request.ontoChange + " not found");
return null;
}
if (change.getStatus().isClosed()) {
reject(request.cmd, "change " + request.ontoChange + " closed");
return null;
}
Change change = db.changes().get(request.ontoChange);
if (change == null) {
reject(request.cmd, "change " + request.ontoChange + " not found");
return null;
}
if (change.getStatus().isClosed()) {
reject(request.cmd, "change " + request.ontoChange + " closed");
return null;
}
final PatchSet.Id priorPatchSet = change.currentPatchSetId();
for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
if (ps.getRevision() == null) {
reject(request.cmd, "change state corrupt");
return null;
}
final String revIdStr = ps.getRevision().get();
final ObjectId commitId;
try {
commitId = ObjectId.fromString(revIdStr);
} catch (IllegalArgumentException e) {
log.warn("Invalid revision in " + ps.getId() + ": " + revIdStr);
reject(request.cmd, "change state corrupt");
return null;
}
try {
final RevCommit prior = rp.getRevWalk().parseCommit(commitId);
// Don't allow a change to directly depend upon itself. This is a
// very common error due to users making a new commit rather than
// amending when trying to address review comments.
//
if (rp.getRevWalk().isMergedInto(prior, c)) {
reject(request.cmd, "squash commits first");
return null;
}
// Don't allow the same commit to appear twice on the same change
//
if (c == prior) {
reject(request.cmd, "commit already exists");
return null;
}
// Don't allow the same tree if the commit message is unmodified
// or no parents were updated (rebase), else warn that only part
// of the commit was modified.
//
if (priorPatchSet.equals(ps.getId())
&& c.getTree() == prior.getTree()) {
rp.getRevWalk().parseBody(prior);
final boolean messageEq =
c.getFullMessage().equals(prior.getFullMessage());
final boolean parentsEq = parentsEqual(c, prior);
if (messageEq && parentsEq) {
reject(request.cmd, "no changes made");
return null;
} else {
err.write(Constants.encode("warning: " //
+ change.getKey().abbreviate() + ": " //
+ " no files changed, but" //
+ (!messageEq ? " message updated" : "") //
+ (!messageEq && !parentsEq ? " and" : "") //
+ (!parentsEq ? " was rebased" : "") //
+ "\n" //
));
}
}
} catch (IOException e) {
log.error("Change " + change.getId() + " missing " + revIdStr, e);
reject(request.cmd, "change state corrupt");
return null;
}
}
final PatchSet ps = new PatchSet(change.newPatchSetId());
ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
ps.setUploader(currentUser.getAccountId());
final PatchSetImporter imp = importFactory.create(db, c, ps, true);
imp.setTransaction(txn);
imp.run();
final Ref mergedInto = findMergedInto(change.getDest().get(), c);
final ReplaceResult result = new ReplaceResult();
result.mergedIntoRef = mergedInto != null ? mergedInto.getName() : null;
result.change = change;
result.patchSet = ps;
result.info = imp.getPatchSetInfo();
final Account.Id authorId =
imp.getPatchSetInfo().getAuthor() != null ? imp.getPatchSetInfo()
.getAuthor().getAccount() : null;
final Account.Id committerId =
imp.getPatchSetInfo().getCommitter() != null ? imp
.getPatchSetInfo().getCommitter().getAccount() : null;
boolean haveAuthor = false;
boolean haveCommitter = false;
final Set<Account.Id> haveApprovals = new HashSet<Account.Id>();
oldReviewers.clear();
oldCC.clear();
for (PatchSetApproval a : db.patchSetApprovals().byChange(
change.getId())) {
haveApprovals.add(a.getAccountId());
if (a.getValue() != 0) {
oldReviewers.add(a.getAccountId());
} else {
oldCC.add(a.getAccountId());
}
final ApprovalType type =
approvalTypes.getApprovalType(a.getCategoryId());
if (a.getPatchSetId().equals(priorPatchSet)
&& type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) {
// If there was a negative vote on the prior patch set, carry it
// into this patch set.
//
db.patchSetApprovals()
.insert(
Collections.singleton(new PatchSetApproval(ps.getId(), a)),
txn);
}
if (!haveAuthor && authorId != null
&& a.getAccountId().equals(authorId)) {
haveAuthor = true;
}
if (!haveCommitter && committerId != null
&& a.getAccountId().equals(committerId)) {
haveCommitter = true;
}
}
final ChangeMessage msg =
new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
.messageUUID(db)), me, ps.getCreatedOn());
msg.setMessage("Uploaded patch set " + ps.getPatchSetId() + ".");
db.changeMessages().insert(Collections.singleton(msg), txn);
result.msg = msg;
if (result.mergedIntoRef != null) {
// Change was already submitted to a branch, close it.
//
markChangeMergedByPush(db, txn, result);
} else {
// Change should be new, so it can go through review again.
//
change.setStatus(Change.Status.NEW);
change.setCurrentPatchSet(imp.getPatchSetInfo());
ChangeUtil.updated(change);
db.changes().update(Collections.singleton(change), txn);
}
final List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
if (allTypes.size() > 0) {
final ApprovalCategory.Id catId =
allTypes.get(allTypes.size() - 1).getCategory().getId();
if (authorId != null && haveApprovals.add(authorId)) {
insertDummyApproval(result, authorId, catId, db, txn);
}
if (committerId != null && haveApprovals.add(committerId)) {
insertDummyApproval(result, committerId, catId, db, txn);
}
for (final Account.Id reviewer : reviewers) {
if (haveApprovals.add(reviewer)) {
insertDummyApproval(result, reviewer, catId, db, txn);
}
}
}
return result;
final PatchSet.Id priorPatchSet = change.currentPatchSetId();
for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
if (ps.getRevision() == null) {
reject(request.cmd, "change state corrupt");
return null;
}
});
if (result != null) {
final PatchSet ps = result.patchSet;
final RefUpdate ru = repo.updateRef(ps.getRefName());
ru.setNewObjectId(c);
ru.disableRefLog();
if (ru.update(rp.getRevWalk()) != RefUpdate.Result.NEW) {
throw new IOException("Failed to create ref " + ps.getRefName()
+ " in " + repo.getDirectory() + ": " + ru.getResult());
final String revIdStr = ps.getRevision().get();
final ObjectId commitId;
try {
commitId = ObjectId.fromString(revIdStr);
} catch (IllegalArgumentException e) {
log.warn("Invalid revision in " + ps.getId() + ": " + revIdStr);
reject(request.cmd, "change state corrupt");
return null;
}
replication.scheduleUpdate(project.getNameKey(), ru.getName());
request.cmd.setResult(ReceiveCommand.Result.OK);
try {
final ReplacePatchSetSender cm;
cm = replacePatchSetFactory.create(result.change);
cm.setFrom(me);
cm.setPatchSet(ps, result.info);
cm.setChangeMessage(result.msg);
cm.setReviewDb(db);
cm.addReviewers(reviewers);
cm.addExtraCC(cc);
cm.addReviewers(oldReviewers);
cm.addExtraCC(oldCC);
cm.send();
} catch (EmailException e) {
log.error("Cannot send email for new patch set " + ps.getId(), e);
final RevCommit prior = rp.getRevWalk().parseCommit(commitId);
// Don't allow a change to directly depend upon itself. This is a
// very common error due to users making a new commit rather than
// amending when trying to address review comments.
//
if (rp.getRevWalk().isMergedInto(prior, c)) {
reject(request.cmd, "squash commits first");
return null;
}
// Don't allow the same commit to appear twice on the same change
//
if (c == prior) {
reject(request.cmd, "commit already exists");
return null;
}
// Don't allow the same tree if the commit message is unmodified
// or no parents were updated (rebase), else warn that only part
// of the commit was modified.
//
if (priorPatchSet.equals(ps.getId()) && c.getTree() == prior.getTree()) {
rp.getRevWalk().parseBody(prior);
final boolean messageEq =
c.getFullMessage().equals(prior.getFullMessage());
final boolean parentsEq = parentsEqual(c, prior);
if (messageEq && parentsEq) {
reject(request.cmd, "no changes made");
return null;
} else {
err.write(Constants.encode("warning: " //
+ change.getKey().abbreviate() + ": " //
+ " no files changed, but" //
+ (!messageEq ? " message updated" : "") //
+ (!messageEq && !parentsEq ? " and" : "") //
+ (!parentsEq ? " was rebased" : "") //
+ "\n" //
));
}
}
} catch (IOException e) {
log.error("Change " + change.getId() + " missing " + revIdStr, e);
reject(request.cmd, "change state corrupt");
return null;
}
}
change =
db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()) {
change.nextPatchSetId();
return change;
} else {
return null;
}
}
});
if (change == null) {
reject(request.cmd, "change is closed");
return null;
}
final PatchSet ps = new PatchSet(change.currPatchSetId());
ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
ps.setUploader(currentUser.getAccountId());
ps.setRevision(toRevId(c));
insertAncestors(ps.getId(), c);
db.patchSets().insert(Collections.singleton(ps));
final Ref mergedInto = findMergedInto(change.getDest().get(), c);
result.mergedIntoRef = mergedInto != null ? mergedInto.getName() : null;
result.change = change;
result.patchSet = ps;
result.info = patchSetInfoFactory.get(c, ps.getId());
final Account.Id authorId =
result.info.getAuthor() != null ? result.info.getAuthor().getAccount()
: null;
final Account.Id committerId =
result.info.getCommitter() != null ? result.info.getCommitter()
.getAccount() : null;
boolean haveAuthor = false;
boolean haveCommitter = false;
final Set<Account.Id> haveApprovals = new HashSet<Account.Id>();
oldReviewers.clear();
oldCC.clear();
for (PatchSetApproval a : db.patchSetApprovals().byChange(change.getId())) {
haveApprovals.add(a.getAccountId());
if (a.getValue() != 0) {
oldReviewers.add(a.getAccountId());
} else {
oldCC.add(a.getAccountId());
}
final ApprovalType type =
approvalTypes.getApprovalType(a.getCategoryId());
if (a.getPatchSetId().equals(priorPatchSet)
&& type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) {
// If there was a negative vote on the prior patch set, carry it
// into this patch set.
//
db.patchSetApprovals().insert(
Collections.singleton(new PatchSetApproval(ps.getId(), a)));
}
if (!haveAuthor && authorId != null && a.getAccountId().equals(authorId)) {
haveAuthor = true;
}
if (!haveCommitter && committerId != null
&& a.getAccountId().equals(committerId)) {
haveCommitter = true;
}
}
final ChangeMessage msg =
new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
.messageUUID(db)), me, ps.getCreatedOn());
msg.setMessage("Uploaded patch set " + ps.getPatchSetId() + ".");
db.changeMessages().insert(Collections.singleton(msg));
result.msg = msg;
if (result.mergedIntoRef != null) {
// Change was already submitted to a branch, close it.
//
markChangeMergedByPush(db, result);
} else {
// Change should be new, so it can go through review again.
//
change =
db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()) {
change.setStatus(Change.Status.NEW);
change.setCurrentPatchSet(result.info);
ChangeUtil.updated(change);
return change;
} else {
return null;
}
}
});
if (change == null) {
db.patchSets().delete(Collections.singleton(ps));
db.changeMessages().delete(Collections.singleton(msg));
reject(request.cmd, "change is closed");
return null;
}
}
final List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
if (allTypes.size() > 0) {
final ApprovalCategory.Id catId =
allTypes.get(allTypes.size() - 1).getCategory().getId();
if (authorId != null && haveApprovals.add(authorId)) {
insertDummyApproval(result, authorId, catId, db);
}
if (committerId != null && haveApprovals.add(committerId)) {
insertDummyApproval(result, committerId, catId, db);
}
for (final Account.Id reviewer : reviewers) {
if (haveApprovals.add(reviewer)) {
insertDummyApproval(result, reviewer, catId, db);
}
}
}
final RefUpdate ru = repo.updateRef(ps.getRefName());
ru.setNewObjectId(c);
ru.disableRefLog();
if (ru.update(rp.getRevWalk()) != RefUpdate.Result.NEW) {
throw new IOException("Failed to create ref " + ps.getRefName() + " in "
+ repo.getDirectory() + ": " + ru.getResult());
}
replication.scheduleUpdate(project.getNameKey(), ru.getName());
request.cmd.setResult(ReceiveCommand.Result.OK);
try {
final ReplacePatchSetSender cm;
cm = replacePatchSetFactory.create(result.change);
cm.setFrom(me);
cm.setPatchSet(ps, result.info);
cm.setChangeMessage(result.msg);
cm.setReviewDb(db);
cm.addReviewers(reviewers);
cm.addExtraCC(cc);
cm.addReviewers(oldReviewers);
cm.addExtraCC(oldCC);
cm.send();
} catch (EmailException e) {
log.error("Cannot send email for new patch set " + ps.getId(), e);
}
sendMergedEmail(result);
return result != null ? result.info.getKey() : null;
}
@@ -1165,19 +1177,19 @@ final class Receive extends AbstractGitCommand {
private void insertDummyApproval(final ReplaceResult result,
final Account.Id forAccount, final ApprovalCategory.Id catId,
final ReviewDb db, final Transaction txn) throws OrmException {
final ReviewDb db) throws OrmException {
insertDummyApproval(result.change, result.patchSet.getId(), forAccount,
catId, db, txn);
catId, db);
}
private void insertDummyApproval(final Change change, final PatchSet.Id psId,
final Account.Id forAccount, final ApprovalCategory.Id catId,
final ReviewDb db, final Transaction txn) throws OrmException {
final ReviewDb db) throws OrmException {
final PatchSetApproval ca =
new PatchSetApproval(new PatchSetApproval.Key(psId, forAccount, catId),
(short) 0);
ca.cache(change);
db.patchSetApprovals().insert(Collections.singleton(ca), txn);
db.patchSetApprovals().insert(Collections.singleton(ca));
}
private Ref findMergedInto(final String first, final RevCommit commit) {
@@ -1327,35 +1339,29 @@ final class Receive extends AbstractGitCommand {
final RevCommit commit) throws OrmException {
final String refName = cmd.getRefName();
final Change.Id cid = psi.getParentKey();
final ReplaceResult result =
db.run(new OrmRunnable<ReplaceResult, ReviewDb>() {
@Override
public ReplaceResult run(ReviewDb db, Transaction txn, boolean retry)
throws OrmException {
final Change change = db.changes().get(cid);
final PatchSet ps = db.patchSets().get(psi);
if (change == null || ps == null) {
log.warn(project.getName() + " " + psi + " is missing");
return null;
}
if (change.getStatus() == Change.Status.MERGED) {
// If its already merged, don't make further updates, it
// might just be moving from an experimental branch into
// a more stable branch.
//
return null;
}
final Change change = db.changes().get(cid);
final PatchSet ps = db.patchSets().get(psi);
if (change == null || ps == null) {
log.warn(project.getName() + " " + psi + " is missing");
return;
}
final ReplaceResult result = new ReplaceResult();
result.change = change;
result.patchSet = ps;
result.info = patchSetInfoFactory.get(commit, psi);
result.mergedIntoRef = refName;
markChangeMergedByPush(db, txn, result);
return result;
}
});
if (change.getStatus() == Change.Status.MERGED) {
// If its already merged, don't make further updates, it
// might just be moving from an experimental branch into
// a more stable branch.
//
return;
}
final ReplaceResult result = new ReplaceResult();
result.change = change;
result.patchSet = ps;
result.info = patchSetInfoFactory.get(commit, psi);
result.mergedIntoRef = refName;
markChangeMergedByPush(db, result);
sendMergedEmail(result);
}
@@ -1379,7 +1385,7 @@ final class Receive extends AbstractGitCommand {
return r;
}
private void markChangeMergedByPush(final ReviewDb db, final Transaction txn,
private void markChangeMergedByPush(final ReviewDb db,
final ReplaceResult result) throws OrmException {
final Change change = result.change;
final String mergedIntoRef = result.mergedIntoRef;
@@ -1393,6 +1399,7 @@ final class Receive extends AbstractGitCommand {
for (PatchSetApproval a : approvals) {
a.cache(change);
}
db.patchSetApprovals().update(approvals);
final StringBuilder msgBuf = new StringBuilder();
msgBuf.append("Change has been successfully pushed");
@@ -1411,9 +1418,19 @@ final class Receive extends AbstractGitCommand {
.messageUUID(db)), currentUser.getAccountId());
msg.setMessage(msgBuf.toString());
db.patchSetApprovals().update(approvals, txn);
db.changeMessages().insert(Collections.singleton(msg), txn);
db.changes().update(Collections.singleton(change), txn);
db.changeMessages().insert(Collections.singleton(msg));
db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()) {
change.setCurrentPatchSet(result.info);
change.setStatus(Change.Status.MERGED);
ChangeUtil.updated(change);
}
return change;
}
});
}
private void sendMergedEmail(final ReplaceResult result) {
@@ -1433,6 +1450,24 @@ final class Receive extends AbstractGitCommand {
}
}
private void insertAncestors(PatchSet.Id id, RevCommit src)
throws OrmException {
final int cnt = src.getParentCount();
List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
for (int p = 0; p < cnt; p++) {
PatchSetAncestor a;
a = new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
a.setAncestorRevision(toRevId(src.getParent(p)));
toInsert.add(a);
}
db.patchSetAncestors().insert(toInsert);
}
private static RevId toRevId(final RevCommit src) {
return new RevId(src.getId().name());
}
private boolean canPerform(final ApprovalCategory.Id actionId, final short val) {
return projectControl.canPerform(actionId, val);
}