PageRenderTime 1000ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/java/com/google/gerrit/server/change/ChangeInserter.java

https://gitlab.com/chenfengxu/gerrit
Java | 576 lines | 483 code | 51 blank | 42 comment | 36 complexity | a287fb5b8e0d2aa2cf0c31037e8232d1 MD5 | raw file
  1. // Copyright (C) 2013 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. package com.google.gerrit.server.change;
  15. import static com.google.common.base.Preconditions.checkNotNull;
  16. import static com.google.common.base.Preconditions.checkState;
  17. import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
  18. import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
  19. import static java.util.stream.Collectors.toSet;
  20. import com.google.common.base.MoreObjects;
  21. import com.google.common.collect.ImmutableListMultimap;
  22. import com.google.common.collect.ListMultimap;
  23. import com.google.gerrit.common.FooterConstants;
  24. import com.google.gerrit.common.data.LabelType;
  25. import com.google.gerrit.common.data.LabelTypes;
  26. import com.google.gerrit.extensions.api.changes.NotifyHandling;
  27. import com.google.gerrit.extensions.api.changes.RecipientType;
  28. import com.google.gerrit.extensions.restapi.ResourceConflictException;
  29. import com.google.gerrit.extensions.restapi.RestApiException;
  30. import com.google.gerrit.reviewdb.client.Account;
  31. import com.google.gerrit.reviewdb.client.Branch;
  32. import com.google.gerrit.reviewdb.client.Change;
  33. import com.google.gerrit.reviewdb.client.ChangeMessage;
  34. import com.google.gerrit.reviewdb.client.PatchSet;
  35. import com.google.gerrit.reviewdb.client.PatchSetInfo;
  36. import com.google.gerrit.reviewdb.server.ReviewDb;
  37. import com.google.gerrit.server.ApprovalsUtil;
  38. import com.google.gerrit.server.ChangeMessagesUtil;
  39. import com.google.gerrit.server.PatchSetUtil;
  40. import com.google.gerrit.server.config.SendEmailExecutor;
  41. import com.google.gerrit.server.events.CommitReceivedEvent;
  42. import com.google.gerrit.server.extensions.events.CommentAdded;
  43. import com.google.gerrit.server.extensions.events.RevisionCreated;
  44. import com.google.gerrit.server.git.GroupCollector;
  45. import com.google.gerrit.server.git.validators.CommitValidationException;
  46. import com.google.gerrit.server.git.validators.CommitValidators;
  47. import com.google.gerrit.server.mail.send.CreateChangeSender;
  48. import com.google.gerrit.server.notedb.ChangeNotes;
  49. import com.google.gerrit.server.notedb.ChangeUpdate;
  50. import com.google.gerrit.server.notedb.NotesMigration;
  51. import com.google.gerrit.server.patch.PatchSetInfoFactory;
  52. import com.google.gerrit.server.permissions.ChangePermission;
  53. import com.google.gerrit.server.permissions.PermissionBackend;
  54. import com.google.gerrit.server.permissions.PermissionBackendException;
  55. import com.google.gerrit.server.project.ProjectCache;
  56. import com.google.gerrit.server.project.ProjectState;
  57. import com.google.gerrit.server.ssh.NoSshInfo;
  58. import com.google.gerrit.server.update.ChangeContext;
  59. import com.google.gerrit.server.update.Context;
  60. import com.google.gerrit.server.update.InsertChangeOp;
  61. import com.google.gerrit.server.update.RepoContext;
  62. import com.google.gerrit.server.util.RequestScopePropagator;
  63. import com.google.gwtorm.server.OrmException;
  64. import com.google.inject.Inject;
  65. import com.google.inject.assistedinject.Assisted;
  66. import java.io.IOException;
  67. import java.util.Collections;
  68. import java.util.HashMap;
  69. import java.util.HashSet;
  70. import java.util.List;
  71. import java.util.Map;
  72. import java.util.Set;
  73. import java.util.concurrent.ExecutorService;
  74. import java.util.concurrent.Future;
  75. import org.eclipse.jgit.lib.ObjectId;
  76. import org.eclipse.jgit.revwalk.RevCommit;
  77. import org.eclipse.jgit.revwalk.RevWalk;
  78. import org.eclipse.jgit.transport.ReceiveCommand;
  79. import org.eclipse.jgit.util.ChangeIdUtil;
  80. import org.slf4j.Logger;
  81. import org.slf4j.LoggerFactory;
  82. public class ChangeInserter implements InsertChangeOp {
  83. public interface Factory {
  84. ChangeInserter create(Change.Id cid, ObjectId commitId, String refName);
  85. }
  86. private static final Logger log = LoggerFactory.getLogger(ChangeInserter.class);
  87. private final PermissionBackend permissionBackend;
  88. private final ProjectCache projectCache;
  89. private final PatchSetInfoFactory patchSetInfoFactory;
  90. private final PatchSetUtil psUtil;
  91. private final ApprovalsUtil approvalsUtil;
  92. private final ChangeMessagesUtil cmUtil;
  93. private final CreateChangeSender.Factory createChangeSenderFactory;
  94. private final ExecutorService sendEmailExecutor;
  95. private final CommitValidators.Factory commitValidatorsFactory;
  96. private final RevisionCreated revisionCreated;
  97. private final CommentAdded commentAdded;
  98. private final NotesMigration migration;
  99. private final Change.Id changeId;
  100. private final PatchSet.Id psId;
  101. private final ObjectId commitId;
  102. private final String refName;
  103. // Fields exposed as setters.
  104. private Change.Status status;
  105. private String topic;
  106. private String message;
  107. private String patchSetDescription;
  108. private boolean isPrivate;
  109. private boolean workInProgress;
  110. private List<String> groups = Collections.emptyList();
  111. private boolean validate = true;
  112. private NotifyHandling notify = NotifyHandling.ALL;
  113. private ListMultimap<RecipientType, Account.Id> accountsToNotify = ImmutableListMultimap.of();
  114. private Set<Account.Id> reviewers;
  115. private Set<Account.Id> extraCC;
  116. private Map<String, Short> approvals;
  117. private RequestScopePropagator requestScopePropagator;
  118. private boolean fireRevisionCreated;
  119. private boolean sendMail;
  120. private boolean updateRef;
  121. private Change.Id revertOf;
  122. // Fields set during the insertion process.
  123. private ReceiveCommand cmd;
  124. private Change change;
  125. private ChangeMessage changeMessage;
  126. private PatchSetInfo patchSetInfo;
  127. private PatchSet patchSet;
  128. private String pushCert;
  129. private ProjectState projectState;
  130. @Inject
  131. ChangeInserter(
  132. PermissionBackend permissionBackend,
  133. ProjectCache projectCache,
  134. PatchSetInfoFactory patchSetInfoFactory,
  135. PatchSetUtil psUtil,
  136. ApprovalsUtil approvalsUtil,
  137. ChangeMessagesUtil cmUtil,
  138. CreateChangeSender.Factory createChangeSenderFactory,
  139. @SendEmailExecutor ExecutorService sendEmailExecutor,
  140. CommitValidators.Factory commitValidatorsFactory,
  141. CommentAdded commentAdded,
  142. RevisionCreated revisionCreated,
  143. NotesMigration migration,
  144. @Assisted Change.Id changeId,
  145. @Assisted ObjectId commitId,
  146. @Assisted String refName) {
  147. this.permissionBackend = permissionBackend;
  148. this.projectCache = projectCache;
  149. this.patchSetInfoFactory = patchSetInfoFactory;
  150. this.psUtil = psUtil;
  151. this.approvalsUtil = approvalsUtil;
  152. this.cmUtil = cmUtil;
  153. this.createChangeSenderFactory = createChangeSenderFactory;
  154. this.sendEmailExecutor = sendEmailExecutor;
  155. this.commitValidatorsFactory = commitValidatorsFactory;
  156. this.revisionCreated = revisionCreated;
  157. this.commentAdded = commentAdded;
  158. this.migration = migration;
  159. this.changeId = changeId;
  160. this.psId = new PatchSet.Id(changeId, INITIAL_PATCH_SET_ID);
  161. this.commitId = commitId.copy();
  162. this.refName = refName;
  163. this.reviewers = Collections.emptySet();
  164. this.extraCC = Collections.emptySet();
  165. this.approvals = Collections.emptyMap();
  166. this.fireRevisionCreated = true;
  167. this.sendMail = true;
  168. this.updateRef = true;
  169. }
  170. @Override
  171. public Change createChange(Context ctx) throws IOException {
  172. change =
  173. new Change(
  174. getChangeKey(ctx.getRevWalk(), commitId),
  175. changeId,
  176. ctx.getAccountId(),
  177. new Branch.NameKey(ctx.getProject(), refName),
  178. ctx.getWhen());
  179. change.setStatus(MoreObjects.firstNonNull(status, Change.Status.NEW));
  180. change.setTopic(topic);
  181. change.setPrivate(isPrivate);
  182. change.setWorkInProgress(workInProgress);
  183. change.setReviewStarted(!workInProgress);
  184. change.setRevertOf(revertOf);
  185. return change;
  186. }
  187. private static Change.Key getChangeKey(RevWalk rw, ObjectId id) throws IOException {
  188. RevCommit commit = rw.parseCommit(id);
  189. rw.parseBody(commit);
  190. List<String> idList = commit.getFooterLines(FooterConstants.CHANGE_ID);
  191. if (!idList.isEmpty()) {
  192. return new Change.Key(idList.get(idList.size() - 1).trim());
  193. }
  194. ObjectId changeId =
  195. ChangeIdUtil.computeChangeId(
  196. commit.getTree(),
  197. commit,
  198. commit.getAuthorIdent(),
  199. commit.getCommitterIdent(),
  200. commit.getShortMessage());
  201. StringBuilder changeIdStr = new StringBuilder();
  202. changeIdStr.append("I").append(ObjectId.toString(changeId));
  203. return new Change.Key(changeIdStr.toString());
  204. }
  205. public PatchSet.Id getPatchSetId() {
  206. return psId;
  207. }
  208. public ObjectId getCommitId() {
  209. return commitId;
  210. }
  211. public Change getChange() {
  212. checkState(change != null, "getChange() only valid after creating change");
  213. return change;
  214. }
  215. public ChangeInserter setTopic(String topic) {
  216. checkState(change == null, "setTopic(String) only valid before creating change");
  217. this.topic = topic;
  218. return this;
  219. }
  220. public ChangeInserter setMessage(String message) {
  221. this.message = message;
  222. return this;
  223. }
  224. public ChangeInserter setPatchSetDescription(String patchSetDescription) {
  225. this.patchSetDescription = patchSetDescription;
  226. return this;
  227. }
  228. public ChangeInserter setValidate(boolean validate) {
  229. this.validate = validate;
  230. return this;
  231. }
  232. public ChangeInserter setNotify(NotifyHandling notify) {
  233. this.notify = notify;
  234. return this;
  235. }
  236. public ChangeInserter setAccountsToNotify(
  237. ListMultimap<RecipientType, Account.Id> accountsToNotify) {
  238. this.accountsToNotify = checkNotNull(accountsToNotify);
  239. return this;
  240. }
  241. public ChangeInserter setReviewers(Set<Account.Id> reviewers) {
  242. this.reviewers = reviewers;
  243. return this;
  244. }
  245. public ChangeInserter setExtraCC(Set<Account.Id> extraCC) {
  246. this.extraCC = extraCC;
  247. return this;
  248. }
  249. public ChangeInserter setPrivate(boolean isPrivate) {
  250. checkState(change == null, "setPrivate(boolean) only valid before creating change");
  251. this.isPrivate = isPrivate;
  252. return this;
  253. }
  254. public ChangeInserter setWorkInProgress(boolean workInProgress) {
  255. this.workInProgress = workInProgress;
  256. return this;
  257. }
  258. public ChangeInserter setStatus(Change.Status status) {
  259. checkState(change == null, "setStatus(Change.Status) only valid before creating change");
  260. this.status = status;
  261. return this;
  262. }
  263. public ChangeInserter setGroups(List<String> groups) {
  264. checkNotNull(groups, "groups may not be empty");
  265. checkState(patchSet == null, "setGroups(Iterable<String>) only valid before creating change");
  266. this.groups = groups;
  267. return this;
  268. }
  269. public ChangeInserter setFireRevisionCreated(boolean fireRevisionCreated) {
  270. this.fireRevisionCreated = fireRevisionCreated;
  271. return this;
  272. }
  273. public ChangeInserter setSendMail(boolean sendMail) {
  274. this.sendMail = sendMail;
  275. return this;
  276. }
  277. public ChangeInserter setRequestScopePropagator(RequestScopePropagator r) {
  278. this.requestScopePropagator = r;
  279. return this;
  280. }
  281. public ChangeInserter setRevertOf(Change.Id revertOf) {
  282. this.revertOf = revertOf;
  283. return this;
  284. }
  285. public void setPushCertificate(String cert) {
  286. pushCert = cert;
  287. }
  288. public PatchSet getPatchSet() {
  289. checkState(patchSet != null, "getPatchSet() only valid after creating change");
  290. return patchSet;
  291. }
  292. public ChangeInserter setApprovals(Map<String, Short> approvals) {
  293. this.approvals = approvals;
  294. return this;
  295. }
  296. /**
  297. * Set whether to include the new patch set ref update in this update.
  298. *
  299. * <p>If false, the caller is responsible for creating the patch set ref <strong>before</strong>
  300. * executing the containing {@code BatchUpdate}.
  301. *
  302. * <p>Should not be used in new code, as it doesn't result in a single atomic batch ref update for
  303. * code and NoteDb meta refs.
  304. *
  305. * @param updateRef whether to update the ref during {@code updateRepo}.
  306. */
  307. @Deprecated
  308. public ChangeInserter setUpdateRef(boolean updateRef) {
  309. this.updateRef = updateRef;
  310. return this;
  311. }
  312. public ChangeMessage getChangeMessage() {
  313. if (message == null) {
  314. return null;
  315. }
  316. checkState(changeMessage != null, "getChangeMessage() only valid after inserting change");
  317. return changeMessage;
  318. }
  319. public ReceiveCommand getCommand() {
  320. return cmd;
  321. }
  322. @Override
  323. public void updateRepo(RepoContext ctx) throws ResourceConflictException, IOException {
  324. cmd = new ReceiveCommand(ObjectId.zeroId(), commitId, psId.toRefName());
  325. projectState = projectCache.checkedGet(ctx.getProject());
  326. validate(ctx);
  327. if (!updateRef) {
  328. return;
  329. }
  330. ctx.addRefUpdate(cmd);
  331. }
  332. @Override
  333. public boolean updateChange(ChangeContext ctx)
  334. throws RestApiException, OrmException, IOException, PermissionBackendException {
  335. change = ctx.getChange(); // Use defensive copy created by ChangeControl.
  336. ReviewDb db = ctx.getDb();
  337. patchSetInfo =
  338. patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
  339. ctx.getChange().setCurrentPatchSet(patchSetInfo);
  340. ChangeUpdate update = ctx.getUpdate(psId);
  341. update.setChangeId(change.getKey().get());
  342. update.setSubjectForCommit("Create change");
  343. update.setBranch(change.getDest().get());
  344. update.setTopic(change.getTopic());
  345. update.setPsDescription(patchSetDescription);
  346. update.setPrivate(isPrivate);
  347. update.setWorkInProgress(workInProgress);
  348. if (revertOf != null) {
  349. update.setRevertOf(revertOf.get());
  350. }
  351. List<String> newGroups = groups;
  352. if (newGroups.isEmpty()) {
  353. newGroups = GroupCollector.getDefaultGroups(commitId);
  354. }
  355. patchSet =
  356. psUtil.insert(
  357. ctx.getDb(),
  358. ctx.getRevWalk(),
  359. update,
  360. psId,
  361. commitId,
  362. newGroups,
  363. pushCert,
  364. patchSetDescription);
  365. /* TODO: fixStatus is used here because the tests
  366. * (byStatusClosed() in AbstractQueryChangesTest)
  367. * insert changes that are already merged,
  368. * and setStatus may not be used to set the Status to merged
  369. *
  370. * is it possible to make the tests use the merge code path,
  371. * instead of setting the status directly?
  372. */
  373. update.fixStatus(change.getStatus());
  374. Set<Account.Id> reviewersToAdd = new HashSet<>(reviewers);
  375. if (migration.readChanges()) {
  376. approvalsUtil.addCcs(
  377. ctx.getNotes(), update, filterOnChangeVisibility(db, ctx.getNotes(), extraCC));
  378. } else {
  379. reviewersToAdd.addAll(extraCC);
  380. }
  381. LabelTypes labelTypes = projectState.getLabelTypes();
  382. approvalsUtil.addReviewers(
  383. db,
  384. update,
  385. labelTypes,
  386. change,
  387. patchSet,
  388. patchSetInfo,
  389. filterOnChangeVisibility(db, ctx.getNotes(), reviewersToAdd),
  390. Collections.<Account.Id>emptySet());
  391. approvalsUtil.addApprovalsForNewPatchSet(
  392. db, update, labelTypes, patchSet, ctx.getUser(), approvals);
  393. // Check if approvals are changing in with this update. If so, add current user to reviewers.
  394. // Note that this is done separately as addReviewers is filtering out the change owner as
  395. // reviewer which is needed in several other code paths.
  396. if (!approvals.isEmpty()) {
  397. update.putReviewer(ctx.getAccountId(), REVIEWER);
  398. }
  399. if (message != null) {
  400. changeMessage =
  401. ChangeMessagesUtil.newMessage(
  402. patchSet.getId(),
  403. ctx.getUser(),
  404. patchSet.getCreatedOn(),
  405. message,
  406. ChangeMessagesUtil.uploadedPatchSetTag(workInProgress));
  407. cmUtil.addChangeMessage(db, update, changeMessage);
  408. }
  409. return true;
  410. }
  411. private Set<Account.Id> filterOnChangeVisibility(
  412. final ReviewDb db, ChangeNotes notes, Set<Account.Id> accounts) {
  413. return accounts
  414. .stream()
  415. .filter(
  416. accountId -> {
  417. try {
  418. return permissionBackend
  419. .absentUser(accountId)
  420. .change(notes)
  421. .database(db)
  422. .test(ChangePermission.READ)
  423. && projectState.statePermitsRead();
  424. } catch (PermissionBackendException e) {
  425. log.warn(
  426. String.format(
  427. "Failed to check if account %d can see change %d",
  428. accountId.get(), notes.getChangeId().get()),
  429. e);
  430. return false;
  431. }
  432. })
  433. .collect(toSet());
  434. }
  435. @Override
  436. public void postUpdate(Context ctx) throws OrmException, IOException {
  437. if (sendMail && (notify != NotifyHandling.NONE || !accountsToNotify.isEmpty())) {
  438. Runnable sender =
  439. new Runnable() {
  440. @Override
  441. public void run() {
  442. try {
  443. CreateChangeSender cm =
  444. createChangeSenderFactory.create(change.getProject(), change.getId());
  445. cm.setFrom(change.getOwner());
  446. cm.setPatchSet(patchSet, patchSetInfo);
  447. cm.setNotify(notify);
  448. cm.setAccountsToNotify(accountsToNotify);
  449. cm.addReviewers(reviewers);
  450. cm.addExtraCC(extraCC);
  451. cm.send();
  452. } catch (Exception e) {
  453. log.error("Cannot send email for new change " + change.getId(), e);
  454. }
  455. }
  456. @Override
  457. public String toString() {
  458. return "send-email newchange";
  459. }
  460. };
  461. if (requestScopePropagator != null) {
  462. @SuppressWarnings("unused")
  463. Future<?> possiblyIgnoredError =
  464. sendEmailExecutor.submit(requestScopePropagator.wrap(sender));
  465. } else {
  466. sender.run();
  467. }
  468. }
  469. /* For labels that are not set in this operation, show the "current" value
  470. * of 0, and no oldValue as the value was not modified by this operation.
  471. * For labels that are set in this operation, the value was modified, so
  472. * show a transition from an oldValue of 0 to the new value.
  473. */
  474. if (fireRevisionCreated) {
  475. revisionCreated.fire(change, patchSet, ctx.getAccount(), ctx.getWhen(), notify);
  476. if (approvals != null && !approvals.isEmpty()) {
  477. List<LabelType> labels =
  478. projectState.getLabelTypes(change.getDest(), ctx.getUser()).getLabelTypes();
  479. Map<String, Short> allApprovals = new HashMap<>();
  480. Map<String, Short> oldApprovals = new HashMap<>();
  481. for (LabelType lt : labels) {
  482. allApprovals.put(lt.getName(), (short) 0);
  483. oldApprovals.put(lt.getName(), null);
  484. }
  485. for (Map.Entry<String, Short> entry : approvals.entrySet()) {
  486. if (entry.getValue() != 0) {
  487. allApprovals.put(entry.getKey(), entry.getValue());
  488. oldApprovals.put(entry.getKey(), (short) 0);
  489. }
  490. }
  491. commentAdded.fire(
  492. change, patchSet, ctx.getAccount(), null, allApprovals, oldApprovals, ctx.getWhen());
  493. }
  494. }
  495. }
  496. private void validate(RepoContext ctx) throws IOException, ResourceConflictException {
  497. if (!validate) {
  498. return;
  499. }
  500. PermissionBackend.ForRef perm =
  501. permissionBackend.user(ctx.getUser()).project(ctx.getProject()).ref(refName);
  502. try {
  503. try (CommitReceivedEvent event =
  504. new CommitReceivedEvent(
  505. cmd,
  506. projectState.getProject(),
  507. change.getDest().get(),
  508. ctx.getRevWalk().getObjectReader(),
  509. commitId,
  510. ctx.getIdentifiedUser())) {
  511. commitValidatorsFactory
  512. .forGerritCommits(
  513. perm,
  514. new Branch.NameKey(ctx.getProject(), refName),
  515. ctx.getIdentifiedUser(),
  516. new NoSshInfo(),
  517. ctx.getRevWalk(),
  518. change)
  519. .validate(event);
  520. }
  521. } catch (CommitValidationException e) {
  522. throw new ResourceConflictException(e.getFullMessage());
  523. }
  524. }
  525. }