MergeOp: Pre-open all OpenRepos

The openRepo method may throw NoSuchProjectException, which should
fail the merge process and abandon all open changes. Better to do this
in advance rather than lazily after some side effects may have already
happened.

Change-Id: Iba219f83de65f19a329088ff60366e34eebe569e
This commit is contained in:
Dave Borowitz 2016-01-12 15:28:50 -05:00 committed by David Pursehouse
parent 82253c6e51
commit c5b1712ae8
5 changed files with 49 additions and 43 deletions

View File

@ -186,8 +186,6 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
ReviewDb db = dbProvider.get(); ReviewDb db = dbProvider.get();
op.merge(db, change, caller, true); op.merge(db, change, caller, true);
change = db.changes().get(change.getId()); change = db.changes().get(change.getId());
} catch (NoSuchChangeException e) {
throw new OrmException("Submission failed", e);
} }
if (change == null) { if (change == null) {

View File

@ -326,22 +326,27 @@ public class MergeOp implements AutoCloseable {
commits = new CommitStatus(); commits = new CommitStatus();
} }
private OpenRepo openRepo(Project.NameKey project) private OpenRepo getRepo(Project.NameKey project) {
OpenRepo or = openRepos.get(project);
checkState(or != null, "repo not yet opened: %s", project);
return or;
}
private void openRepo(Project.NameKey project)
throws NoSuchProjectException, IOException { throws NoSuchProjectException, IOException {
OpenRepo repo = openRepos.get(project); checkState(!openRepos.containsKey(project),
if (repo == null) { "repo already opened: %s", project);
ProjectState projectState = projectCache.get(project); ProjectState projectState = projectCache.get(project);
if (projectState == null) { if (projectState == null) {
throw new NoSuchProjectException(project); throw new NoSuchProjectException(project);
} }
try { try {
repo = new OpenRepo(repoManager.openRepository(project), projectState); OpenRepo or =
} catch (RepositoryNotFoundException e) { new OpenRepo(repoManager.openRepository(project), projectState);
throw new NoSuchProjectException(project); openRepos.put(project, or);
} } catch (RepositoryNotFoundException e) {
openRepos.put(project, repo); throw new NoSuchProjectException(project);
} }
return repo;
} }
@Override @Override
@ -474,8 +479,7 @@ public class MergeOp implements AutoCloseable {
} }
public void merge(ReviewDb db, Change change, IdentifiedUser caller, public void merge(ReviewDb db, Change change, IdentifiedUser caller,
boolean checkSubmitRules) throws NoSuchChangeException, boolean checkSubmitRules) throws OrmException, ResourceConflictException {
OrmException, ResourceConflictException {
this.caller = caller; this.caller = caller;
updateSubmissionId(change); updateSubmissionId(change);
this.db = db; this.db = db;
@ -523,22 +527,22 @@ public class MergeOp implements AutoCloseable {
} }
private void integrateIntoHistory(ChangeSet cs) private void integrateIntoHistory(ChangeSet cs)
throws IntegrationException, NoSuchChangeException, throws IntegrationException, ResourceConflictException {
ResourceConflictException {
logDebug("Beginning merge attempt on {}", cs); logDebug("Beginning merge attempt on {}", cs);
Map<Branch.NameKey, BranchBatch> toSubmit = new HashMap<>(); Map<Branch.NameKey, BranchBatch> toSubmit = new HashMap<>();
logDebug("Perform the merges"); logDebug("Perform the merges");
try { try {
Multimap<Project.NameKey, Branch.NameKey> br = cs.branchesByProject(); Multimap<Project.NameKey, Branch.NameKey> br = cs.branchesByProject();
Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch(); Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
openRepos(br.keySet());
for (Branch.NameKey branch : cbb.keySet()) { for (Branch.NameKey branch : cbb.keySet()) {
OpenRepo or = openRepo(branch.getParentKey()); OpenRepo or = getRepo(branch.getParentKey());
toSubmit.put(branch, validateChangeList(or, cbb.get(branch))); toSubmit.put(branch, validateChangeList(or, cbb.get(branch)));
} }
failFast(cs); // Done checks that don't involve running submit strategies. failFast(cs); // Done checks that don't involve running submit strategies.
for (Branch.NameKey branch : cbb.keySet()) { for (Branch.NameKey branch : cbb.keySet()) {
OpenRepo or = openRepo(branch.getParentKey()); OpenRepo or = getRepo(branch.getParentKey());
OpenBranch ob = or.getBranch(branch); OpenBranch ob = or.getBranch(branch);
BranchBatch submitting = toSubmit.get(branch); BranchBatch submitting = toSubmit.get(branch);
SubmitStrategy strategy = createStrategy(or, branch, SubmitStrategy strategy = createStrategy(or, branch,
@ -547,7 +551,7 @@ public class MergeOp implements AutoCloseable {
} }
checkMergeStrategyResults(cs, toSubmit.values()); checkMergeStrategyResults(cs, toSubmit.values());
for (Project.NameKey project : br.keySet()) { for (Project.NameKey project : br.keySet()) {
openRepo(project).ins.flush(); getRepo(project).ins.flush();
} }
Set<Branch.NameKey> done = Set<Branch.NameKey> done =
@ -555,7 +559,7 @@ public class MergeOp implements AutoCloseable {
logDebug("Write out the new branch tips"); logDebug("Write out the new branch tips");
SubmoduleOp subOp = subOpProvider.get(); SubmoduleOp subOp = subOpProvider.get();
for (Project.NameKey project : br.keySet()) { for (Project.NameKey project : br.keySet()) {
OpenRepo or = openRepo(project); OpenRepo or = getRepo(project);
for (Branch.NameKey branch : br.get(project)) { for (Branch.NameKey branch : br.get(project)) {
OpenBranch ob = or.getBranch(branch); OpenBranch ob = or.getBranch(branch);
boolean updated = updateBranch(or, branch); boolean updated = updateBranch(or, branch);
@ -574,10 +578,6 @@ public class MergeOp implements AutoCloseable {
checkState(done.equals(cbb.keySet()), "programmer error: did not process" checkState(done.equals(cbb.keySet()), "programmer error: did not process"
+ " all branches in input set.\nExpected: %s\nActual: %s", + " all branches in input set.\nExpected: %s\nActual: %s",
done, cbb.keySet()); done, cbb.keySet());
} catch (NoSuchProjectException noProject) {
logWarn("Project " + noProject.project() + " no longer exists, "
+ "abandoning open changes");
abandonAllOpenChanges(noProject.project());
} catch (OrmException e) { } catch (OrmException e) {
throw new IntegrationException("Cannot query the database", e); throw new IntegrationException("Cannot query the database", e);
} catch (IOException e) { } catch (IOException e) {
@ -602,8 +602,7 @@ public class MergeOp implements AutoCloseable {
private SubmitStrategy createStrategy(OpenRepo or, private SubmitStrategy createStrategy(OpenRepo or,
Branch.NameKey destBranch, SubmitType submitType, Branch.NameKey destBranch, SubmitType submitType,
CodeReviewCommit branchTip) CodeReviewCommit branchTip) throws IntegrationException {
throws IntegrationException, NoSuchProjectException {
return submitStrategyFactory.create(submitType, db, or.repo, or.rw, or.ins, return submitStrategyFactory.create(submitType, db, or.repo, or.rw, or.ins,
or.canMergeFlag, getAlreadyAccepted(or, branchTip), destBranch, caller, or.canMergeFlag, getAlreadyAccepted(or, branchTip), destBranch, caller,
commits); commits);
@ -1209,19 +1208,33 @@ public class MergeOp implements AutoCloseable {
}); });
} }
private void abandonAllOpenChanges(Project.NameKey destProject) private void openRepos(Collection<Project.NameKey> projects)
throws NoSuchChangeException { throws IntegrationException {
for (Project.NameKey project : projects) {
try {
openRepo(project);
} catch (NoSuchProjectException noProject) {
logWarn("Project " + noProject.project() + " no longer exists, "
+ "abandoning open changes");
abandonAllOpenChanges(noProject.project());
} catch (IOException e) {
throw new IntegrationException("Error opening project " + project, e);
}
}
}
private void abandonAllOpenChanges(Project.NameKey destProject) {
try { try {
for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) { for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
abandonOneChange(cd.change()); abandonOneChange(cd.change());
} }
} catch (IOException | OrmException e) { } catch (NoSuchChangeException | IOException | OrmException e) {
logWarn("Cannot abandon changes for deleted project ", e); logWarn("Cannot abandon changes for deleted project " + destProject, e);
} }
} }
private void abandonOneChange(Change change) throws OrmException, private void abandonOneChange(Change change) throws OrmException,
NoSuchChangeException, IOException { NoSuchChangeException, IOException {
db.changes().beginTransaction(change.getId()); db.changes().beginTransaction(change.getId());
//TODO(dborowitz): support InternalUser in ChangeUpdate //TODO(dborowitz): support InternalUser in ChangeUpdate

View File

@ -1830,8 +1830,6 @@ public class ReceiveCommits {
try (MergeOp op = mergeOpProvider.get()) { try (MergeOp op = mergeOpProvider.get()) {
op.merge(db, rsrc.getChange(), op.merge(db, rsrc.getChange(),
changeCtl.getUser().asIdentifiedUser(), false); changeCtl.getUser().asIdentifiedUser(), false);
} catch (NoSuchChangeException e) {
throw new OrmException(e);
} }
addMessage(""); addMessage("");
Change c = db.changes().get(rsrc.getChange().getId()); Change c = db.changes().get(rsrc.getChange().getId());

View File

@ -139,7 +139,8 @@ public abstract class SubmitStrategy {
this.db = db; this.db = db;
this.alreadyAccepted = alreadyAccepted; this.alreadyAccepted = alreadyAccepted;
this.project = projectCache.get(destBranch.getParentKey()); this.project = checkNotNull(projectCache.get(destBranch.getParentKey()),
"project not found: %s", destBranch.getParentKey());
this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag); this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag);
this.mergeUtil = mergeUtilFactory.create(project); this.mergeUtil = mergeUtilFactory.create(project);
} }

View File

@ -21,7 +21,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk; import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.IntegrationException; import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.MergeOp.CommitStatus; import com.google.gerrit.server.git.MergeOp.CommitStatus;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@ -51,12 +50,9 @@ public class SubmitStrategyFactory {
Repository repo, CodeReviewRevWalk rw, ObjectInserter inserter, Repository repo, CodeReviewRevWalk rw, ObjectInserter inserter,
RevFlag canMergeFlag, Set<RevCommit> alreadyAccepted, RevFlag canMergeFlag, Set<RevCommit> alreadyAccepted,
Branch.NameKey destBranch, IdentifiedUser caller, CommitStatus commits) Branch.NameKey destBranch, IdentifiedUser caller, CommitStatus commits)
throws IntegrationException, NoSuchProjectException { throws IntegrationException {
SubmitStrategy.Arguments args = argsFactory.create(destBranch, commits, rw, SubmitStrategy.Arguments args = argsFactory.create(destBranch, commits, rw,
caller, inserter, repo, canMergeFlag, db, alreadyAccepted); caller, inserter, repo, canMergeFlag, db, alreadyAccepted);
if (args.project == null) {
throw new NoSuchProjectException(destBranch.getParentKey());
}
switch (submitType) { switch (submitType) {
case CHERRY_PICK: case CHERRY_PICK:
return new CherryPick(args); return new CherryPick(args);