Merge changes from topic 'submit-whole-topic-mergeability-check'

* changes:
  AbstractSubmit: Check submit button is there before clicking it
  ChangeNotes: Enforce project passed to factory matches change
  AbstractDaemonTest: Extract methods for parsing resources
  AbstractSubmit: Use testing time
  TestTimeUtil: Also stub out JGit's SystemReader
  ChangeNotesParser: Don't always bump lastUpdatedOn
This commit is contained in:
Edwin Kempin
2016-02-22 12:03:42 +00:00
committed by Gerrit Code Review
15 changed files with 271 additions and 83 deletions

View File

@@ -68,7 +68,8 @@ public class GetRevisionActions implements ETagView<RevisionResource> {
rsrc.getChangeResource().prepareETag(h, user);
h.putBoolean(Submit.wholeTopicEnabled(config));
ReviewDb db = dbProvider.get();
ChangeSet cs = mergeSuperSet.completeChangeSet(db, rsrc.getChange());
ChangeSet cs =
mergeSuperSet.completeChangeSet(db, rsrc.getChange(), user);
for (ChangeData cd : cs.changes()) {
new ChangeResource(cd.changeControl()).prepareETag(h, user);
}

View File

@@ -32,7 +32,6 @@ import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -47,7 +46,6 @@ import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeSuperSet;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
@@ -117,7 +115,6 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private final GitRepositoryManager repoManager;
private final ChangeData.Factory changeDataFactory;
private final ChangeMessagesUtil cmUtil;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeNotes.Factory changeNotesFactory;
private final Provider<MergeOp> mergeOpProvider;
private final MergeSuperSet mergeSuperSet;
@@ -137,7 +134,6 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
GitRepositoryManager repoManager,
ChangeData.Factory changeDataFactory,
ChangeMessagesUtil cmUtil,
ChangeControl.GenericFactory changeControlFactory,
ChangeNotes.Factory changeNotesFactory,
Provider<MergeOp> mergeOpProvider,
MergeSuperSet mergeSuperSet,
@@ -149,7 +145,6 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
this.repoManager = repoManager;
this.changeDataFactory = changeDataFactory;
this.cmUtil = cmUtil;
this.changeControlFactory = changeControlFactory;
this.changeNotesFactory = changeNotesFactory;
this.mergeOpProvider = mergeOpProvider;
this.mergeSuperSet = mergeSuperSet;
@@ -233,19 +228,16 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
/**
* @param cs set of changes to be submitted at once
* @param project the name of the project
* @param identifiedUser the user who is checking to submit
* @return a reason why any of the changes is not submittable or null
*/
private String problemsForSubmittingChangeset(ChangeSet cs,
Project.NameKey project, IdentifiedUser identifiedUser) {
IdentifiedUser identifiedUser) {
try {
@SuppressWarnings("resource")
ReviewDb db = dbProvider.get();
for (PatchSet.Id psId : cs.patchIds()) {
ChangeControl changeControl = changeControlFactory
.controlFor(db, project, psId.getParentKey(), identifiedUser);
ChangeData c = changeDataFactory.create(db, changeControl);
for (ChangeData c : cs.changes()) {
ChangeControl changeControl = c.changeControl(identifiedUser);
if (!changeControl.isVisible(db)) {
return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
@@ -270,7 +262,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
}
} catch (ResourceConflictException e) {
return BLOCKED_SUBMIT_TOOLTIP;
} catch (NoSuchChangeException | OrmException e) {
} catch (OrmException e) {
log.error("Error checking if change is submittable", e);
throw new OrmRuntimeException("Could not determine problems for the change", e);
}
@@ -329,7 +321,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
ChangeSet cs;
try {
cs = mergeSuperSet.completeChangeSet(db, cd.change());
cs = mergeSuperSet.completeChangeSet(
db, cd.change(), resource.getControl().getUser());
} catch (OrmException | IOException e) {
throw new OrmRuntimeException("Could not determine complete set of " +
"changes to be submitted", e);
@@ -343,8 +336,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
&& !Strings.isNullOrEmpty(topic)
&& topicSize > 1;
String submitProblems = problemsForSubmittingChangeset(cs,
resource.getProject(), resource.getUser());
String submitProblems =
problemsForSubmittingChangeset(cs, resource.getUser());
if (submitProblems != null) {
return new UiAction.Description()
.setLabel(treatWithTopic

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.WalkSorter.PatchSetData;
import com.google.gerrit.server.git.ChangeSet;
import com.google.gerrit.server.git.MergeSuperSet;
@@ -74,7 +75,7 @@ public class SubmittedTogether implements RestReadView<ChangeResource> {
Change c = resource.getChange();
List<ChangeData> cds;
if (c.getStatus().isOpen()) {
cds = getForOpenChange(c);
cds = getForOpenChange(c, resource.getControl().getUser());
} else if (c.getStatus().asChangeStatus() == ChangeStatus.MERGED) {
cds = getForMergedChange(c);
} else {
@@ -99,9 +100,9 @@ public class SubmittedTogether implements RestReadView<ChangeResource> {
}
}
private List<ChangeData> getForOpenChange(Change c)
private List<ChangeData> getForOpenChange(Change c, CurrentUser user)
throws OrmException, IOException {
ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(), c);
ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(), c, user);
return cs.changes().asList();
}

View File

@@ -547,7 +547,7 @@ public class MergeOp implements AutoCloseable {
this.db = db;
logDebug("Beginning integration of {}", change);
try {
ChangeSet cs = mergeSuperSet.completeChangeSet(db, change);
ChangeSet cs = mergeSuperSet.completeChangeSet(db, change, caller);
checkState(cs.ids().contains(change.getId()),
"change %s missing from %s", change.getId(), cs);
this.commits = new CommitStatus(cs);

View File

@@ -24,6 +24,7 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.ChangeField;
@@ -90,21 +91,22 @@ public class MergeSuperSet {
this.repoManager = repoManager;
}
public ChangeSet completeChangeSet(ReviewDb db, Change change)
public ChangeSet completeChangeSet(ReviewDb db, Change change, CurrentUser user)
throws MissingObjectException, IncorrectObjectTypeException, IOException,
OrmException {
ChangeData cd =
changeDataFactory.create(db, change.getProject(), change.getId());
cd.changeControl(user);
if (Submit.wholeTopicEnabled(cfg)) {
return completeChangeSetIncludingTopics(db, new ChangeSet(cd));
return completeChangeSetIncludingTopics(db, new ChangeSet(cd), user);
} else {
return completeChangeSetWithoutTopic(db, new ChangeSet(cd));
return completeChangeSetWithoutTopic(db, new ChangeSet(cd), user);
}
}
private ChangeSet completeChangeSetWithoutTopic(ReviewDb db, ChangeSet changes)
throws MissingObjectException, IncorrectObjectTypeException, IOException,
OrmException {
private ChangeSet completeChangeSetWithoutTopic(ReviewDb db, ChangeSet changes,
CurrentUser user) throws MissingObjectException,
IncorrectObjectTypeException, IOException, OrmException {
List<ChangeData> ret = new ArrayList<>();
Multimap<Project.NameKey, Change.Id> pc = changes.changesByProject();
@@ -113,6 +115,7 @@ public class MergeSuperSet {
RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
for (Change.Id cId : pc.get(project)) {
ChangeData cd = changeDataFactory.create(db, project, cId);
cd.changeControl(user);
SubmitTypeRecord str = cd.submitTypeRecord();
if (!str.isOk()) {
@@ -169,11 +172,12 @@ public class MergeSuperSet {
}
private ChangeSet completeChangeSetIncludingTopics(
ReviewDb db, ChangeSet changes) throws MissingObjectException,
IncorrectObjectTypeException, IOException, OrmException {
ReviewDb db, ChangeSet changes, CurrentUser user)
throws MissingObjectException, IncorrectObjectTypeException, IOException,
OrmException {
Set<String> topicsTraversed = new HashSet<>();
boolean done = false;
ChangeSet newCs = completeChangeSetWithoutTopic(db, changes);
ChangeSet newCs = completeChangeSetWithoutTopic(db, changes, user);
while (!done) {
List<ChangeData> chgs = new ArrayList<>();
done = true;
@@ -187,7 +191,7 @@ public class MergeSuperSet {
}
}
changes = new ChangeSet(chgs);
newCs = completeChangeSetWithoutTopic(db, changes);
newCs = completeChangeSetWithoutTopic(db, changes, user);
}
return newCs;
}

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.GERRIT_PLACEHOLDER_HOST;
@@ -174,6 +175,10 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
public ChangeNotes create(ReviewDb db, Project.NameKey project,
Change.Id changeId) throws OrmException {
Change change = db.changes().get(changeId);
checkArgument(change.getProject().equals(project),
"passed project %s when creating ChangeNotes for %s, but actual"
+ " project is %s",
project, changeId, change.getProject());
// TODO: Throw NoSuchChangeException when the change is not found in the
// database
return new ChangeNotes(repoManager, migration, allUsersProvider, project,

View File

@@ -195,15 +195,15 @@ class ChangeNotesParser implements AutoCloseable {
Timestamp ts =
new Timestamp(commit.getCommitterIdent().getWhen().getTime());
boolean updateTs = commit.getParentCount() == 0;
createdOn = ts;
if (lastUpdatedOn == null) {
lastUpdatedOn = ts;
}
if (branch == null) {
branch = parseBranch(commit);
updateTs |= branch != null;
}
if (status == null) {
status = parseStatus(commit);
updateTs |= status != null;
}
PatchSet.Id psId = parsePatchSetId(commit);
@@ -221,6 +221,7 @@ class ChangeNotesParser implements AutoCloseable {
if (changeId == null) {
changeId = parseChangeId(commit);
updateTs |= changeId != null;
}
String currSubject = parseSubject(commit);
@@ -229,21 +230,28 @@ class ChangeNotesParser implements AutoCloseable {
subject = currSubject;
}
originalSubject = currSubject;
updateTs = true;
}
parseChangeMessage(psId, accountId, commit, ts);
updateTs |= parseChangeMessage(psId, accountId, commit, ts) != null;
if (topic == null) {
topic = parseTopic(commit);
updateTs |= topic != null;
}
Set<String> oldHashtags = hashtags;
parseHashtags(commit);
updateTs |= hashtags != oldHashtags;
if (submissionId == null) {
submissionId = parseSubmissionId(commit);
updateTs |= submissionId != null;
}
ObjectId currRev = parseRevision(commit);
if (currRev != null) {
parsePatchSet(psId, currRev, accountId, ts);
updateTs = true;
}
parseGroups(psId, commit);
@@ -251,16 +259,24 @@ class ChangeNotesParser implements AutoCloseable {
// Only parse the most recent set of submit records; any older ones are
// still there, but not currently used.
parseSubmitRecords(commit.getFooterLines(FOOTER_SUBMITTED_WITH));
updateTs |= !submitRecords.isEmpty();
}
for (String line : commit.getFooterLines(FOOTER_LABEL)) {
parseApproval(psId, accountId, ts, line);
updateTs = true;
}
for (ReviewerStateInternal state : ReviewerStateInternal.values()) {
for (String line : commit.getFooterLines(state.getFooterKey())) {
parseReviewer(state, line);
}
// Don't update timestamp when a reviewer was added, matching RevewDb
// behavior.
}
if (lastUpdatedOn == null && updateTs) {
lastUpdatedOn = ts;
}
}
@@ -418,20 +434,20 @@ class ChangeNotesParser implements AutoCloseable {
throw invalidFooter(FOOTER_PATCH_SET, psIdLine);
}
private void parseChangeMessage(PatchSet.Id psId, Account.Id accountId,
RevCommit commit, Timestamp ts) {
private ChangeMessage parseChangeMessage(PatchSet.Id psId,
Account.Id accountId, RevCommit commit, Timestamp ts) {
byte[] raw = commit.getRawBuffer();
int size = raw.length;
Charset enc = RawParseUtils.parseEncoding(raw);
int subjectStart = RawParseUtils.commitMessage(raw, 0);
if (subjectStart < 0 || subjectStart >= size) {
return;
return null;
}
int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
if (subjectEnd == size) {
return;
return null;
}
int changeMessageStart;
@@ -441,7 +457,7 @@ class ChangeNotesParser implements AutoCloseable {
} else if (raw[subjectEnd] == '\r') {
changeMessageStart = subjectEnd + 4; //\r\n\r\n ends paragraph
} else {
return;
return null;
}
int ptr = size - 1;
@@ -461,7 +477,7 @@ class ChangeNotesParser implements AutoCloseable {
}
if (ptr <= changeMessageStart) {
return;
return null;
}
String changeMsgString = RawParseUtils.decode(enc, raw,
@@ -474,6 +490,7 @@ class ChangeNotesParser implements AutoCloseable {
changeMessage.setMessage(changeMsgString);
changeMessagesByPatchSet.put(psId, changeMessage);
allChangeMessages.add(changeMessage);
return changeMessage;
}
private void parseNotes()

View File

@@ -618,6 +618,13 @@ public class ChangeData {
public ChangeControl changeControl(CurrentUser user) throws OrmException {
if (changeControl != null) {
CurrentUser oldUser = user;
// TODO(dborowitz): This is a hack; general CurrentUser equality would be
// better.
if (user.isIdentifiedUser() && oldUser.isIdentifiedUser()
&& user.getAccountId().equals(oldUser.getAccountId())) {
return changeControl;
}
throw new IllegalStateException(
"user already specified: " + changeControl.getUser());
}