use of com.google.gerrit.entities.SubmissionId in project gerrit by GerritCodeReview.
the class MergeOp method merge.
/**
* Merges the given change.
*
* <p>Depending on the server configuration, more changes may be affected, e.g. by submission of a
* topic or via superproject subscriptions. All affected changes are integrated using the projects
* integration strategy.
*
* @param change the change to be merged.
* @param caller the identity of the caller
* @param checkSubmitRules whether the prolog submit rules should be evaluated
* @param submitInput parameters regarding the merge
* @throws RestApiException if an error occurred.
* @throws PermissionBackendException if permissions can't be checked
* @throws IOException an error occurred reading from NoteDb.
* @return the merged change
*/
public Change merge(Change change, IdentifiedUser caller, boolean checkSubmitRules, SubmitInput submitInput, boolean dryrun) throws RestApiException, UpdateException, IOException, ConfigInvalidException, PermissionBackendException {
this.submitInput = submitInput;
this.notify = notifyResolver.resolve(firstNonNull(submitInput.notify, NotifyHandling.ALL), submitInput.notifyDetails);
this.dryrun = dryrun;
this.caller = caller;
this.ts = TimeUtil.now();
this.submissionId = new SubmissionId(change);
try (TraceContext traceContext = TraceContext.open().addTag(RequestId.Type.SUBMISSION_ID, new RequestId(submissionId.toString()))) {
openRepoManager();
logger.atFine().log("Beginning integration of %s", change);
try {
ChangeSet indexBackedChangeSet = mergeSuperSet.setMergeOpRepoManager(orm).completeChangeSet(change, caller, /* includingTopicClosure= */
false);
if (!indexBackedChangeSet.ids().contains(change.getId())) {
// indexBackedChangeSet contains only open changes, if the change is missing in this set
// it might be that the change was concurrently submitted in the meantime.
change = changeDataFactory.create(change).reloadChange();
if (!change.isNew()) {
throw new ResourceConflictException("change is " + ChangeUtil.status(change));
}
throw new IllegalStateException(String.format("change %s missing from %s", change.getId(), indexBackedChangeSet));
}
if (indexBackedChangeSet.furtherHiddenChanges()) {
throw new AuthException("A change to be submitted with " + change.getId() + " is not visible");
}
logger.atFine().log("Calculated to merge %s", indexBackedChangeSet);
// Reload ChangeSet so that we don't rely on (potentially) stale index data for merging
ChangeSet noteDbChangeSet = reloadChanges(indexBackedChangeSet);
// At this point, any change that isn't new can be filtered out since they were only here
// in the first place due to stale index.
List<ChangeData> filteredChanges = new ArrayList<>();
for (ChangeData changeData : noteDbChangeSet.changes()) {
if (!changeData.change().getStatus().equals(Status.NEW)) {
logger.atFine().log("Change %s has status %s due to stale index, so it is skipped during submit", changeData.getId(), changeData.change().getStatus().name());
continue;
}
filteredChanges.add(changeData);
}
// There are no hidden changes (or else we would have thrown AuthException above).
ChangeSet filteredNoteDbChangeSet = new ChangeSet(filteredChanges, /* hiddenChanges= */
ImmutableList.of());
// Count cross-project submissions outside of the retry loop. The chance of a single project
// failing increases with the number of projects, so the failure count would be inflated if
// this metric were incremented inside of integrateIntoHistory.
int projects = filteredNoteDbChangeSet.projects().size();
if (projects > 1) {
topicMetrics.topicSubmissions.increment();
}
SubmissionExecutor submissionExecutor = new SubmissionExecutor(dryrun, superprojectUpdateSubmissionListeners);
RetryTracker retryTracker = new RetryTracker();
retryHelper.changeUpdate("integrateIntoHistory", updateFactory -> {
long attempt = retryTracker.lastAttemptNumber + 1;
boolean isRetry = attempt > 1;
if (isRetry) {
logger.atFine().log("Retrying, attempt #%d; skipping merged changes", attempt);
this.ts = TimeUtil.now();
openRepoManager();
}
this.commitStatus = new CommitStatus(filteredNoteDbChangeSet, isRetry);
if (checkSubmitRules) {
logger.atFine().log("Checking submit rules and state");
checkSubmitRulesAndState(filteredNoteDbChangeSet, isRetry);
} else {
logger.atFine().log("Bypassing submit rules");
bypassSubmitRulesAndRequirements(filteredNoteDbChangeSet);
}
integrateIntoHistory(filteredNoteDbChangeSet, submissionExecutor);
return null;
}).listener(retryTracker).defaultTimeoutMultiplier(filteredNoteDbChangeSet.projects().size() * 2).retryOn(t -> t instanceof RuntimeException).call();
submissionExecutor.afterExecutions(orm);
if (projects > 1) {
topicMetrics.topicSubmissionsCompleted.increment();
}
// (e.g. caller provided a change that was already merged).
return updatedChanges.containsKey(change.getId()) ? updatedChanges.get(change.getId()) : change;
} catch (IOException e) {
// Anything before the merge attempt is an error
throw new StorageException(e);
}
}
}
use of com.google.gerrit.entities.SubmissionId in project gerrit by GerritCodeReview.
the class CommitMessageOutputTest method submitCommitFormat.
@Test
// Instants
@SuppressWarnings("JdkObsolete")
public void submitCommitFormat() throws Exception {
Change c = newChange();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setSubjectForCommit("Submit patch set 1");
SubmissionId submissionId = new SubmissionId(c);
update.merge(submissionId, ImmutableList.of(submitRecord("NOT_READY", null, submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), submitLabel(LabelId.CODE_REVIEW, "NEED", null)), submitRecord("NOT_READY", null, submitLabel(LabelId.VERIFIED, "OK", changeOwner.getAccountId()), submitLabel("Alternative-Code-Review", "NEED", null))));
update.commit();
RevCommit commit = parseCommit(update.getResult());
assertBodyEquals("Submit patch set 1\n" + "\n" + "Patch-set: 1\n" + "Status: merged\n" + "Submission-id: " + submissionId.toString() + "\n" + "Submitted-with: NOT_READY\n" + "Submitted-with: OK: Verified: Gerrit User 1 <1@gerrit>\n" + "Submitted-with: NEED: Code-Review\n" + "Submitted-with: NOT_READY\n" + "Submitted-with: OK: Verified: Gerrit User 1 <1@gerrit>\n" + "Submitted-with: NEED: Alternative-Code-Review\n", commit);
PersonIdent author = commit.getAuthorIdent();
assertThat(author.getName()).isEqualTo("Gerrit User 1");
assertThat(author.getEmailAddress()).isEqualTo("1@gerrit");
assertThat(author.getWhen().getTime()).isEqualTo(c.getCreatedOn().toEpochMilli() + 2000);
assertThat(author.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT-7:00"));
PersonIdent committer = commit.getCommitterIdent();
assertThat(committer.getName()).isEqualTo("Gerrit Server");
assertThat(committer.getEmailAddress()).isEqualTo("noreply@gerrit.com");
assertThat(committer.getWhen().getTime()).isEqualTo(author.getWhen().getTime());
assertThat(committer.getTimeZone()).isEqualTo(author.getTimeZone());
}
use of com.google.gerrit.entities.SubmissionId in project gerrit by GerritCodeReview.
the class ChangeInserter method updateChange.
@Override
public boolean updateChange(ChangeContext ctx) throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException {
// Use defensive copy created by ChangeControl.
change = ctx.getChange();
patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
ctx.getChange().setCurrentPatchSet(patchSetInfo);
ChangeUpdate update = ctx.getUpdate(psId);
update.setChangeId(change.getKey().get());
update.setSubjectForCommit("Create change");
update.setBranch(change.getDest().branch());
try {
update.setTopic(change.getTopic());
} catch (ValidationException ex) {
throw new BadRequestException(ex.getMessage());
}
update.setPsDescription(patchSetDescription);
update.setPrivate(isPrivate);
update.setWorkInProgress(workInProgress);
if (revertOf != null) {
update.setRevertOf(revertOf.get());
}
if (cherryPickOf != null) {
update.setCherryPickOf(cherryPickOf.getCommaSeparatedChangeAndPatchSetId());
}
List<String> newGroups = groups;
if (newGroups.isEmpty()) {
newGroups = GroupCollector.getDefaultGroups(commitId);
}
patchSet = psUtil.insert(ctx.getRevWalk(), update, psId, commitId, newGroups, pushCert, patchSetDescription);
/* TODO: fixStatusToMerged is used here because the tests
* (byStatusClosed() in AbstractQueryChangesTest)
* insert changes that are already merged,
* and setStatus may not be used to set the Status to merged
*
* is it possible to make the tests use the merge code path,
* instead of setting the status directly?
*/
if (change.getStatus() == Change.Status.MERGED) {
update.fixStatusToMerged(new SubmissionId(change));
} else {
update.setStatus(change.getStatus());
}
reviewerAdditions = reviewerModifier.prepare(ctx.getNotes(), ctx.getUser(), getReviewerInputs(), true);
Optional<ReviewerModification> reviewerError = reviewerAdditions.getFailures().stream().findFirst();
if (reviewerError.isPresent()) {
throw new UnprocessableEntityException(reviewerError.get().result.error);
}
reviewerAdditions.updateChange(ctx, patchSet);
LabelTypes labelTypes = projectState.getLabelTypes();
approvalsUtil.addApprovalsForNewPatchSet(update, labelTypes, patchSet, ctx.getUser(), approvals);
// TODO(dborowitz): Still necessary?
if (!approvals.isEmpty()) {
update.putReviewer(ctx.getAccountId(), REVIEWER);
}
if (message != null) {
changeMessage = cmUtil.setChangeMessage(update, message, ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
}
return true;
}
use of com.google.gerrit.entities.SubmissionId in project gerrit by GerritCodeReview.
the class ReceiveCommits method autoCloseChanges.
private void autoCloseChanges(ReceiveCommand cmd, Task progress) {
try (TraceTimer traceTimer = newTimer("autoCloseChanges")) {
logger.atFine().log("Starting auto-closing of changes");
String refName = cmd.getRefName();
Set<Change.Id> ids = new HashSet<>();
// handleRegularCommands
try {
retryHelper.changeUpdate("autoCloseChanges", updateFactory -> {
try (BatchUpdate bu = updateFactory.create(projectState.getNameKey(), user, TimeUtil.now());
ObjectInserter ins = repo.newObjectInserter();
ObjectReader reader = ins.newReader();
RevWalk rw = new RevWalk(reader)) {
if (ObjectId.zeroId().equals(cmd.getOldId())) {
// potentially expensive computation that loop over all commits.
return null;
}
bu.setRepository(repo, rw, ins);
// TODO(dborowitz): Teach BatchUpdate to ignore missing changes.
RevCommit newTip = rw.parseCommit(cmd.getNewId());
BranchNameKey branch = BranchNameKey.create(project.getNameKey(), refName);
rw.reset();
rw.sort(RevSort.REVERSE);
rw.markStart(newTip);
rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
Map<Change.Key, ChangeNotes> byKey = null;
List<ReplaceRequest> replaceAndClose = new ArrayList<>();
int existingPatchSets = 0;
int newPatchSets = 0;
SubmissionId submissionId = null;
COMMIT: for (RevCommit c; (c = rw.next()) != null; ) {
rw.parseBody(c);
// refs pointing to this commit.
for (PatchSet.Id psId : receivePackRefCache.patchSetIdsFromObjectId(c.copy())) {
Optional<ChangeNotes> notes = getChangeNotes(psId.changeId());
if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
if (submissionId == null) {
submissionId = new SubmissionId(notes.get().getChange());
}
existingPatchSets++;
bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
bu.addOp(psId.changeId(), mergedByPushOpFactory.create(requestScopePropagator, psId, submissionId, refName, newTip.getId().getName()));
continue COMMIT;
}
}
for (String changeId : ChangeUtil.getChangeIdsFromFooter(c, urlFormatter.get())) {
if (byKey == null) {
byKey = retryHelper.changeIndexQuery("queryOpenChangesByKeyByBranch", q -> openChangesByKeyByBranch(q, branch)).call();
}
ChangeNotes onto = byKey.get(Change.key(changeId.trim()));
if (onto != null) {
newPatchSets++;
// Hold onto this until we're done with the walk, as the call to
// req.validate below calls isMergedInto which resets the walk.
ReplaceRequest req = new ReplaceRequest(onto.getChangeId(), c, cmd, false);
req.notes = onto;
replaceAndClose.add(req);
continue COMMIT;
}
}
}
for (ReplaceRequest req : replaceAndClose) {
Change.Id id = req.notes.getChangeId();
if (!req.validateNewPatchSetForAutoClose()) {
logger.atFine().log("Not closing %s because validation failed", id);
continue;
}
if (submissionId == null) {
submissionId = new SubmissionId(req.notes.getChange());
}
req.addOps(bu, null);
bu.addOp(id, setPrivateOpFactory.create(false, null));
bu.addOp(id, mergedByPushOpFactory.create(requestScopePropagator, req.psId, submissionId, refName, newTip.getId().getName()).setPatchSetProvider(req.replaceOp::getPatchSet));
bu.addOp(id, new ChangeProgressOp(progress));
ids.add(id);
}
logger.atFine().log("Auto-closing %d changes with existing patch sets and %d with new patch" + " sets", existingPatchSets, newPatchSets);
bu.execute();
} catch (IOException | StorageException | PermissionBackendException e) {
throw new StorageException("Failed to auto-close changes", e);
}
// If we are here, we didn't throw UpdateException. Record the result.
// The ordering is indeterminate due to the HashSet; unfortunately, Change.Id
// doesn't
// fit into TreeSet.
ids.stream().forEach(id -> result.addChange(ReceiveCommitsResult.ChangeStatus.AUTOCLOSED, id));
return null;
}).defaultTimeoutMultiplier(5).call();
} catch (RestApiException e) {
logger.atSevere().withCause(e).log("Can't insert patchset");
} catch (UpdateException e) {
logger.atSevere().withCause(e).log("Failed to auto-close changes");
} finally {
logger.atFine().log("Done auto-closing changes");
}
}
}
use of com.google.gerrit.entities.SubmissionId in project gerrit by GerritCodeReview.
the class ConsistencyCheckerIT method mergedChangeIsNotMerged.
@Test
public void mergedChangeIsNotMerged() throws Exception {
ChangeNotes notes = insertChange();
try (BatchUpdate bu = newUpdate(adminId)) {
bu.addOp(notes.getChangeId(), new BatchUpdateOp() {
@Override
public boolean updateChange(ChangeContext ctx) {
ctx.getChange().setStatus(Change.Status.MERGED);
ctx.getUpdate(ctx.getChange().currentPatchSetId()).fixStatusToMerged(new SubmissionId(ctx.getChange()));
return true;
}
});
bu.execute();
}
notes = reload(notes);
ObjectId tip = getDestRef(notes);
assertProblems(notes, null, problem("Patch set 1 (" + psUtil.current(notes).commitId().name() + ") is not merged into destination ref" + " refs/heads/master (" + tip.name() + "), but change status is MERGED"));
}
Aggregations