public void markCleanMerges( final RevWalk rw, final RevFlag canMergeFlag, final CodeReviewCommit mergeTip, final Set<RevCommit> alreadyAccepted) throws IntegrationException { if (mergeTip == null) { // If mergeTip is null here, branchTip was null, indicating a new branch // at the start of the merge process. We also elected to merge nothing, // probably due to missing dependencies. Nothing was cleanly merged. // return; } try { rw.resetRetain(canMergeFlag); rw.sort(RevSort.TOPO); rw.sort(RevSort.REVERSE, true); rw.markStart(mergeTip); for (RevCommit c : alreadyAccepted) { rw.markUninteresting(c); } CodeReviewCommit c; while ((c = (CodeReviewCommit) rw.next()) != null) { if (c.getPatchsetId() != null) { c.setStatusCode(CommitMergeStatus.CLEAN_MERGE); } } } catch (IOException e) { throw new IntegrationException("Cannot mark clean merges", e); } }
private String summarize(RevWalk rw, List<CodeReviewCommit> merged) throws IOException { if (merged.size() == 1) { CodeReviewCommit c = merged.get(0); rw.parseBody(c); return String.format("Merge \"%s\"", c.getShortMessage()); } LinkedHashSet<String> topics = new LinkedHashSet<>(4); for (CodeReviewCommit c : merged) { if (!Strings.isNullOrEmpty(c.change().getTopic())) { topics.add(c.change().getTopic()); } } if (topics.size() == 1) { return String.format("Merge changes from topic '%s'", Iterables.getFirst(topics, null)); } else if (topics.size() > 1) { return String.format("Merge changes from topics '%s'", Joiner.on("', '").join(topics)); } else { return String.format( "Merge changes %s%s", Joiner.on(',') .join( Iterables.transform( Iterables.limit(merged, 5), new Function<CodeReviewCommit, String>() { @Override public String apply(CodeReviewCommit in) { return in.change().getKey().abbreviate(); } })), merged.size() > 5 ? ", ..." : ""); } }
private Account getAccount(CodeReviewCommit codeReviewCommit) { Account account = null; PatchSetApproval submitter = approvalsUtil.getSubmitter(db, codeReviewCommit.notes(), codeReviewCommit.getPatchsetId()); if (submitter != null) { account = accountCache.get(submitter.getAccountId()).getAccount(); } return account; }
private void updateChangeStatus(OpenBranch ob, List<ChangeData> submitted, IdentifiedUser caller) throws ResourceConflictException { List<Change.Id> problemChanges = new ArrayList<>(submitted.size()); logDebug("Updating change status for {} changes", submitted.size()); for (ChangeData cd : submitted) { Change.Id id = cd.getId(); try { Change c = cd.change(); CodeReviewCommit commit = commits.get(id); CommitMergeStatus s = commit != null ? commit.getStatusCode() : null; logDebug("Status of change {} ({}) on {}: {}", id, commit.name(), c.getDest(), s); checkState(s != null, "status not set for change %s; expected to previously fail fast", id); setApproval(cd, caller); ObjectId mergeResultRev = ob.mergeTip != null ? ob.mergeTip.getMergeResults().get(commit) : null; String txt = s.getMessage(); // The change notes must be forcefully reloaded so that the SUBMIT // approval that we added earlier is visible commit.notes().reload(); if (s == CommitMergeStatus.CLEAN_MERGE) { setMerged(c, message(c, txt + getByAccountName(commit)), mergeResultRev); } else if (s == CommitMergeStatus.CLEAN_REBASE || s == CommitMergeStatus.CLEAN_PICK) { setMerged( c, message(c, txt + " as " + commit.name() + getByAccountName(commit)), mergeResultRev); } else if (s == CommitMergeStatus.ALREADY_MERGED) { setMerged(c, null, mergeResultRev); } else { throw new IllegalStateException( "unexpected status " + s + " for change " + c.getId() + "; expected to previously fail fast"); } } catch (OrmException | IOException err) { logWarn("Error updating change status for " + id, err); problemChanges.add(id); } } if (problemChanges.isEmpty()) { return; } StringBuilder msg = new StringBuilder("Error updating status of change"); if (problemChanges.size() == 1) { msg.append(' ').append(problemChanges.iterator().next()); } else { msg.append('s').append(Joiner.on(", ").join(problemChanges)); } throw new ResourceConflictException(msg.toString()); }
private void setMerged(Change c, ChangeMessage msg, ObjectId mergeResultRev) throws OrmException, IOException { logDebug("Setting change {} merged", c.getId()); ChangeUpdate update = null; final PatchSetApproval submitter; PatchSet merged; try { db.changes().beginTransaction(c.getId()); // We must pull the patchset out of commits, because the patchset ID is // modified when using the cherry-pick merge strategy. CodeReviewCommit commit = commits.get(c.getId()); PatchSet.Id mergedId = commit.change().currentPatchSetId(); merged = db.patchSets().get(mergedId); c = setMergedPatchSet(c.getId(), mergedId); submitter = approvalsUtil.getSubmitter(db, commit.notes(), mergedId); ChangeControl control = commit.getControl(); update = updateFactory.create(control, c.getLastUpdatedOn()); // TODO(yyonas): we need to be able to change the author of the message // is not the person for whom the change was made. addMergedMessage // did this in the past. if (msg != null) { cmUtil.addChangeMessage(db, update, msg); } db.commit(); } finally { db.rollback(); } update.commit(); indexer.index(db, c); try { mergedSenderFactory .create(c.getId(), submitter != null ? submitter.getAccountId() : null) .sendAsync(); } catch (Exception e) { log.error("Cannot email merged notification for " + c.getId(), e); } if (submitter != null && mergeResultRev != null) { try { hooks.doChangeMergedHook( c, accountCache.get(submitter.getAccountId()).getAccount(), merged, db, mergeResultRev.name()); } catch (OrmException ex) { logError("Cannot run hook for submitted patch set " + c.getId(), ex); } } }
private static CodeReviewCommit failed( CodeReviewRevWalk rw, RevFlag canMergeFlag, CodeReviewCommit mergeTip, CodeReviewCommit n, CommitMergeStatus failure) throws MissingObjectException, IncorrectObjectTypeException, IOException { rw.resetRetain(canMergeFlag); rw.markStart(n); rw.markUninteresting(mergeTip); CodeReviewCommit failed; while ((failed = rw.next()) != null) { failed.setStatusCode(failure); } return failed; }
public boolean canCherryPick( MergeSorter mergeSorter, Repository repo, CodeReviewCommit mergeTip, CodeReviewRevWalk rw, CodeReviewCommit toMerge) throws IntegrationException { if (mergeTip == null) { // The branch is unborn. Fast-forward is possible. // return true; } if (toMerge.getParentCount() == 0) { // Refuse to merge a root commit into an existing branch, // we cannot obtain a delta for the cherry-pick to apply. // return false; } if (toMerge.getParentCount() == 1) { // If there is only one parent, a cherry-pick can be done by // taking the delta relative to that one parent and redoing // that on the current merge tip. // try { ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo)); m.setBase(toMerge.getParent(0)); return m.merge(mergeTip, toMerge); } catch (IOException e) { throw new IntegrationException("Cannot merge " + toMerge.name(), e); } } // There are multiple parents, so this is a merge commit. We // don't want to cherry-pick the merge as clients can't easily // rebase their history with that merge present and replaced // by an equivalent merge with a different first parent. So // instead behave as though MERGE_IF_NECESSARY was configured. // return canFastForward(mergeSorter, mergeTip, rw, toMerge) || canMerge(mergeSorter, repo, mergeTip, toMerge); }
OpenRepo(Repository repo, ProjectState project) { this.repo = repo; this.project = project; rw = CodeReviewCommit.newRevWalk(repo); rw.sort(RevSort.TOPO); rw.sort(RevSort.COMMIT_TIME_DESC, true); rw.setRetainBody(false); canMergeFlag = rw.newFlag("CAN_MERGE"); ins = repo.newObjectInserter(); branches = Maps.newHashMapWithExpectedSize(1); }
public boolean canMerge( final MergeSorter mergeSorter, final Repository repo, final CodeReviewCommit mergeTip, final CodeReviewCommit toMerge) throws IntegrationException { if (hasMissingDependencies(mergeSorter, toMerge)) { return false; } ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo)); try { return m.merge(new AnyObjectId[] {mergeTip, toMerge}); } catch (LargeObjectException e) { log.warn("Cannot merge due to LargeObjectException: " + toMerge.name()); return false; } catch (NoMergeBaseException e) { return false; } catch (IOException e) { throw new IntegrationException("Cannot merge " + toMerge.name(), e); } }
private void checkMergeStrategyResults(ChangeSet cs, Collection<BranchBatch> batches) throws ResourceConflictException { for (ChangeData cd : flattenBatches(batches)) { Change.Id id = cd.getId(); CodeReviewCommit commit = commits.get(id); CommitMergeStatus s = commit != null ? commit.getStatusCode() : null; if (s == null) { problems.put(id, "internal error: change not processed by merge strategy"); continue; } switch (s) { case CLEAN_MERGE: case CLEAN_REBASE: case CLEAN_PICK: case ALREADY_MERGED: break; // Merge strategy accepted this change. case PATH_CONFLICT: case REBASE_MERGE_CONFLICT: case MANUAL_RECURSIVE_MERGE: case CANNOT_CHERRY_PICK_ROOT: case NOT_FAST_FORWARD: // TODO(dborowitz): Reformat these messages to be more appropriate for // short problem descriptions. problems.put(id, CharMatcher.is('\n').collapseFrom(s.getMessage(), ' ')); break; case MISSING_DEPENDENCY: problems.put(id, "depends on change that was not submitted"); break; default: problems.put(id, "unspecified merge failure: " + s); break; } } failFast(cs); }
public CodeReviewCommit mergeOneCommit( PersonIdent author, PersonIdent committer, Repository repo, CodeReviewRevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag, Branch.NameKey destBranch, CodeReviewCommit mergeTip, CodeReviewCommit n) throws IntegrationException { final ThreeWayMerger m = newThreeWayMerger(repo, inserter); try { if (m.merge(new AnyObjectId[] {mergeTip, n})) { return writeMergeCommit( author, committer, rw, inserter, canMergeFlag, destBranch, mergeTip, m.getResultTreeId(), n); } else { failed(rw, canMergeFlag, mergeTip, n, CommitMergeStatus.PATH_CONFLICT); } } catch (NoMergeBaseException e) { try { failed(rw, canMergeFlag, mergeTip, n, getCommitMergeStatus(e.getReason())); } catch (IOException e2) { throw new IntegrationException("Cannot merge " + n.name(), e); } } catch (IOException e) { throw new IntegrationException("Cannot merge " + n.name(), e); } return mergeTip; }
public CodeReviewCommit writeMergeCommit( PersonIdent author, PersonIdent committer, CodeReviewRevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag, Branch.NameKey destBranch, CodeReviewCommit mergeTip, ObjectId treeId, CodeReviewCommit n) throws IOException, MissingObjectException, IncorrectObjectTypeException { final List<CodeReviewCommit> merged = new ArrayList<>(); rw.resetRetain(canMergeFlag); rw.markStart(n); rw.markUninteresting(mergeTip); CodeReviewCommit crc; while ((crc = rw.next()) != null) { if (crc.getPatchsetId() != null) { merged.add(crc); } } StringBuilder msgbuf = new StringBuilder().append(summarize(rw, merged)); if (!R_HEADS_MASTER.equals(destBranch.get())) { msgbuf.append(" into "); msgbuf.append(destBranch.getShortName()); } if (merged.size() > 1) { msgbuf.append("\n\n* changes:\n"); for (final CodeReviewCommit c : merged) { rw.parseBody(c); msgbuf.append(" "); msgbuf.append(c.getShortMessage()); msgbuf.append("\n"); } } final CommitBuilder mergeCommit = new CommitBuilder(); mergeCommit.setTreeId(treeId); mergeCommit.setParentIds(mergeTip, n); mergeCommit.setAuthor(author); mergeCommit.setCommitter(committer); mergeCommit.setMessage(msgbuf.toString()); CodeReviewCommit mergeResult = rw.parseCommit(commit(inserter, mergeCommit)); mergeResult.setControl(n.getControl()); return mergeResult; }
private void openRepository(Project.NameKey name) throws IntegrationException, NoSuchProjectException { try { repo = repoManager.openRepository(name); } catch (RepositoryNotFoundException notFound) { throw new NoSuchProjectException(name, notFound); } catch (IOException err) { String m = "Error opening repository \"" + name.get() + '"'; throw new IntegrationException(m, err); } rw = CodeReviewCommit.newRevWalk(repo); rw.sort(RevSort.TOPO); rw.sort(RevSort.COMMIT_TIME_DESC, true); rw.setRetainBody(false); canMergeFlag = rw.newFlag("CAN_MERGE"); inserter = repo.newObjectInserter(); }
private RefUpdate updateBranch(Branch.NameKey destBranch, IdentifiedUser caller) throws IntegrationException { RefUpdate branchUpdate = getPendingRefUpdate(destBranch); CodeReviewCommit branchTip = getBranchTip(destBranch); MergeTip mergeTip = mergeTips.get(destBranch); CodeReviewCommit currentTip = mergeTip != null ? mergeTip.getCurrentTip() : null; if (Objects.equals(branchTip, currentTip)) { if (currentTip != null) { logDebug("Branch already at merge tip {}, no update to perform", currentTip.name()); } else { logDebug("Both branch and merge tip are nonexistent, no update"); } return null; } else if (currentTip == null) { logDebug("No merge tip, no update to perform"); return null; } if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) { logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG); try { ProjectConfig cfg = new ProjectConfig(destProject.getProject().getNameKey()); cfg.load(repo, currentTip); } catch (Exception e) { throw new IntegrationException( "Submit would store invalid" + " project configuration " + currentTip.name() + " for " + destProject.getProject().getName(), e); } } branchUpdate.setRefLogIdent( identifiedUserFactory.create(caller.getAccountId()).newRefLogIdent()); branchUpdate.setForceUpdate(false); branchUpdate.setNewObjectId(currentTip); branchUpdate.setRefLogMessage("merged", true); try { RefUpdate.Result result = branchUpdate.update(rw); logDebug( "Update of {}: {}..{} returned status {}", branchUpdate.getName(), branchUpdate.getOldObjectId(), branchUpdate.getNewObjectId(), result); switch (result) { case NEW: case FAST_FORWARD: if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) { tagCache.updateFastForward( destBranch.getParentKey(), branchUpdate.getName(), branchUpdate.getOldObjectId(), currentTip); } if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) { Project p = destProject.getProject(); projectCache.evict(p); destProject = projectCache.get(p.getNameKey()); repoManager.setProjectDescription(p.getNameKey(), p.getDescription()); } return branchUpdate; case LOCK_FAILURE: throw new IntegrationException("Failed to lock " + branchUpdate.getName()); default: throw new IOException(branchUpdate.getResult().name() + '\n' + branchUpdate); } } catch (IOException e) { throw new IntegrationException("Cannot update " + branchUpdate.getName(), e); } }
private ListMultimap<SubmitType, ChangeData> validateChangeList(Collection<ChangeData> submitted) throws IntegrationException { logDebug("Validating {} changes", submitted.size()); ListMultimap<SubmitType, ChangeData> toSubmit = ArrayListMultimap.create(); Map<String, Ref> allRefs; try { allRefs = repo.getRefDatabase().getRefs(ALL); } catch (IOException e) { throw new IntegrationException(e.getMessage(), e); } Set<ObjectId> tips = new HashSet<>(); for (Ref r : allRefs.values()) { tips.add(r.getObjectId()); } for (ChangeData cd : submitted) { ChangeControl ctl; Change chg; try { ctl = cd.changeControl(); // Reload change in case index was stale. chg = cd.reloadChange(); } catch (OrmException e) { throw new IntegrationException("Failed to validate changes", e); } Change.Id changeId = cd.getId(); if (chg.getStatus() != Change.Status.NEW) { logDebug("Change {} is not new: {}", changeId, chg.getStatus()); continue; } if (chg.currentPatchSetId() == null) { logError("Missing current patch set on change " + changeId); commits.put(changeId, CodeReviewCommit.noPatchSet(ctl)); continue; } PatchSet ps; Branch.NameKey destBranch = chg.getDest(); try { ps = cd.currentPatchSet(); } catch (OrmException e) { throw new IntegrationException("Cannot query the database", e); } if (ps == null || ps.getRevision() == null || ps.getRevision().get() == null) { logError("Missing patch set or revision on change " + changeId); commits.put(changeId, CodeReviewCommit.noPatchSet(ctl)); continue; } String idstr = ps.getRevision().get(); ObjectId id; try { id = ObjectId.fromString(idstr); } catch (IllegalArgumentException iae) { logError("Invalid revision on patch set " + ps.getId()); commits.put(changeId, CodeReviewCommit.noPatchSet(ctl)); continue; } if (!tips.contains(id)) { // TODO Technically the proper way to do this test is to use a // RevWalk on "$id --not --all" and test for an empty set. But // that is way slower than looking for a ref directly pointing // at the desired tip. We should always have a ref available. // // TODO this is actually an error, the branch is gone but we // want to merge the issue. We can't safely do that if the // tip is not reachable. // logError( "Revision " + idstr + " of patch set " + ps.getId() + " is not contained in any ref"); commits.put(changeId, CodeReviewCommit.revisionGone(ctl)); continue; } CodeReviewCommit commit; try { commit = rw.parseCommit(id); } catch (IOException e) { logError("Invalid commit " + idstr + " on patch set " + ps.getId(), e); commits.put(changeId, CodeReviewCommit.revisionGone(ctl)); continue; } // TODO(dborowitz): Consider putting ChangeData in CodeReviewCommit. commit.setControl(ctl); commit.setPatchsetId(ps.getId()); commits.put(changeId, commit); MergeValidators mergeValidators = mergeValidatorsFactory.create(); try { mergeValidators.validatePreMerge(repo, commit, destProject, destBranch, ps.getId()); } catch (MergeValidationException mve) { logDebug( "Revision {} of patch set {} failed validation: {}", idstr, ps.getId(), mve.getStatus()); commit.setStatusCode(mve.getStatus()); continue; } SubmitType submitType; submitType = getSubmitType(commit.getControl(), ps); if (submitType == null) { logError("No submit type for revision " + idstr + " of patch set " + ps.getId()); commit.setStatusCode(CommitMergeStatus.NO_SUBMIT_TYPE); continue; } commit.add(canMergeFlag); toSubmit.put(submitType, cd); } logDebug("Submitting on this run: {}", toSubmit); return toSubmit; }
private void updateChangeStatus( List<ChangeData> submitted, Branch.NameKey destBranch, boolean dryRun, IdentifiedUser caller) throws NoSuchChangeException, IntegrationException, ResourceConflictException, OrmException { if (!dryRun) { logDebug("Updating change status for {} changes", submitted.size()); } else { logDebug("Checking change state for {} changes in a dry run", submitted.size()); } MergeTip mergeTip = mergeTips.get(destBranch); for (ChangeData cd : submitted) { Change c = cd.change(); CodeReviewCommit commit = commits.get(c.getId()); CommitMergeStatus s = commit != null ? commit.getStatusCode() : null; if (s == null) { // Shouldn't ever happen, but leave the change alone. We'll pick // it up on the next pass. // logDebug( "Submitted change {} did not appear in set of new commits" + " produced by merge strategy", c.getId()); continue; } if (!dryRun) { try { setApproval(cd, caller); } catch (IOException e) { throw new OrmException(e); } } String txt = s.getMessage(); logDebug("Status of change {} ({}) on {}: {}", c.getId(), commit.name(), c.getDest(), s); // If mergeTip is null merge failed and mergeResultRev will not be read. ObjectId mergeResultRev = mergeTip != null ? mergeTip.getMergeResults().get(commit) : null; // The change notes must be forcefully reloaded so that the SUBMIT // approval that we added earlier is visible commit.notes().reload(); try { ChangeMessage msg; switch (s) { case CLEAN_MERGE: if (!dryRun) { setMerged(c, message(c, txt + getByAccountName(commit)), mergeResultRev); } break; case CLEAN_REBASE: case CLEAN_PICK: if (!dryRun) { setMerged( c, message(c, txt + " as " + commit.name() + getByAccountName(commit)), mergeResultRev); } break; case ALREADY_MERGED: if (!dryRun) { setMerged(c, null, mergeResultRev); } break; case PATH_CONFLICT: case REBASE_MERGE_CONFLICT: case MANUAL_RECURSIVE_MERGE: case CANNOT_CHERRY_PICK_ROOT: case NOT_FAST_FORWARD: case INVALID_PROJECT_CONFIGURATION: case INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_PERMITTED: case INVALID_PROJECT_CONFIGURATION_PLUGIN_VALUE_NOT_EDITABLE: case INVALID_PROJECT_CONFIGURATION_PARENT_PROJECT_NOT_FOUND: case INVALID_PROJECT_CONFIGURATION_ROOT_PROJECT_CANNOT_HAVE_PARENT: case SETTING_PARENT_PROJECT_ONLY_ALLOWED_BY_ADMIN: setNew(commit.notes(), message(c, txt)); throw new ResourceConflictException( "Cannot merge " + commit.name() + "\n" + s.getMessage()); case MISSING_DEPENDENCY: logDebug("Change {} is missing dependency", c.getId()); throw new IntegrationException("Cannot merge " + commit.name() + "\n" + s.getMessage()); case REVISION_GONE: logDebug("Commit not found for change {}", c.getId()); msg = new ChangeMessage( new ChangeMessage.Key(c.getId(), ChangeUtil.messageUUID(db)), null, TimeUtil.nowTs(), c.currentPatchSetId()); msg.setMessage("Failed to read commit for this patch set"); setNew(commit.notes(), msg); throw new IntegrationException(msg.getMessage()); default: msg = message(c, "Unspecified merge failure: " + s.name()); setNew(commit.notes(), msg); throw new IntegrationException(msg.getMessage()); } } catch (OrmException | IOException err) { logWarn("Error updating change status for " + c.getId(), err); } } }
private BranchBatch validateChangeList(OpenRepo or, Collection<ChangeData> submitted) throws IntegrationException { logDebug("Validating {} changes", submitted.size()); List<ChangeData> toSubmit = new ArrayList<>(submitted.size()); Multimap<ObjectId, PatchSet.Id> revisions = getRevisions(or, submitted); SubmitType submitType = null; ChangeData choseSubmitTypeFrom = null; for (ChangeData cd : submitted) { Change.Id changeId = cd.getId(); ChangeControl ctl; Change chg; try { ctl = cd.changeControl(); chg = cd.change(); } catch (OrmException e) { logProblem(changeId, e); continue; } if (chg.currentPatchSetId() == null) { String msg = "Missing current patch set on change"; logError(msg + " " + changeId); problems.put(changeId, msg); continue; } PatchSet ps; Branch.NameKey destBranch = chg.getDest(); try { ps = cd.currentPatchSet(); } catch (OrmException e) { logProblem(changeId, e); continue; } if (ps == null || ps.getRevision() == null || ps.getRevision().get() == null) { logProblem(changeId, "Missing patch set or revision on change"); continue; } String idstr = ps.getRevision().get(); ObjectId id; try { id = ObjectId.fromString(idstr); } catch (IllegalArgumentException e) { logProblem(changeId, e); continue; } if (!revisions.containsEntry(id, ps.getId())) { // TODO this is actually an error, the branch is gone but we // want to merge the issue. We can't safely do that if the // tip is not reachable. // logProblem( changeId, "Revision " + idstr + " of patch set " + ps.getPatchSetId() + " does not match " + ps.getId().toRefName() + " for change"); continue; } CodeReviewCommit commit; try { commit = or.rw.parseCommit(id); } catch (IOException e) { logProblem(changeId, e); continue; } // TODO(dborowitz): Consider putting ChangeData in CodeReviewCommit. commit.setControl(ctl); commit.setPatchsetId(ps.getId()); commits.put(changeId, commit); MergeValidators mergeValidators = mergeValidatorsFactory.create(); try { mergeValidators.validatePreMerge(or.repo, commit, or.project, destBranch, ps.getId()); } catch (MergeValidationException mve) { problems.put(changeId, mve.getMessage()); continue; } SubmitType st = getSubmitType(cd); if (st == null) { logProblem(changeId, "No submit type for change"); continue; } if (submitType == null) { submitType = st; choseSubmitTypeFrom = cd; } else if (st != submitType) { problems.put( changeId, String.format( "Change has submit type %s, but previously chose submit type %s " + "from change %s in the same batch", st, submitType, choseSubmitTypeFrom.getId())); continue; } commit.add(or.canMergeFlag); toSubmit.add(cd); } logDebug("Submitting on this run: {}", toSubmit); return new AutoValue_MergeOp_BranchBatch(submitType, toSubmit); }
/** * Update the submodules in one branch of one repository. * * @param subscriber the branch of the repository which should be changed. * @param updates submodule updates which should be updated to. * @throws SubmoduleException */ private void updateGitlinks( ReviewDb db, Branch.NameKey subscriber, Collection<SubmoduleSubscription> updates) throws SubmoduleException { PersonIdent author = null; Repository pdb = null; RevWalk recRw = null; StringBuilder msgbuf = new StringBuilder("Updated git submodules\n\n"); try { boolean sameAuthorForAll = true; pdb = repoManager.openRepository(subscriber.getParentKey()); if (pdb.getRef(subscriber.get()) == null) { throw new SubmoduleException( "The branch was probably deleted from the subscriber repository"); } DirCache dc = readTree(pdb, pdb.getRef(subscriber.get())); DirCacheEditor ed = dc.editor(); for (SubmoduleSubscription s : updates) { try (Repository subrepo = repoManager.openRepository(s.getSubmodule().getParentKey()); RevWalk rw = CodeReviewCommit.newRevWalk(subrepo)) { Ref ref = subrepo.getRefDatabase().exactRef(s.getSubmodule().get()); if (ref == null) { ed.add(new DeletePath(s.getPath())); continue; } final ObjectId updateTo = ref.getObjectId(); RevCommit newCommit = rw.parseCommit(updateTo); if (author == null) { author = newCommit.getAuthorIdent(); } else if (!author.equals(newCommit.getAuthorIdent())) { sameAuthorForAll = false; } DirCacheEntry dce = dc.getEntry(s.getPath()); ObjectId oldId = null; if (dce != null) { if (!dce.getFileMode().equals(FileMode.GITLINK)) { log.error( "Requested to update gitlink " + s.getPath() + " in " + s.getSubmodule().getParentKey().get() + " but entry " + "doesn't have gitlink file mode."); continue; } oldId = dce.getObjectId(); } else { // This submodule did not exist before. We do not want to add // the full submodule history to the commit message, so omit it. oldId = updateTo; } ed.add( new PathEdit(s.getPath()) { @Override public void apply(DirCacheEntry ent) { ent.setFileMode(FileMode.GITLINK); ent.setObjectId(updateTo); } }); if (verboseSuperProject) { msgbuf.append("Project: " + s.getSubmodule().getParentKey().get()); msgbuf.append(" " + s.getSubmodule().getShortName()); msgbuf.append(" " + updateTo.getName()); msgbuf.append("\n\n"); try { rw.markStart(newCommit); if (oldId != null) { rw.markUninteresting(rw.parseCommit(oldId)); } for (RevCommit c : rw) { msgbuf.append(c.getFullMessage() + "\n\n"); } } catch (IOException e) { logAndThrowSubmoduleException( "Could not perform a revwalk to " + "create superproject commit message", e); } } } } ed.finish(); if (!sameAuthorForAll || author == null) { author = myIdent; } ObjectInserter oi = pdb.newObjectInserter(); ObjectId tree = dc.writeTree(oi); ObjectId currentCommitId = pdb.getRef(subscriber.get()).getObjectId(); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(tree); commit.setParentIds(new ObjectId[] {currentCommitId}); commit.setAuthor(author); commit.setCommitter(myIdent); commit.setMessage(msgbuf.toString()); oi.insert(commit); oi.flush(); ObjectId commitId = oi.idFor(Constants.OBJ_COMMIT, commit.build()); final RefUpdate rfu = pdb.updateRef(subscriber.get()); rfu.setForceUpdate(false); rfu.setNewObjectId(commitId); rfu.setExpectedOldObjectId(currentCommitId); rfu.setRefLogMessage("Submit to " + subscriber.getParentKey().get(), true); switch (rfu.update()) { case NEW: case FAST_FORWARD: gitRefUpdated.fire(subscriber.getParentKey(), rfu); changeHooks.doRefUpdatedHook(subscriber, rfu, account); // TODO since this is performed "in the background" no mail will be // sent to inform users about the updated branch break; default: throw new IOException(rfu.getResult().name()); } recRw = new RevWalk(pdb); // Recursive call: update subscribers of the subscriber updateSuperProjects(db, Sets.newHashSet(subscriber)); } catch (IOException e) { throw new SubmoduleException("Cannot update gitlinks for " + subscriber.get(), e); } finally { if (recRw != null) { recRw.close(); } if (pdb != null) { pdb.close(); } } }
public String createCherryPickCommitMessage(final CodeReviewCommit n) { return createCherryPickCommitMessage(n, n.getControl(), n.getPatchsetId()); }