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.git; 16 : 17 : import static java.util.Objects.requireNonNull; 18 : 19 : import com.google.common.flogger.FluentLogger; 20 : import com.google.gerrit.entities.Change; 21 : import com.google.gerrit.entities.LabelId; 22 : import com.google.gerrit.entities.PatchSet; 23 : import com.google.gerrit.entities.PatchSetInfo; 24 : import com.google.gerrit.entities.SubmissionId; 25 : import com.google.gerrit.server.ChangeMessagesUtil; 26 : import com.google.gerrit.server.PatchSetUtil; 27 : import com.google.gerrit.server.config.SendEmailExecutor; 28 : import com.google.gerrit.server.extensions.events.ChangeMerged; 29 : import com.google.gerrit.server.mail.send.MergedSender; 30 : import com.google.gerrit.server.mail.send.MessageIdGenerator; 31 : import com.google.gerrit.server.notedb.ChangeUpdate; 32 : import com.google.gerrit.server.patch.PatchSetInfoFactory; 33 : import com.google.gerrit.server.update.BatchUpdateOp; 34 : import com.google.gerrit.server.update.ChangeContext; 35 : import com.google.gerrit.server.update.PostUpdateContext; 36 : import com.google.gerrit.server.util.RequestScopePropagator; 37 : import com.google.inject.Inject; 38 : import com.google.inject.Provider; 39 : import com.google.inject.assistedinject.Assisted; 40 : import java.io.IOException; 41 : import java.util.Optional; 42 : import java.util.concurrent.ExecutorService; 43 : import java.util.concurrent.Future; 44 : import org.eclipse.jgit.lib.Constants; 45 : import org.eclipse.jgit.lib.Repository; 46 : import org.eclipse.jgit.revwalk.RevCommit; 47 : import org.eclipse.jgit.revwalk.RevWalk; 48 : 49 : /** 50 : * Operation to close a change on push. 51 : * 52 : * <p>When we find a change corresponding to a commit that is pushed to a branch directly, we close 53 : * the change. This class marks the change as merged, and sends out the email notification. 54 : */ 55 : public class MergedByPushOp implements BatchUpdateOp { 56 11 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 57 : 58 : public interface Factory { 59 : MergedByPushOp create( 60 : RequestScopePropagator requestScopePropagator, 61 : PatchSet.Id psId, 62 : @Assisted SubmissionId submissionId, 63 : @Assisted("refName") String refName, 64 : @Assisted("mergeResultRevId") String mergeResultRevId); 65 : } 66 : 67 : private final RequestScopePropagator requestScopePropagator; 68 : private final PatchSetInfoFactory patchSetInfoFactory; 69 : private final ChangeMessagesUtil cmUtil; 70 : private final MergedSender.Factory mergedSenderFactory; 71 : private final PatchSetUtil psUtil; 72 : private final ExecutorService sendEmailExecutor; 73 : private final ChangeMerged changeMerged; 74 : private final MessageIdGenerator messageIdGenerator; 75 : 76 : private final PatchSet.Id psId; 77 : private final SubmissionId submissionId; 78 : private final String refName; 79 : private final String mergeResultRevId; 80 : 81 : private Change change; 82 : private boolean correctBranch; 83 : private Provider<PatchSet> patchSetProvider; 84 : private PatchSet patchSet; 85 : private PatchSetInfo info; 86 : 87 : @Inject 88 : MergedByPushOp( 89 : PatchSetInfoFactory patchSetInfoFactory, 90 : ChangeMessagesUtil cmUtil, 91 : MergedSender.Factory mergedSenderFactory, 92 : PatchSetUtil psUtil, 93 : @SendEmailExecutor ExecutorService sendEmailExecutor, 94 : ChangeMerged changeMerged, 95 : MessageIdGenerator messageIdGenerator, 96 : @Assisted RequestScopePropagator requestScopePropagator, 97 : @Assisted PatchSet.Id psId, 98 : @Assisted SubmissionId submissionId, 99 : @Assisted("refName") String refName, 100 11 : @Assisted("mergeResultRevId") String mergeResultRevId) { 101 11 : this.patchSetInfoFactory = patchSetInfoFactory; 102 11 : this.cmUtil = cmUtil; 103 11 : this.mergedSenderFactory = mergedSenderFactory; 104 11 : this.psUtil = psUtil; 105 11 : this.sendEmailExecutor = sendEmailExecutor; 106 11 : this.changeMerged = changeMerged; 107 11 : this.messageIdGenerator = messageIdGenerator; 108 11 : this.requestScopePropagator = requestScopePropagator; 109 11 : this.submissionId = submissionId; 110 11 : this.psId = psId; 111 11 : this.refName = refName; 112 11 : this.mergeResultRevId = mergeResultRevId; 113 11 : } 114 : 115 : public String getMergedIntoRef() { 116 0 : return refName; 117 : } 118 : 119 : public MergedByPushOp setPatchSetProvider(Provider<PatchSet> patchSetProvider) { 120 6 : this.patchSetProvider = requireNonNull(patchSetProvider); 121 6 : return this; 122 : } 123 : 124 : @Override 125 : public boolean updateChange(ChangeContext ctx) throws IOException { 126 11 : change = ctx.getChange(); 127 11 : correctBranch = refName.equals(change.getDest().branch()); 128 11 : if (!correctBranch) { 129 0 : return false; 130 : } 131 : 132 11 : if (patchSetProvider != null) { 133 : // Caller might have also arranged for construction of a new patch set 134 : // that is not present in the old notes so we can't use PatchSetUtil. 135 6 : patchSet = patchSetProvider.get(); 136 : } else { 137 10 : patchSet = 138 10 : requireNonNull( 139 10 : psUtil.get(ctx.getNotes(), psId), 140 0 : () -> String.format("patch set %s not found", psId)); 141 : } 142 11 : info = getPatchSetInfo(ctx); 143 : 144 11 : ChangeUpdate update = ctx.getUpdate(psId); 145 11 : if (change.isMerged()) { 146 0 : return true; 147 : } 148 11 : change.setCurrentPatchSet(info); 149 11 : change.setStatus(Change.Status.MERGED); 150 11 : change.setSubmissionId(submissionId.toString()); 151 : // we cannot reconstruct the submit records for when this change was 152 : // submitted, this is why we must fix the status and other details. 153 11 : update.fixStatusToMerged(submissionId); 154 11 : update.setCurrentPatchSet(); 155 11 : if (change.isWorkInProgress()) { 156 3 : change.setWorkInProgress(false); 157 3 : update.setWorkInProgress(false); 158 : } 159 11 : StringBuilder msgBuf = new StringBuilder(); 160 11 : msgBuf.append("Change has been successfully pushed"); 161 11 : if (!refName.equals(change.getDest().branch())) { 162 0 : msgBuf.append(" into "); 163 0 : if (refName.startsWith(Constants.R_HEADS)) { 164 0 : msgBuf.append("branch "); 165 0 : msgBuf.append(Repository.shortenRefName(refName)); 166 : } else { 167 0 : msgBuf.append(refName); 168 : } 169 : } 170 11 : msgBuf.append("."); 171 11 : cmUtil.setChangeMessage(update, msgBuf.toString(), ChangeMessagesUtil.TAG_MERGED); 172 11 : update.putApproval(LabelId.legacySubmit().get(), (short) 1); 173 11 : return true; 174 : } 175 : 176 : @Override 177 : public void postUpdate(PostUpdateContext ctx) { 178 11 : if (!correctBranch) { 179 0 : return; 180 : } 181 : @SuppressWarnings("unused") // Runnable already handles errors 182 11 : Future<?> possiblyIgnoredError = 183 11 : sendEmailExecutor.submit( 184 11 : requestScopePropagator.wrap( 185 11 : new Runnable() { 186 : @Override 187 : public void run() { 188 : try { 189 : // The stickyApprovalDiff is always empty here since this is not supported 190 : // for direct pushes. 191 11 : MergedSender emailSender = 192 11 : mergedSenderFactory.create( 193 11 : ctx.getProject(), 194 11 : psId.changeId(), 195 11 : /* stickyApprovalDiff= */ Optional.empty()); 196 11 : emailSender.setFrom(ctx.getAccountId()); 197 11 : emailSender.setPatchSet(patchSet, info); 198 11 : emailSender.setMessageId( 199 11 : messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), patchSet.id())); 200 11 : emailSender.send(); 201 0 : } catch (Exception e) { 202 0 : logger.atSevere().withCause(e).log( 203 : "Cannot send email for submitted patch set %s", psId); 204 11 : } 205 11 : } 206 : 207 : @Override 208 : public String toString() { 209 0 : return "send-email merged"; 210 : } 211 : })); 212 : 213 11 : changeMerged.fire( 214 11 : ctx.getChangeData(change), patchSet, ctx.getAccount(), mergeResultRevId, ctx.getWhen()); 215 11 : } 216 : 217 : private PatchSetInfo getPatchSetInfo(ChangeContext ctx) throws IOException { 218 11 : RevWalk rw = ctx.getRevWalk(); 219 11 : RevCommit commit = rw.parseCommit(requireNonNull(patchSet).commitId()); 220 11 : return patchSetInfoFactory.get(rw, commit, psId); 221 : } 222 : }