Line data Source code
1 : // Copyright (C) 2016 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.submit;
16 :
17 : import static com.google.common.base.MoreObjects.firstNonNull;
18 : import static com.google.common.base.Preconditions.checkState;
19 : import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
20 : import static com.google.gerrit.server.project.ProjectCache.illegalState;
21 : import static java.util.Comparator.comparing;
22 : import static java.util.Objects.requireNonNull;
23 :
24 : import com.google.common.flogger.FluentLogger;
25 : import com.google.gerrit.common.Nullable;
26 : import com.google.gerrit.entities.BranchNameKey;
27 : import com.google.gerrit.entities.Change;
28 : import com.google.gerrit.entities.LabelId;
29 : import com.google.gerrit.entities.PatchSet;
30 : import com.google.gerrit.entities.PatchSetApproval;
31 : import com.google.gerrit.entities.Project;
32 : import com.google.gerrit.entities.RefNames;
33 : import com.google.gerrit.entities.SubmitRecord;
34 : import com.google.gerrit.exceptions.StorageException;
35 : import com.google.gerrit.extensions.restapi.AuthException;
36 : import com.google.gerrit.server.ChangeMessagesUtil;
37 : import com.google.gerrit.server.IdentifiedUser;
38 : import com.google.gerrit.server.approval.ApprovalsUtil;
39 : import com.google.gerrit.server.change.LabelNormalizer;
40 : import com.google.gerrit.server.git.CodeReviewCommit;
41 : import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
42 : import com.google.gerrit.server.git.GroupCollector;
43 : import com.google.gerrit.server.git.MergeUtil;
44 : import com.google.gerrit.server.notedb.ChangeUpdate;
45 : import com.google.gerrit.server.permissions.PermissionBackendException;
46 : import com.google.gerrit.server.project.InvalidChangeOperationException;
47 : import com.google.gerrit.server.project.ProjectConfig;
48 : import com.google.gerrit.server.project.ProjectState;
49 : import com.google.gerrit.server.update.BatchUpdateOp;
50 : import com.google.gerrit.server.update.ChangeContext;
51 : import com.google.gerrit.server.update.PostUpdateContext;
52 : import com.google.gerrit.server.update.RepoContext;
53 : import java.io.IOException;
54 : import java.util.ArrayList;
55 : import java.util.HashMap;
56 : import java.util.List;
57 : import java.util.Map;
58 : import java.util.Objects;
59 : import java.util.Optional;
60 : import org.eclipse.jgit.errors.IncorrectObjectTypeException;
61 : import org.eclipse.jgit.errors.MissingObjectException;
62 : import org.eclipse.jgit.lib.ObjectId;
63 : import org.eclipse.jgit.lib.Repository;
64 : import org.eclipse.jgit.transport.ReceiveCommand;
65 :
66 : abstract class SubmitStrategyOp implements BatchUpdateOp {
67 53 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
68 :
69 : protected final SubmitStrategy.Arguments args;
70 : protected final CodeReviewCommit toMerge;
71 :
72 : private ReceiveCommand command;
73 : private PatchSetApproval submitter;
74 : private ObjectId mergeResultRev;
75 : private PatchSet mergedPatchSet;
76 : private Change updatedChange;
77 : private CodeReviewCommit alreadyMergedCommit;
78 : private boolean changeAlreadyMerged;
79 : private String stickyApprovalDiff;
80 :
81 53 : protected SubmitStrategyOp(SubmitStrategy.Arguments args, CodeReviewCommit toMerge) {
82 53 : this.args = args;
83 53 : this.toMerge = toMerge;
84 53 : }
85 :
86 : final Change.Id getId() {
87 53 : return toMerge.change().getId();
88 : }
89 :
90 : final CodeReviewCommit getCommit() {
91 53 : return toMerge;
92 : }
93 :
94 : protected final BranchNameKey getDest() {
95 53 : return toMerge.change().getDest();
96 : }
97 :
98 : protected final Project.NameKey getProject() {
99 53 : return getDest().project();
100 : }
101 :
102 : @Override
103 : public final void updateRepo(RepoContext ctx) throws Exception {
104 53 : logger.atFine().log(
105 53 : "%s#updateRepo for change %s", getClass().getSimpleName(), toMerge.change().getId());
106 53 : checkState(
107 53 : ctx.getRevWalk() == args.rw,
108 : "SubmitStrategyOp requires callers to call BatchUpdate#setRepository with exactly the same"
109 : + " CodeReviewRevWalk instance from the SubmitStrategy.Arguments: %s != %s",
110 53 : ctx.getRevWalk(),
111 : args.rw);
112 : // Run the submit strategy implementation and record the merge tip state so
113 : // we can create the ref update.
114 53 : CodeReviewCommit tipBefore = args.mergeTip.getCurrentTip();
115 53 : alreadyMergedCommit = getAlreadyMergedCommit(ctx);
116 53 : if (alreadyMergedCommit == null) {
117 53 : updateRepoImpl(ctx);
118 : } else {
119 7 : logger.atFine().log("Already merged as %s", alreadyMergedCommit.name());
120 : }
121 53 : CodeReviewCommit tipAfter = args.mergeTip.getCurrentTip();
122 :
123 53 : if (Objects.equals(tipBefore, tipAfter)) {
124 19 : logger.atFine().log("Did not move tip");
125 19 : return;
126 53 : } else if (tipAfter == null) {
127 0 : logger.atFine().log("No merge tip, no update to perform");
128 0 : return;
129 : }
130 53 : logger.atFine().log("Moved tip from %s to %s", tipBefore, tipAfter);
131 :
132 53 : checkProjectConfig(ctx, tipAfter);
133 :
134 : // Needed by postUpdate, at which point mergeTip will have advanced further,
135 : // so it's easier to just snapshot the command.
136 53 : command =
137 : new ReceiveCommand(
138 53 : firstNonNull(tipBefore, ObjectId.zeroId()), tipAfter, getDest().branch());
139 53 : ctx.addRefUpdate(command);
140 53 : args.submoduleCommits.addBranchTip(getDest(), tipAfter);
141 53 : }
142 :
143 : private void checkProjectConfig(RepoContext ctx, CodeReviewCommit commit) {
144 53 : String refName = getDest().branch();
145 53 : if (RefNames.REFS_CONFIG.equals(refName)) {
146 7 : logger.atFine().log("Loading new configuration from %s", RefNames.REFS_CONFIG);
147 : try {
148 7 : ProjectConfig cfg = args.projectConfigFactory.create(getProject());
149 7 : cfg.load(ctx.getRevWalk(), commit);
150 0 : } catch (Exception e) {
151 0 : throw new StorageException(
152 : "Submit would store invalid"
153 : + " project configuration "
154 0 : + commit.name()
155 : + " for "
156 0 : + getProject(),
157 : e);
158 7 : }
159 : }
160 53 : }
161 :
162 : @Nullable
163 : private CodeReviewCommit getAlreadyMergedCommit(RepoContext ctx) throws IOException {
164 53 : CodeReviewCommit tip = args.mergeTip.getInitialTip();
165 53 : if (tip == null) {
166 16 : return null;
167 : }
168 53 : CodeReviewRevWalk rw = (CodeReviewRevWalk) ctx.getRevWalk();
169 53 : Change.Id id = getId();
170 53 : String refPrefix = id.toRefPrefix();
171 :
172 53 : Map<String, ObjectId> refs = ctx.getRepoView().getRefs(refPrefix);
173 53 : List<CodeReviewCommit> commits = new ArrayList<>(refs.size());
174 53 : for (Map.Entry<String, ObjectId> e : refs.entrySet()) {
175 53 : PatchSet.Id psId = PatchSet.Id.fromRef(refPrefix + e.getKey());
176 53 : if (psId == null) {
177 53 : continue;
178 : }
179 : try {
180 53 : CodeReviewCommit c = rw.parseCommit(e.getValue());
181 53 : c.setPatchsetId(psId);
182 53 : commits.add(c);
183 0 : } catch (MissingObjectException | IncorrectObjectTypeException ex) {
184 0 : continue; // Bogus ref, can't be merged into tip so we don't care.
185 53 : }
186 53 : }
187 53 : commits.sort(comparing((CodeReviewCommit c) -> c.getPatchsetId().get()).reversed());
188 53 : CodeReviewCommit result = MergeUtil.findAnyMergedInto(rw, commits, tip);
189 53 : if (result == null) {
190 53 : return null;
191 : }
192 :
193 : // Some patch set of this change is actually merged into the target
194 : // branch, most likely because a previous run of MergeOp failed after
195 : // updateRepo, during updateChange.
196 : //
197 : // Do the best we can to clean this up: mark the change as merged and set
198 : // the current patch set. Don't touch the dest branch at all. This can
199 : // lead to some odd situations like another change in the set merging in
200 : // a different patch set of this change, but that's unavoidable at this
201 : // point. At least the change will end up in the right state.
202 : //
203 : // TODO(dborowitz): Consider deleting later junk patch set refs. They
204 : // presumably don't have PatchSets pointing to them.
205 7 : rw.parseBody(result);
206 7 : result.add(args.canMergeFlag);
207 7 : PatchSet.Id psId = result.getPatchsetId();
208 7 : result.copyFrom(toMerge);
209 7 : result.setPatchsetId(psId); // Got overwriten by copyFrom.
210 7 : result.setStatusCode(CommitMergeStatus.ALREADY_MERGED);
211 7 : args.commitStatus.put(result);
212 7 : return result;
213 : }
214 :
215 : @Override
216 : public final boolean updateChange(ChangeContext ctx) throws Exception {
217 53 : logger.atFine().log(
218 53 : "%s#updateChange for change %s", getClass().getSimpleName(), toMerge.change().getId());
219 53 : toMerge.setNotes(ctx.getNotes()); // Update change and notes from ctx.
220 :
221 53 : if (ctx.getChange().isMerged()) {
222 : // Either another thread won a race, or we are retrying a whole topic submission after one
223 : // repo failed with lock failure.
224 7 : if (alreadyMergedCommit == null) {
225 0 : logger.atFine().log(
226 : "Change is already merged according to its status, but we were unable to find it"
227 : + " merged into the current tip (%s)",
228 0 : args.mergeTip.getCurrentTip().name());
229 : } else {
230 7 : logger.atFine().log("Change is already merged");
231 : }
232 7 : changeAlreadyMerged = true;
233 7 : return false;
234 : }
235 :
236 53 : if (alreadyMergedCommit != null) {
237 6 : alreadyMergedCommit.setNotes(ctx.getNotes());
238 6 : mergedPatchSet = getOrCreateAlreadyMergedPatchSet(ctx);
239 : } else {
240 53 : PatchSet newPatchSet = updateChangeImpl(ctx);
241 53 : PatchSet.Id oldPsId = requireNonNull(toMerge.getPatchsetId());
242 53 : PatchSet.Id newPsId = requireNonNull(ctx.getChange().currentPatchSetId());
243 53 : if (newPatchSet == null) {
244 53 : checkState(
245 53 : oldPsId.equals(newPsId),
246 : "patch set advanced from %s to %s but updateChangeImpl did not"
247 : + " return new patch set instance",
248 : oldPsId,
249 : newPsId);
250 : // Ok to use stale notes to get the old patch set, which didn't change
251 : // during the submit strategy.
252 53 : mergedPatchSet =
253 53 : requireNonNull(
254 53 : args.psUtil.get(ctx.getNotes(), oldPsId),
255 0 : () -> String.format("missing old patch set %s", oldPsId));
256 : } else {
257 9 : PatchSet.Id n = newPatchSet.id();
258 9 : checkState(
259 9 : !n.equals(oldPsId) && n.equals(newPsId),
260 : "current patch was %s and is now %s, but updateChangeImpl returned"
261 : + " new patch set instance at %s",
262 : oldPsId,
263 : newPsId,
264 : n);
265 9 : mergedPatchSet = newPatchSet;
266 : }
267 : }
268 :
269 53 : Change c = ctx.getChange();
270 53 : Change.Id id = c.getId();
271 53 : CodeReviewCommit commit = args.commitStatus.get(id);
272 53 : requireNonNull(commit, () -> String.format("missing commit for change %s", id));
273 53 : CommitMergeStatus s = commit.getStatusCode();
274 53 : requireNonNull(
275 : s,
276 0 : () -> String.format("status not set for change %s; expected to previously fail fast", id));
277 53 : logger.atFine().log("Status of change %s (%s) on %s: %s", id, commit.name(), c.getDest(), s);
278 53 : setApproval(ctx, args.caller);
279 :
280 53 : mergeResultRev =
281 53 : alreadyMergedCommit == null
282 53 : ? args.mergeTip.getMergeResults().get(commit)
283 : // Our fixup code is not smart enough to find a merge commit
284 : // corresponding to the merge result. This results in a different
285 : // ChangeMergedEvent in the fixup case, but we'll just live with that.
286 53 : : alreadyMergedCommit;
287 : try {
288 53 : setMerged(ctx, commit, message(ctx, commit, s));
289 0 : } catch (StorageException err) {
290 0 : String msg = "Error updating change status for " + id;
291 0 : logger.atSevere().withCause(err).log("%s", msg);
292 0 : args.commitStatus.logProblem(id, msg);
293 : // It's possible this happened before updating anything in the db, but
294 : // it's hard to know for sure, so just return true below to be safe.
295 53 : }
296 53 : updatedChange = c;
297 53 : return true;
298 : }
299 :
300 : /**
301 : * Returns the updated change after this op has been executed.
302 : *
303 : * @return the updated change after this op has been executed, {@link Optional#empty()} if the op
304 : * was not executed yet, or if the execution has failed
305 : */
306 : public Optional<Change> getUpdatedChange() {
307 53 : return Optional.ofNullable(updatedChange);
308 : }
309 :
310 : private PatchSet getOrCreateAlreadyMergedPatchSet(ChangeContext ctx) throws IOException {
311 6 : PatchSet.Id psId = alreadyMergedCommit.getPatchsetId();
312 6 : logger.atFine().log("Fixing up already-merged patch set %s", psId);
313 6 : PatchSet prevPs = args.psUtil.current(ctx.getNotes());
314 6 : ctx.getRevWalk().parseBody(alreadyMergedCommit);
315 6 : ctx.getChange()
316 6 : .setCurrentPatchSet(
317 6 : psId, alreadyMergedCommit.getShortMessage(), ctx.getChange().getOriginalSubject());
318 6 : PatchSet existing = args.psUtil.get(ctx.getNotes(), psId);
319 6 : if (existing != null) {
320 6 : logger.atFine().log("Patch set row exists, only updating change");
321 6 : return existing;
322 : }
323 : // No patch set for the already merged commit, although we know it came form
324 : // a patch set ref. Fix up the database. Note that this uses the current
325 : // user as the uploader, which is as good a guess as any.
326 : List<String> groups =
327 0 : prevPs != null ? prevPs.groups() : GroupCollector.getDefaultGroups(alreadyMergedCommit);
328 0 : return args.psUtil.insert(
329 0 : ctx.getRevWalk(), ctx.getUpdate(psId), psId, alreadyMergedCommit, groups, null, null);
330 : }
331 :
332 : private void setApproval(ChangeContext ctx, IdentifiedUser user) {
333 53 : Change.Id id = ctx.getChange().getId();
334 53 : List<SubmitRecord> records = args.commitStatus.getSubmitRecords(id);
335 53 : PatchSet.Id oldPsId = toMerge.getPatchsetId();
336 53 : PatchSet.Id newPsId = ctx.getChange().currentPatchSetId();
337 :
338 53 : logger.atFine().log("Add approval for %s", id);
339 53 : ChangeUpdate origPsUpdate = ctx.getUpdate(oldPsId);
340 53 : origPsUpdate.putReviewer(user.getAccountId(), REVIEWER);
341 53 : LabelNormalizer.Result normalized = approve(ctx, origPsUpdate);
342 :
343 53 : ChangeUpdate newPsUpdate = ctx.getUpdate(newPsId);
344 53 : newPsUpdate.merge(args.submissionId, records);
345 : // If the submit strategy created a new revision (rebase, cherry-pick), copy
346 : // approvals as well.
347 53 : if (!newPsId.equals(oldPsId)) {
348 9 : saveApprovals(normalized, newPsUpdate, true);
349 9 : submitter = submitter.copyWithPatchSet(newPsId);
350 : }
351 53 : }
352 :
353 : private LabelNormalizer.Result approve(ChangeContext ctx, ChangeUpdate update) {
354 53 : PatchSet.Id psId = update.getPatchSetId();
355 53 : Map<PatchSetApproval.Key, PatchSetApproval> byKey = new HashMap<>();
356 53 : for (PatchSetApproval psa : args.approvalsUtil.byPatchSet(ctx.getNotes(), psId)) {
357 46 : byKey.put(psa.key(), psa);
358 46 : }
359 :
360 53 : submitter =
361 53 : ApprovalsUtil.newApproval(psId, ctx.getUser(), LabelId.legacySubmit(), 1, ctx.getWhen())
362 53 : .build();
363 53 : byKey.put(submitter.key(), submitter);
364 :
365 : // Flatten out existing approvals for this patch set based upon the current
366 : // permissions. Once the change is closed the approvals are not updated at
367 : // presentation view time, except for zero votes used to indicate a reviewer
368 : // was added. So we need to make sure votes are accurate now. This way if
369 : // permissions get modified in the future, historical records stay accurate.
370 53 : LabelNormalizer.Result normalized =
371 53 : args.labelNormalizer.normalize(ctx.getNotes(), byKey.values());
372 53 : update.putApproval(submitter.label(), submitter.value());
373 53 : saveApprovals(normalized, update, false);
374 53 : return normalized;
375 : }
376 :
377 : private void saveApprovals(
378 : LabelNormalizer.Result normalized, ChangeUpdate update, boolean includeUnchanged) {
379 53 : for (PatchSetApproval psa : normalized.updated()) {
380 0 : update.putApprovalFor(psa.accountId(), psa.label(), psa.value());
381 0 : }
382 53 : for (PatchSetApproval psa : normalized.deleted()) {
383 0 : update.removeApprovalFor(psa.accountId(), psa.label());
384 0 : }
385 :
386 : // TODO(dborowitz): Don't use a label in NoteDb; just check when status
387 : // change happened.
388 53 : for (PatchSetApproval psa : normalized.unchanged()) {
389 53 : if (includeUnchanged || psa.isLegacySubmit()) {
390 53 : logger.atFine().log("Adding submit label %s", psa);
391 53 : update.putApprovalFor(psa.accountId(), psa.label(), psa.value());
392 : }
393 53 : }
394 53 : }
395 :
396 : private String message(ChangeContext ctx, CodeReviewCommit commit, CommitMergeStatus s)
397 : throws AuthException, IOException, PermissionBackendException,
398 : InvalidChangeOperationException {
399 53 : requireNonNull(s, "CommitMergeStatus may not be null");
400 53 : String txt = s.getDescription();
401 53 : if (s == CommitMergeStatus.CLEAN_MERGE) {
402 53 : return message(ctx, txt);
403 12 : } else if (s == CommitMergeStatus.CLEAN_REBASE || s == CommitMergeStatus.CLEAN_PICK) {
404 9 : return message(ctx, txt + " as " + commit.name());
405 7 : } else if (s == CommitMergeStatus.SKIPPED_IDENTICAL_TREE) {
406 2 : return message(ctx, txt);
407 6 : } else if (s == CommitMergeStatus.ALREADY_MERGED) {
408 : // Best effort to mimic the message that would have happened had this
409 : // succeeded the first time around.
410 6 : switch (args.submitType) {
411 : case FAST_FORWARD_ONLY:
412 : case MERGE_ALWAYS:
413 : case MERGE_IF_NECESSARY:
414 3 : return message(ctx, commit, CommitMergeStatus.CLEAN_MERGE);
415 : case CHERRY_PICK:
416 1 : return message(ctx, commit, CommitMergeStatus.CLEAN_PICK);
417 : case REBASE_IF_NECESSARY:
418 : case REBASE_ALWAYS:
419 2 : return message(ctx, commit, CommitMergeStatus.CLEAN_REBASE);
420 : case INHERIT:
421 : default:
422 0 : throw new IllegalStateException(
423 : "unexpected submit type "
424 0 : + args.submitType.toString()
425 : + " for change "
426 0 : + commit.change().getId());
427 : }
428 : } else {
429 0 : throw new IllegalStateException(
430 : "unexpected status "
431 : + s
432 : + " for change "
433 0 : + commit.change().getId()
434 : + "; expected to previously fail fast");
435 : }
436 : }
437 :
438 : private String message(ChangeContext ctx, String body)
439 : throws AuthException, IOException, PermissionBackendException,
440 : InvalidChangeOperationException {
441 53 : stickyApprovalDiff = args.submitWithStickyApprovalDiff.apply(ctx.getNotes(), ctx.getUser());
442 53 : return body + stickyApprovalDiff;
443 : }
444 :
445 : private void setMerged(ChangeContext ctx, CodeReviewCommit commit, String msg) {
446 53 : Change c = ctx.getChange();
447 53 : logger.atFine().log("Setting change %s merged", c.getId());
448 53 : c.setStatus(Change.Status.MERGED);
449 53 : c.setSubmissionId(args.submissionId.toString());
450 : // TODO(dborowitz): We need to be able to change the author of the message,
451 : // which is not the user from the update context. addMergedMessage was able
452 : // to do this in the past.
453 53 : if (msg != null) {
454 53 : args.cmUtil.setChangeMessage(
455 53 : ctx.getUpdate(commit.getPatchsetId()), msg, ChangeMessagesUtil.TAG_MERGED);
456 : }
457 53 : }
458 :
459 : @Override
460 : public final void postUpdate(PostUpdateContext ctx) throws Exception {
461 53 : if (changeAlreadyMerged) {
462 : // TODO(dborowitz): This is suboptimal behavior in the presence of retries: postUpdate steps
463 : // will never get run for changes that submitted successfully on any but the final attempt.
464 : // This is primarily a temporary workaround for the fact that the submitter field is not
465 : // populated in the changeAlreadyMerged case.
466 : //
467 : // If we naively execute postUpdate even if the change is already merged when updateChange
468 : // being, then we are subject to a race where postUpdate steps are run twice if two submit
469 : // processes run at the same time.
470 7 : logger.atFine().log(
471 7 : "Skipping post-update steps for change %s; submitter is %s", getId(), submitter);
472 7 : return;
473 : }
474 53 : logger.atFine().log(
475 53 : "Begin post-update steps for change %s; submitter is %s", getId(), submitter);
476 53 : postUpdateImpl(ctx);
477 :
478 53 : if (command != null) {
479 53 : args.tagCache.updateFastForward(
480 53 : getProject(), command.getRefName(), command.getOldId(), command.getNewId());
481 : // TODO(dborowitz): Move to BatchUpdate? Would also allow us to run once
482 : // per project even if multiple changes to refs/meta/config are submitted.
483 53 : if (RefNames.REFS_CONFIG.equals(getDest().branch())) {
484 7 : args.projectCache.evictAndReindex(getProject());
485 7 : ProjectState p =
486 7 : args.projectCache.get(getProject()).orElseThrow(illegalState(getProject()));
487 7 : try (Repository git = args.repoManager.openRepository(getProject())) {
488 7 : git.setGitwebDescription(p.getProject().getDescription());
489 0 : } catch (IOException e) {
490 0 : logger.atSevere().withCause(e).log("cannot update description of %s", p.getName());
491 7 : }
492 : }
493 : }
494 :
495 53 : logger.atFine().log(
496 53 : "Begin sending emails for submitting change %s; submitter is %s", getId(), submitter);
497 :
498 : // Assume the change must have been merged at this point, otherwise we would
499 : // have failed fast in one of the other steps.
500 : try {
501 53 : args.mergedSenderFactory
502 53 : .create(
503 53 : ctx.getProject(),
504 53 : toMerge.change(),
505 : args.caller,
506 53 : ctx.getNotify(getId()),
507 53 : ctx.getRepoView(),
508 : stickyApprovalDiff)
509 53 : .sendAsync();
510 0 : } catch (Exception e) {
511 0 : logger.atSevere().withCause(e).log("Cannot email merged notification for %s", getId());
512 53 : }
513 53 : if (mergeResultRev != null && !args.dryrun) {
514 53 : args.changeMerged.fire(
515 53 : ctx.getChangeData(updatedChange),
516 : mergedPatchSet,
517 53 : args.accountCache.get(submitter.accountId()).orElse(null),
518 53 : args.mergeTip.getCurrentTip().name(),
519 53 : ctx.getWhen());
520 : }
521 53 : }
522 :
523 : /**
524 : * See {@link #updateRepo(RepoContext)}
525 : *
526 : * @param ctx context for the repository update
527 : */
528 15 : protected void updateRepoImpl(RepoContext ctx) throws Exception {}
529 :
530 : /**
531 : * Returns a new patch set if one was created by the submit strategy, or null if not
532 : *
533 : * <p>See {@link #updateChange(ChangeContext)}
534 : *
535 : * @param ctx context for the change update
536 : */
537 : protected PatchSet updateChangeImpl(ChangeContext ctx) throws Exception {
538 53 : return null;
539 : }
540 :
541 : /**
542 : * See {@link #postUpdate(PostUpdateContext)}
543 : *
544 : * @param ctx context for the post update
545 : */
546 53 : protected void postUpdateImpl(PostUpdateContext ctx) throws Exception {}
547 :
548 : /** Amend the commit with gitlink update */
549 : protected CodeReviewCommit amendGitlink(CodeReviewCommit commit)
550 : throws IntegrationConflictException {
551 22 : if (!args.subscriptionGraph.hasSubscription(args.destBranch)) {
552 22 : return commit;
553 : }
554 :
555 : // Modify the commit with gitlink update
556 : try {
557 1 : return args.submoduleCommits.amendGitlinksCommit(
558 1 : args.destBranch, commit, args.subscriptionGraph.getSubscriptions(args.destBranch));
559 0 : } catch (IOException e) {
560 0 : throw new StorageException(
561 0 : String.format("cannot update gitlink for the commit at branch %s", args.destBranch), e);
562 0 : } catch (SubmoduleConflictException e) {
563 0 : throw new IntegrationConflictException(
564 0 : String.format(
565 : "cannot update gitlink for the commit at branch %s: %s",
566 0 : args.destBranch, e.getMessage()),
567 : e);
568 : }
569 : }
570 : }
|