Submit: Differentiate between conflicts and server errors

If a change cannot be submitted we currently always throw an
IntegrationException that is mapped to ResouceConflictException, which
the calling user sees as '409 Conflict'.

IntegrationException is thrown in 2 cases:
1. the change cannot be submitted due to conflicts
2. the change cannot be submitted due to an error in Gerrit

For 2. we should not return '409 Conflict' but rather '500 Internal
Server Error'. Returning '409 Conflict' for server errors is bad because
our metric and alerting system is not seeing them as errors and we do
leak internal messages to the calling user.

To fix this we introduce an IntegrateConflictException as subclass of
ResourceConflictException that is thrown if there is a conflict on merge
and the user should get a '409 Conflict' response. In case of internal
server errors we throw StorageException now. Since StorageException is a
RuntimeException this cleans up our method signatures quite a bit.

We do have some places now that catch StorageException and then throw a
new StorageException. I left this in place because I think the message
of the newly thrown StorageException is of value.

SubmoduleException is still mapped to '409 Conflict', but for this
exception we have the same issue. This will be addressed in a follow-up
change.

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: Ifdc9d122b54f190ac37a0c3e8e10f9a6b0ec4139
This commit is contained in:
Edwin Kempin
2020-03-06 14:45:35 +01:00
parent fe22b7dfae
commit 9417d69c13
22 changed files with 116 additions and 154 deletions

View File

@@ -56,7 +56,6 @@ import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.submit.ChangeAlreadyMergedException; import com.google.gerrit.server.submit.ChangeAlreadyMergedException;
import com.google.gerrit.server.submit.CommitMergeStatus; import com.google.gerrit.server.submit.CommitMergeStatus;
import com.google.gerrit.server.submit.IntegrationException;
import com.google.gerrit.server.submit.MergeIdenticalTreeException; import com.google.gerrit.server.submit.MergeIdenticalTreeException;
import com.google.gerrit.server.submit.MergeSorter; import com.google.gerrit.server.submit.MergeSorter;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
@@ -185,8 +184,7 @@ public class MergeUtil {
} }
public CodeReviewCommit getFirstFastForward( public CodeReviewCommit getFirstFastForward(
CodeReviewCommit mergeTip, RevWalk rw, List<CodeReviewCommit> toMerge) CodeReviewCommit mergeTip, RevWalk rw, List<CodeReviewCommit> toMerge) {
throws IntegrationException {
for (Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext(); ) { for (Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext(); ) {
try { try {
final CodeReviewCommit n = i.next(); final CodeReviewCommit n = i.next();
@@ -195,19 +193,19 @@ public class MergeUtil {
return n; return n;
} }
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Cannot fast-forward test during merge", e); throw new StorageException("Cannot fast-forward test during merge", e);
} }
} }
return mergeTip; return mergeTip;
} }
public List<CodeReviewCommit> reduceToMinimalMerge( public List<CodeReviewCommit> reduceToMinimalMerge(
MergeSorter mergeSorter, Collection<CodeReviewCommit> toSort) throws IntegrationException { MergeSorter mergeSorter, Collection<CodeReviewCommit> toSort) {
List<CodeReviewCommit> result = new ArrayList<>(); List<CodeReviewCommit> result = new ArrayList<>();
try { try {
result.addAll(mergeSorter.sort(toSort)); result.addAll(mergeSorter.sort(toSort));
} catch (IOException | StorageException e) { } catch (IOException | StorageException e) {
throw new IntegrationException("Branch head sorting failed", e); throw new StorageException("Branch head sorting failed", e);
} }
result.sort(CodeReviewCommit.ORDER); result.sort(CodeReviewCommit.ORDER);
return result; return result;
@@ -673,8 +671,10 @@ public class MergeUtil {
} }
public boolean canMerge( public boolean canMerge(
MergeSorter mergeSorter, Repository repo, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) MergeSorter mergeSorter,
throws IntegrationException { Repository repo,
CodeReviewCommit mergeTip,
CodeReviewCommit toMerge) {
if (hasMissingDependencies(mergeSorter, toMerge)) { if (hasMissingDependencies(mergeSorter, toMerge)) {
return false; return false;
} }
@@ -687,7 +687,7 @@ public class MergeUtil {
} catch (NoMergeBaseException e) { } catch (NoMergeBaseException e) {
return false; return false;
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Cannot merge " + toMerge.name(), e); throw new StorageException("Cannot merge " + toMerge.name(), e);
} }
} }
@@ -695,8 +695,7 @@ public class MergeUtil {
MergeSorter mergeSorter, MergeSorter mergeSorter,
CodeReviewCommit mergeTip, CodeReviewCommit mergeTip,
CodeReviewRevWalk rw, CodeReviewRevWalk rw,
CodeReviewCommit toMerge) CodeReviewCommit toMerge) {
throws IntegrationException {
if (hasMissingDependencies(mergeSorter, toMerge)) { if (hasMissingDependencies(mergeSorter, toMerge)) {
return false; return false;
} }
@@ -706,7 +705,7 @@ public class MergeUtil {
|| rw.isMergedInto(mergeTip, toMerge) || rw.isMergedInto(mergeTip, toMerge)
|| rw.isMergedInto(toMerge, mergeTip); || rw.isMergedInto(toMerge, mergeTip);
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Cannot fast-forward test during merge", e); throw new StorageException("Cannot fast-forward test during merge", e);
} }
} }
@@ -715,8 +714,7 @@ public class MergeUtil {
Repository repo, Repository repo,
CodeReviewCommit mergeTip, CodeReviewCommit mergeTip,
CodeReviewRevWalk rw, CodeReviewRevWalk rw,
CodeReviewCommit toMerge) CodeReviewCommit toMerge) {
throws IntegrationException {
if (mergeTip == null) { if (mergeTip == null) {
// The branch is unborn. Fast-forward is possible. // The branch is unborn. Fast-forward is possible.
// //
@@ -740,7 +738,7 @@ public class MergeUtil {
m.setBase(toMerge.getParent(0)); m.setBase(toMerge.getParent(0));
return m.merge(mergeTip, toMerge); return m.merge(mergeTip, toMerge);
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException( throw new StorageException(
String.format( String.format(
"Cannot merge commit %s with mergetip %s", toMerge.name(), mergeTip.name()), "Cannot merge commit %s with mergetip %s", toMerge.name(), mergeTip.name()),
e); e);
@@ -757,12 +755,11 @@ public class MergeUtil {
|| canMerge(mergeSorter, repo, mergeTip, toMerge); || canMerge(mergeSorter, repo, mergeTip, toMerge);
} }
public boolean hasMissingDependencies(MergeSorter mergeSorter, CodeReviewCommit toMerge) public boolean hasMissingDependencies(MergeSorter mergeSorter, CodeReviewCommit toMerge) {
throws IntegrationException {
try { try {
return !mergeSorter.sort(Collections.singleton(toMerge)).contains(toMerge); return !mergeSorter.sort(Collections.singleton(toMerge)).contains(toMerge);
} catch (IOException | StorageException e) { } catch (IOException | StorageException e) {
throw new IntegrationException("Branch head sorting failed", e); throw new StorageException("Branch head sorting failed", e);
} }
} }
@@ -775,7 +772,7 @@ public class MergeUtil {
BranchNameKey destBranch, BranchNameKey destBranch,
CodeReviewCommit mergeTip, CodeReviewCommit mergeTip,
CodeReviewCommit n) CodeReviewCommit n)
throws IntegrationException, InvalidMergeStrategyException { throws InvalidMergeStrategyException {
ThreeWayMerger m = newThreeWayMerger(inserter, repoConfig); ThreeWayMerger m = newThreeWayMerger(inserter, repoConfig);
try { try {
if (m.merge(mergeTip, n)) { if (m.merge(mergeTip, n)) {
@@ -788,10 +785,10 @@ public class MergeUtil {
failed(rw, mergeTip, n, getCommitMergeStatus(e.getReason())); failed(rw, mergeTip, n, getCommitMergeStatus(e.getReason()));
} catch (IOException e2) { } catch (IOException e2) {
logger.atSevere().withCause(e2).log("Failed to set merge failure status for " + n.name()); logger.atSevere().withCause(e2).log("Failed to set merge failure status for " + n.name());
throw new IntegrationException("Cannot merge " + n.name(), e); throw new StorageException("Cannot merge " + n.name(), e);
} }
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Cannot merge " + n.name(), e); throw new StorageException("Cannot merge " + n.name(), e);
} }
return mergeTip; return mergeTip;
} }
@@ -964,8 +961,7 @@ public class MergeUtil {
} }
public void markCleanMerges( public void markCleanMerges(
RevWalk rw, RevFlag canMergeFlag, CodeReviewCommit mergeTip, Set<RevCommit> alreadyAccepted) RevWalk rw, RevFlag canMergeFlag, CodeReviewCommit mergeTip, Set<RevCommit> alreadyAccepted) {
throws IntegrationException {
if (mergeTip == null) { if (mergeTip == null) {
// If mergeTip is null here, branchTip was null, indicating a new branch // 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, // at the start of the merge process. We also elected to merge nothing,
@@ -993,7 +989,7 @@ public class MergeUtil {
} }
} }
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Cannot mark clean merges", e); throw new StorageException("Cannot mark clean merges", e);
} }
} }
@@ -1003,8 +999,7 @@ public class MergeUtil {
RevFlag canMergeFlag, RevFlag canMergeFlag,
CodeReviewCommit oldTip, CodeReviewCommit oldTip,
CodeReviewCommit mergeTip, CodeReviewCommit mergeTip,
Iterable<Change.Id> alreadyMerged) Iterable<Change.Id> alreadyMerged) {
throws IntegrationException {
if (mergeTip == null) { if (mergeTip == null) {
return expected; return expected;
} }
@@ -1035,7 +1030,7 @@ public class MergeUtil {
} }
return Sets.difference(expected, found); return Sets.difference(expected, found);
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Cannot check if changes were merged", e); throw new StorageException("Cannot check if changes were merged", e);
} }
} }

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.server.git.validators;
import com.google.gerrit.entities.Project; import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.validators.OnSubmitValidationListener.Arguments; import com.google.gerrit.server.git.validators.OnSubmitValidationListener.Arguments;
import com.google.gerrit.server.plugincontext.PluginSetContext; import com.google.gerrit.server.plugincontext.PluginSetContext;
import com.google.gerrit.server.submit.IntegrationException; import com.google.gerrit.server.submit.IntegrationConflictException;
import com.google.gerrit.server.update.ChainedReceiveCommands; import com.google.gerrit.server.update.ChainedReceiveCommands;
import com.google.gerrit.server.validators.ValidationException; import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -38,12 +38,12 @@ public class OnSubmitValidators {
public void validate( public void validate(
Project.NameKey project, ObjectReader objectReader, ChainedReceiveCommands commands) Project.NameKey project, ObjectReader objectReader, ChainedReceiveCommands commands)
throws IntegrationException { throws IntegrationConflictException {
try (RevWalk rw = new RevWalk(objectReader)) { try (RevWalk rw = new RevWalk(objectReader)) {
Arguments args = new Arguments(project, rw, commands); Arguments args = new Arguments(project, rw, commands);
listeners.runEach(l -> l.preBranchUpdate(args), ValidationException.class); listeners.runEach(l -> l.preBranchUpdate(args), ValidationException.class);
} catch (ValidationException e) { } catch (ValidationException e) {
throw new IntegrationException(e.getMessage(), e); throw new IntegrationConflictException(e.getMessage(), e);
} }
} }
} }

View File

@@ -35,7 +35,6 @@ import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments; import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gerrit.server.submit.IntegrationException;
import com.google.gerrit.server.submit.SubmitDryRun; import com.google.gerrit.server.submit.SubmitDryRun;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -163,7 +162,7 @@ public class ConflictsPredicate {
args.conflictsCache.put(conflictsKey, conflicts); args.conflictsCache.put(conflictsKey, conflicts);
return conflicts; return conflicts;
} }
} catch (IntegrationException | NoSuchProjectException | StorageException | IOException e) { } catch (NoSuchProjectException | StorageException | IOException e) {
ObjectId finalOther = other; ObjectId finalOther = other;
warnWithOccasionalStackTrace( warnWithOccasionalStackTrace(
e, e,
@@ -181,8 +180,7 @@ public class ConflictsPredicate {
return 5; return 5;
} }
private Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw) private Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw) {
throws IntegrationException {
try { try {
Set<RevCommit> accepted = new HashSet<>(); Set<RevCommit> accepted = new HashSet<>();
SubmitDryRun.addCommits(changeDataCache.getAlreadyAccepted(repo), rw, accepted); SubmitDryRun.addCommits(changeDataCache.getAlreadyAccepted(repo), rw, accepted);
@@ -192,7 +190,7 @@ public class ConflictsPredicate {
} }
return accepted; return accepted;
} catch (StorageException | IOException e) { } catch (StorageException | IOException e) {
throw new IntegrationException("Failed to determine already accepted commits.", e); throw new StorageException("Failed to determine already accepted commits.", e);
} }
} }
} }

View File

@@ -38,7 +38,6 @@ import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.submit.IntegrationException;
import com.google.gerrit.server.update.UpdateException; import com.google.gerrit.server.update.UpdateException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -103,7 +102,7 @@ public class CherryPick
return Response.ok(changeInfo); return Response.ok(changeInfo);
} catch (InvalidChangeOperationException e) { } catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage()); throw new BadRequestException(e.getMessage());
} catch (IntegrationException | NoSuchChangeException e) { } catch (NoSuchChangeException e) {
throw new ResourceConflictException(e.getMessage()); throw new ResourceConflictException(e.getMessage());
} }
} }

View File

@@ -56,7 +56,7 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.submit.IntegrationException; import com.google.gerrit.server.submit.IntegrationConflictException;
import com.google.gerrit.server.submit.MergeIdenticalTreeException; import com.google.gerrit.server.submit.MergeIdenticalTreeException;
import com.google.gerrit.server.update.BatchUpdate; import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException; import com.google.gerrit.server.update.UpdateException;
@@ -155,15 +155,14 @@ public class CherryPickChange {
* @throws IOException Unable to open repository or read from the database. * @throws IOException Unable to open repository or read from the database.
* @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
* key exist in the branch. * key exist in the branch.
* @throws IntegrationException Merge conflict or trees are identical after cherry pick.
* @throws UpdateException Problem updating the database using batchUpdateFactory. * @throws UpdateException Problem updating the database using batchUpdateFactory.
* @throws RestApiException Error such as invalid SHA1 * @throws RestApiException Error such as invalid SHA1
* @throws ConfigInvalidException Can't find account to notify. * @throws ConfigInvalidException Can't find account to notify.
* @throws NoSuchProjectException Can't find project state. * @throws NoSuchProjectException Can't find project state.
*/ */
public Result cherryPick(Change change, PatchSet patch, CherryPickInput input, BranchNameKey dest) public Result cherryPick(Change change, PatchSet patch, CherryPickInput input, BranchNameKey dest)
throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException, throws IOException, InvalidChangeOperationException, UpdateException, RestApiException,
RestApiException, ConfigInvalidException, NoSuchProjectException { ConfigInvalidException, NoSuchProjectException {
return cherryPick( return cherryPick(
change, change,
change.getProject(), change.getProject(),
@@ -193,7 +192,6 @@ public class CherryPickChange {
* @throws IOException Unable to open repository or read from the database. * @throws IOException Unable to open repository or read from the database.
* @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
* key exist in the branch. * key exist in the branch.
* @throws IntegrationException Merge conflict or trees are identical after cherry pick.
* @throws UpdateException Problem updating the database using batchUpdateFactory. * @throws UpdateException Problem updating the database using batchUpdateFactory.
* @throws RestApiException Error such as invalid SHA1 * @throws RestApiException Error such as invalid SHA1
* @throws ConfigInvalidException Can't find account to notify. * @throws ConfigInvalidException Can't find account to notify.
@@ -205,8 +203,8 @@ public class CherryPickChange {
ObjectId sourceCommit, ObjectId sourceCommit,
CherryPickInput input, CherryPickInput input,
BranchNameKey dest) BranchNameKey dest)
throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException, throws IOException, InvalidChangeOperationException, UpdateException, RestApiException,
RestApiException, ConfigInvalidException, NoSuchProjectException { ConfigInvalidException, NoSuchProjectException {
return cherryPick( return cherryPick(
sourceChange, sourceChange,
project, project,
@@ -251,7 +249,6 @@ public class CherryPickChange {
* @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same * @throws InvalidChangeOperationException Parent or branch don't exist, or two changes with same
* key exist in the branch. Also thrown when idForNewChange is not null but cherry-pick only * key exist in the branch. Also thrown when idForNewChange is not null but cherry-pick only
* creates a new patchset rather than a new change. * creates a new patchset rather than a new change.
* @throws IntegrationException Merge conflict or trees are identical after cherry pick.
* @throws UpdateException Problem updating the database using batchUpdateFactory. * @throws UpdateException Problem updating the database using batchUpdateFactory.
* @throws RestApiException Error such as invalid SHA1 * @throws RestApiException Error such as invalid SHA1
* @throws ConfigInvalidException Can't find account to notify. * @throws ConfigInvalidException Can't find account to notify.
@@ -270,8 +267,8 @@ public class CherryPickChange {
@Nullable ObjectId changeIdForNewChange, @Nullable ObjectId changeIdForNewChange,
@Nullable Change.Id idForNewChange, @Nullable Change.Id idForNewChange,
@Nullable String groupName) @Nullable String groupName)
throws IOException, InvalidChangeOperationException, IntegrationException, UpdateException, throws IOException, InvalidChangeOperationException, UpdateException, RestApiException,
RestApiException, ConfigInvalidException, NoSuchProjectException { ConfigInvalidException, NoSuchProjectException {
IdentifiedUser identifiedUser = user.get(); IdentifiedUser identifiedUser = user.get();
try (Repository git = gitManager.openRepository(project); try (Repository git = gitManager.openRepository(project);
@@ -408,7 +405,7 @@ public class CherryPickChange {
return Result.create(changeId, cherryPickCommit.getFilesWithGitConflicts()); return Result.create(changeId, cherryPickCommit.getFilesWithGitConflicts());
} }
} catch (MergeIdenticalTreeException | MergeConflictException e) { } catch (MergeIdenticalTreeException | MergeConflictException e) {
throw new IntegrationException("Cherry pick failed: " + e.getMessage()); throw new IntegrationConflictException("Cherry pick failed: " + e.getMessage());
} }
} }
} }

View File

@@ -21,7 +21,6 @@ import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.CherryPickInput; import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -34,7 +33,6 @@ import com.google.gerrit.server.project.CommitResource;
import com.google.gerrit.server.project.ContributorAgreementsChecker; import com.google.gerrit.server.project.ContributorAgreementsChecker;
import com.google.gerrit.server.project.InvalidChangeOperationException; import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.submit.IntegrationException;
import com.google.gerrit.server.update.UpdateException; import com.google.gerrit.server.update.UpdateException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -99,8 +97,6 @@ public class CherryPickCommit implements RestModifyView<CommitResource, CherryPi
return Response.ok(changeInfo); return Response.ok(changeInfo);
} catch (InvalidChangeOperationException e) { } catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage()); throw new BadRequestException(e.getMessage());
} catch (IntegrationException e) {
throw new ResourceConflictException(e.getMessage());
} }
} }
} }

View File

@@ -45,8 +45,7 @@ public class CherryPick extends SubmitStrategy {
} }
@Override @Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) {
throws IntegrationException {
List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge); List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge);
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size()); List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
boolean first = true; boolean first = true;
@@ -90,7 +89,7 @@ public class CherryPick extends SubmitStrategy {
@Override @Override
protected void updateRepoImpl(RepoContext ctx) protected void updateRepoImpl(RepoContext ctx)
throws IntegrationException, IOException, MethodNotAllowedException { throws IntegrationConflictException, IOException, MethodNotAllowedException {
// If there is only one parent, a cherry-pick can be done by taking the // 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 // delta relative to that one parent and redoing that on the current merge
// tip. // tip.
@@ -181,7 +180,7 @@ public class CherryPick extends SubmitStrategy {
} }
@Override @Override
public void updateRepoImpl(RepoContext ctx) throws IntegrationException, IOException { public void updateRepoImpl(RepoContext ctx) throws IntegrationConflictException, IOException {
if (args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)) { if (args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)) {
// One or more dependencies were not met. The status was already marked // One or more dependencies were not met. The status was already marked
// on the commit so we have nothing further to perform at this time. // on the commit so we have nothing further to perform at this time.
@@ -217,8 +216,7 @@ public class CherryPick extends SubmitStrategy {
} }
static boolean dryRun( static boolean dryRun(
SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) {
throws IntegrationException {
return args.mergeUtil.canCherryPick(args.mergeSorter, args.repo, mergeTip, args.rw, toMerge); return args.mergeUtil.canCherryPick(args.mergeSorter, args.repo, mergeTip, args.rw, toMerge);
} }
} }

View File

@@ -26,8 +26,7 @@ public class FastForwardOnly extends SubmitStrategy {
} }
@Override @Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) {
throws IntegrationException {
List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge); List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size()); List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
CodeReviewCommit newTipCommit = CodeReviewCommit newTipCommit =
@@ -53,8 +52,7 @@ public class FastForwardOnly extends SubmitStrategy {
} }
static boolean dryRun( static boolean dryRun(
SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) {
throws IntegrationException {
return args.mergeUtil.canFastForward(args.mergeSorter, mergeTip, args.rw, toMerge); return args.mergeUtil.canFastForward(args.mergeSorter, mergeTip, args.rw, toMerge);
} }
} }

View File

@@ -26,7 +26,7 @@ class FastForwardOp extends SubmitStrategyOp {
} }
@Override @Override
protected void updateRepoImpl(RepoContext ctx) throws IntegrationException { protected void updateRepoImpl(RepoContext ctx) {
if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT) if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)
&& toMerge.getParentCount() > 0 && toMerge.getParentCount() > 0
&& toMerge.getTree().equals(toMerge.getParent(0).getTree())) { && toMerge.getTree().equals(toMerge.getParent(0).getTree())) {

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2008 The Android Open Source Project // Copyright (C) 2020 The Android Open Source Project
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -14,19 +14,23 @@
package com.google.gerrit.server.submit; package com.google.gerrit.server.submit;
/** Indicates an integration operation (see {@link MergeOp}) failed. */ import com.google.gerrit.extensions.restapi.ResourceConflictException;
public class IntegrationException extends Exception {
/**
* Exception to be thrown if integrating (aka merging) a change into the destination branch is not
* possible due to conflicts.
*
* <p>Throwing this exception results in a {@code 409 Conflict} response to the calling user. The
* exception message is returned as error message to the user.
*/
public class IntegrationConflictException extends ResourceConflictException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public IntegrationException(String msg) { public IntegrationConflictException(String msg) {
super(msg); super(msg);
} }
public IntegrationException(Throwable why) { public IntegrationConflictException(String msg, Throwable why) {
super(why);
}
public IntegrationException(String msg, Throwable why) {
super(msg, why); super(msg, why);
} }
} }

View File

@@ -25,8 +25,7 @@ public class MergeAlways extends SubmitStrategy {
} }
@Override @Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) {
throws IntegrationException {
List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge); List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size()); List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
if (args.mergeTip.getInitialTip() == null && !sorted.isEmpty()) { if (args.mergeTip.getInitialTip() == null && !sorted.isEmpty()) {
@@ -43,8 +42,7 @@ public class MergeAlways extends SubmitStrategy {
} }
static boolean dryRun( static boolean dryRun(
SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) {
throws IntegrationException {
return args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip, toMerge); return args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip, toMerge);
} }
} }

View File

@@ -25,8 +25,7 @@ public class MergeIfNecessary extends SubmitStrategy {
} }
@Override @Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) {
throws IntegrationException {
List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge); List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size()); List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
@@ -48,8 +47,7 @@ public class MergeIfNecessary extends SubmitStrategy {
} }
static boolean dryRun( static boolean dryRun(
SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) {
throws IntegrationException {
return args.mergeUtil.canFastForward(args.mergeSorter, mergeTip, args.rw, toMerge) return args.mergeUtil.canFastForward(args.mergeSorter, mergeTip, args.rw, toMerge)
|| args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip, toMerge); || args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip, toMerge);
} }

View File

@@ -28,7 +28,7 @@ class MergeOneOp extends SubmitStrategyOp {
} }
@Override @Override
public void updateRepoImpl(RepoContext ctx) throws IntegrationException, IOException { public void updateRepoImpl(RepoContext ctx) throws IntegrationConflictException, IOException {
PersonIdent caller = PersonIdent caller =
ctx.getIdentifiedUser() ctx.getIdentifiedUser()
.newCommitterIdent(args.serverIdent.getWhen(), args.serverIdent.getTimeZone()); .newCommitterIdent(args.serverIdent.getWhen(), args.serverIdent.getTimeZone());

View File

@@ -506,12 +506,7 @@ public class MergeOp implements AutoCloseable {
logger.atFine().log("Bypassing submit rules"); logger.atFine().log("Bypassing submit rules");
bypassSubmitRules(cs, isRetry); bypassSubmitRules(cs, isRetry);
} }
try {
integrateIntoHistory(cs); integrateIntoHistory(cs);
} catch (IntegrationException e) {
logger.atWarning().withCause(e).log("Error from integrateIntoHistory");
throw new ResourceConflictException(e.getMessage(), e);
}
return null; return null;
}) })
.listener(retryTracker) .listener(retryTracker)
@@ -585,8 +580,7 @@ public class MergeOp implements AutoCloseable {
} }
} }
private void integrateIntoHistory(ChangeSet cs) private void integrateIntoHistory(ChangeSet cs) throws RestApiException, UpdateException {
throws IntegrationException, RestApiException, UpdateException {
checkArgument(!cs.furtherHiddenChanges(), "cannot integrate hidden changes into history"); checkArgument(!cs.furtherHiddenChanges(), "cannot integrate hidden changes into history");
logger.atFine().log("Beginning merge attempt on %s", cs); logger.atFine().log("Beginning merge attempt on %s", cs);
Map<BranchNameKey, BranchBatch> toSubmit = new HashMap<>(); Map<BranchNameKey, BranchBatch> toSubmit = new HashMap<>();
@@ -595,7 +589,7 @@ public class MergeOp implements AutoCloseable {
try { try {
cbb = cs.changesByBranch(); cbb = cs.changesByBranch();
} catch (StorageException e) { } catch (StorageException e) {
throw new IntegrationException("Error reading changes to submit", e); throw new StorageException("Error reading changes to submit", e);
} }
Set<BranchNameKey> branches = cbb.keySet(); Set<BranchNameKey> branches = cbb.keySet();
@@ -626,8 +620,10 @@ public class MergeOp implements AutoCloseable {
} }
} catch (NoSuchProjectException e) { } catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(e.getMessage()); throw new ResourceNotFoundException(e.getMessage());
} catch (IOException | SubmoduleException e) { } catch (IOException e) {
throw new IntegrationException(e); throw new StorageException(e);
} catch (SubmoduleException e) {
throw new IntegrationConflictException(e.getMessage(), e);
} catch (UpdateException e) { } catch (UpdateException e) {
if (e.getCause() instanceof LockFailureException) { if (e.getCause() instanceof LockFailureException) {
// Lock failures are a special case: RetryHelper depends on this specific causal chain in // Lock failures are a special case: RetryHelper depends on this specific causal chain in
@@ -645,13 +641,10 @@ public class MergeOp implements AutoCloseable {
// //
// If you happen across one of these, the correct fix is to convert the // If you happen across one of these, the correct fix is to convert the
// inner IntegrationException to a ResourceConflictException. // inner IntegrationException to a ResourceConflictException.
String msg; if (e.getCause() instanceof IntegrationConflictException) {
if (e.getCause() instanceof IntegrationException) { throw (IntegrationConflictException) e.getCause();
msg = e.getCause().getMessage();
} else {
msg = genericMergeError(cs);
} }
throw new IntegrationException(msg, e); throw new StorageException(genericMergeError(cs), e);
} }
} }
@@ -665,7 +658,7 @@ public class MergeOp implements AutoCloseable {
private List<SubmitStrategy> getSubmitStrategies( private List<SubmitStrategy> getSubmitStrategies(
Map<BranchNameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp, boolean dryrun) Map<BranchNameKey, BranchBatch> toSubmit, SubmoduleOp submoduleOp, boolean dryrun)
throws IntegrationException, NoSuchProjectException, IOException { throws IntegrationConflictException, NoSuchProjectException, IOException {
List<SubmitStrategy> strategies = new ArrayList<>(); List<SubmitStrategy> strategies = new ArrayList<>();
Set<BranchNameKey> allBranches = submoduleOp.getBranchesInOrder(); Set<BranchNameKey> allBranches = submoduleOp.getBranchesInOrder();
Set<CodeReviewCommit> allCommits = Set<CodeReviewCommit> allCommits =
@@ -711,8 +704,7 @@ public class MergeOp implements AutoCloseable {
return strategies; return strategies;
} }
private Set<RevCommit> getAlreadyAccepted(OpenRepo or, CodeReviewCommit branchTip) private Set<RevCommit> getAlreadyAccepted(OpenRepo or, CodeReviewCommit branchTip) {
throws IntegrationException {
Set<RevCommit> alreadyAccepted = new HashSet<>(); Set<RevCommit> alreadyAccepted = new HashSet<>();
if (branchTip != null) { if (branchTip != null) {
@@ -731,7 +723,7 @@ public class MergeOp implements AutoCloseable {
} }
} }
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Failed to determine already accepted commits.", e); throw new StorageException("Failed to determine already accepted commits.", e);
} }
logger.atFine().log("Found %d existing heads: %s", alreadyAccepted.size(), alreadyAccepted); logger.atFine().log("Found %d existing heads: %s", alreadyAccepted.size(), alreadyAccepted);
@@ -746,8 +738,7 @@ public class MergeOp implements AutoCloseable {
abstract Set<CodeReviewCommit> commits(); abstract Set<CodeReviewCommit> commits();
} }
private BranchBatch validateChangeList(OpenRepo or, Collection<ChangeData> submitted) private BranchBatch validateChangeList(OpenRepo or, Collection<ChangeData> submitted) {
throws IntegrationException {
logger.atFine().log("Validating %d changes", submitted.size()); logger.atFine().log("Validating %d changes", submitted.size());
Set<CodeReviewCommit> toSubmit = new LinkedHashSet<>(submitted.size()); Set<CodeReviewCommit> toSubmit = new LinkedHashSet<>(submitted.size());
SetMultimap<ObjectId, PatchSet.Id> revisions = getRevisions(or, submitted); SetMultimap<ObjectId, PatchSet.Id> revisions = getRevisions(or, submitted);
@@ -862,8 +853,7 @@ public class MergeOp implements AutoCloseable {
return new AutoValue_MergeOp_BranchBatch(submitType, toSubmit); return new AutoValue_MergeOp_BranchBatch(submitType, toSubmit);
} }
private SetMultimap<ObjectId, PatchSet.Id> getRevisions(OpenRepo or, Collection<ChangeData> cds) private SetMultimap<ObjectId, PatchSet.Id> getRevisions(OpenRepo or, Collection<ChangeData> cds) {
throws IntegrationException {
try { try {
List<String> refNames = new ArrayList<>(cds.size()); List<String> refNames = new ArrayList<>(cds.size());
for (ChangeData cd : cds) { for (ChangeData cd : cds) {
@@ -883,7 +873,7 @@ public class MergeOp implements AutoCloseable {
} }
return revisions; return revisions;
} catch (IOException | StorageException e) { } catch (IOException | StorageException e) {
throw new IntegrationException("Failed to validate changes", e); throw new StorageException("Failed to validate changes", e);
} }
} }
@@ -892,14 +882,14 @@ public class MergeOp implements AutoCloseable {
return str.isOk() ? str.type : null; return str.isOk() ? str.type : null;
} }
private OpenRepo openRepo(Project.NameKey project) throws IntegrationException { private OpenRepo openRepo(Project.NameKey project) {
try { try {
return orm.getRepo(project); return orm.getRepo(project);
} catch (NoSuchProjectException e) { } catch (NoSuchProjectException e) {
logger.atWarning().log("Project %s no longer exists, abandoning open changes.", project); logger.atWarning().log("Project %s no longer exists, abandoning open changes.", project);
abandonAllOpenChangeForDeletedProject(project); abandonAllOpenChangeForDeletedProject(project);
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Error opening project " + project, e); throw new StorageException("Error opening project " + project, e);
} }
return null; return null;
} }

View File

@@ -22,6 +22,7 @@ import com.google.common.collect.Maps;
import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project; import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames; import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.NotifyResolver; import com.google.gerrit.server.change.NotifyResolver;
import com.google.gerrit.server.git.CodeReviewCommit; import com.google.gerrit.server.git.CodeReviewCommit;
@@ -84,7 +85,7 @@ public class MergeOpRepoManager implements AutoCloseable {
branches = Maps.newHashMapWithExpectedSize(1); branches = Maps.newHashMapWithExpectedSize(1);
} }
OpenBranch getBranch(BranchNameKey branch) throws IntegrationException { OpenBranch getBranch(BranchNameKey branch) throws IntegrationConflictException {
OpenBranch ob = branches.get(branch); OpenBranch ob = branches.get(branch);
if (ob == null) { if (ob == null) {
ob = new OpenBranch(this, branch); ob = new OpenBranch(this, branch);
@@ -133,7 +134,7 @@ public class MergeOpRepoManager implements AutoCloseable {
final CodeReviewCommit oldTip; final CodeReviewCommit oldTip;
MergeTip mergeTip; MergeTip mergeTip;
OpenBranch(OpenRepo or, BranchNameKey name) throws IntegrationException { OpenBranch(OpenRepo or, BranchNameKey name) throws IntegrationConflictException {
try { try {
Ref ref = or.getRepo().exactRef(name.branch()); Ref ref = or.getRepo().exactRef(name.branch());
if (ref != null) { if (ref != null) {
@@ -142,11 +143,11 @@ public class MergeOpRepoManager implements AutoCloseable {
|| Objects.equals(RefNames.REFS_CONFIG, name.branch())) { || Objects.equals(RefNames.REFS_CONFIG, name.branch())) {
oldTip = null; oldTip = null;
} else { } else {
throw new IntegrationException( throw new IntegrationConflictException(
"The destination branch " + name + " does not exist anymore."); "The destination branch " + name + " does not exist anymore.");
} }
} catch (IOException e) { } catch (IOException e) {
throw new IntegrationException("Cannot open branch " + name, e); throw new StorageException("Cannot open branch " + name, e);
} }
} }
} }

View File

@@ -54,13 +54,12 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
} }
@Override @Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) {
throws IntegrationException {
List<CodeReviewCommit> sorted; List<CodeReviewCommit> sorted;
try { try {
sorted = args.rebaseSorter.sort(toMerge); sorted = args.rebaseSorter.sort(toMerge);
} catch (IOException | StorageException e) { } catch (IOException | StorageException e) {
throw new IntegrationException("Commit sorting failed", e); throw new StorageException("Commit sorting failed", e);
} }
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size()); List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
boolean first = true; boolean first = true;
@@ -118,7 +117,7 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
@Override @Override
public void updateRepoImpl(RepoContext ctx) public void updateRepoImpl(RepoContext ctx)
throws IntegrationException, InvalidChangeOperationException, RestApiException, IOException, throws InvalidChangeOperationException, RestApiException, IOException,
PermissionBackendException { PermissionBackendException {
if (args.mergeUtil.canFastForward( if (args.mergeUtil.canFastForward(
args.mergeSorter, args.mergeTip.getCurrentTip(), args.rw, toMerge)) { args.mergeSorter, args.mergeTip.getCurrentTip(), args.rw, toMerge)) {
@@ -193,7 +192,7 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
rebaseOp.updateRepo(ctx); rebaseOp.updateRepo(ctx);
} catch (MergeConflictException | NoSuchChangeException e) { } catch (MergeConflictException | NoSuchChangeException e) {
toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT); toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
throw new IntegrationException( throw new IntegrationConflictException(
"Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e); "Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e);
} }
newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit()); newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit());
@@ -260,7 +259,7 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
} }
@Override @Override
public void updateRepoImpl(RepoContext ctx) throws IntegrationException, IOException { public void updateRepoImpl(RepoContext ctx) throws IntegrationConflictException, IOException {
// There are multiple parents, so this is a merge commit. We don't want // There are multiple parents, so this is a merge commit. We don't want
// to rebase the merge as clients can't easily rebase their history with // to rebase the merge as clients can't easily rebase their history with
// that merge present and replaced by an equivalent merge with a different // that merge present and replaced by an equivalent merge with a different
@@ -306,8 +305,7 @@ public class RebaseSubmitStrategy extends SubmitStrategy {
SubmitDryRun.Arguments args, SubmitDryRun.Arguments args,
Repository repo, Repository repo,
CodeReviewCommit mergeTip, CodeReviewCommit mergeTip,
CodeReviewCommit toMerge) CodeReviewCommit toMerge) {
throws IntegrationException {
// Test for merge instead of cherry pick to avoid false negatives // Test for merge instead of cherry pick to avoid false negatives
// on commit chains. // on commit chains.
return args.mergeUtil.canMerge(args.mergeSorter, repo, mergeTip, toMerge); return args.mergeUtil.canMerge(args.mergeSorter, repo, mergeTip, toMerge);

View File

@@ -22,6 +22,7 @@ import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.SubmitType; import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.CodeReviewCommit; import com.google.gerrit.server.git.CodeReviewCommit;
@@ -117,7 +118,7 @@ public class SubmitDryRun {
ObjectId tip, ObjectId tip,
ObjectId toMerge, ObjectId toMerge,
Set<RevCommit> alreadyAccepted) Set<RevCommit> alreadyAccepted)
throws IntegrationException, NoSuchProjectException, IOException { throws NoSuchProjectException, IOException {
CodeReviewCommit tipCommit = rw.parseCommit(tip); CodeReviewCommit tipCommit = rw.parseCommit(tip);
CodeReviewCommit toMergeCommit = rw.parseCommit(toMerge); CodeReviewCommit toMergeCommit = rw.parseCommit(toMerge);
RevFlag canMerge = rw.newFlag("CAN_MERGE"); RevFlag canMerge = rw.newFlag("CAN_MERGE");
@@ -152,7 +153,7 @@ public class SubmitDryRun {
default: default:
String errorMsg = "No submit strategy for: " + submitType; String errorMsg = "No submit strategy for: " + submitType;
logger.atSevere().log(errorMsg); logger.atSevere().log(errorMsg);
throw new IntegrationException(errorMsg); throw new StorageException(errorMsg);
} }
} }

View File

@@ -249,12 +249,8 @@ public abstract class SubmitStrategy {
* @param toMerge the set of submitted commits that should be merged using this submit strategy. * @param toMerge the set of submitted commits that should be merged using this submit strategy.
* Implementations are responsible for ordering of commits, and will not modify the input in * Implementations are responsible for ordering of commits, and will not modify the input in
* place. * place.
* @throws IntegrationException if an error occurred initializing the operations (as opposed to an
* error during execution, which will be reported only when the batch update executes the
* operations).
*/ */
public final void addOps(BatchUpdate bu, Set<CodeReviewCommit> toMerge) public final void addOps(BatchUpdate bu, Set<CodeReviewCommit> toMerge) {
throws IntegrationException {
List<SubmitStrategyOp> ops = buildOps(toMerge); List<SubmitStrategyOp> ops = buildOps(toMerge);
Set<CodeReviewCommit> added = Sets.newHashSetWithExpectedSize(ops.size()); Set<CodeReviewCommit> added = Sets.newHashSetWithExpectedSize(ops.size());
@@ -289,6 +285,5 @@ public abstract class SubmitStrategy {
} }
} }
protected abstract List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) protected abstract List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge);
throws IntegrationException;
} }

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.server.submit;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.SubmissionId; import com.google.gerrit.entities.SubmissionId;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.api.changes.SubmitInput; import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.client.SubmitType; import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
@@ -55,8 +56,7 @@ public class SubmitStrategyFactory {
SubmissionId submissionId, SubmissionId submissionId,
SubmitInput submitInput, SubmitInput submitInput,
SubmoduleOp submoduleOp, SubmoduleOp submoduleOp,
boolean dryrun) boolean dryrun) {
throws IntegrationException {
SubmitStrategy.Arguments args = SubmitStrategy.Arguments args =
argsFactory.create( argsFactory.create(
submitType, submitType,
@@ -89,7 +89,7 @@ public class SubmitStrategyFactory {
default: default:
String errorMsg = "No submit strategy for: " + submitType; String errorMsg = "No submit strategy for: " + submitType;
logger.atSevere().log(errorMsg); logger.atSevere().log(errorMsg);
throw new IntegrationException(errorMsg); throw new StorageException(errorMsg);
} }
} }
} }

View File

@@ -50,13 +50,9 @@ public class SubmitStrategyListener implements BatchUpdateListener {
@Override @Override
public void afterUpdateRepos() throws ResourceConflictException { public void afterUpdateRepos() throws ResourceConflictException {
try {
markCleanMerges(); markCleanMerges();
List<Change.Id> alreadyMerged = checkCommitStatus(); List<Change.Id> alreadyMerged = checkCommitStatus();
findUnmergedChanges(alreadyMerged); findUnmergedChanges(alreadyMerged);
} catch (IntegrationException e) {
throw new ResourceConflictException(e.getMessage(), e);
}
} }
@Override @Override
@@ -66,8 +62,7 @@ public class SubmitStrategyListener implements BatchUpdateListener {
} }
} }
private void findUnmergedChanges(List<Change.Id> alreadyMerged) private void findUnmergedChanges(List<Change.Id> alreadyMerged) throws ResourceConflictException {
throws ResourceConflictException, IntegrationException {
for (SubmitStrategy strategy : strategies) { for (SubmitStrategy strategy : strategies) {
if (strategy instanceof CherryPick) { if (strategy instanceof CherryPick) {
// Can't do this sanity check for CherryPick since: // Can't do this sanity check for CherryPick since:
@@ -91,7 +86,7 @@ public class SubmitStrategyListener implements BatchUpdateListener {
commitStatus.maybeFailVerbose(); commitStatus.maybeFailVerbose();
} }
private void markCleanMerges() throws IntegrationException { private void markCleanMerges() {
for (SubmitStrategy strategy : strategies) { for (SubmitStrategy strategy : strategies) {
SubmitStrategy.Arguments args = strategy.args; SubmitStrategy.Arguments args = strategy.args;
RevCommit initialTip = args.mergeTip.getInitialTip(); RevCommit initialTip = args.mergeTip.getInitialTip();

View File

@@ -138,8 +138,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
args.submoduleOp.addBranchTip(getDest(), tipAfter); args.submoduleOp.addBranchTip(getDest(), tipAfter);
} }
private void checkProjectConfig(RepoContext ctx, CodeReviewCommit commit) private void checkProjectConfig(RepoContext ctx, CodeReviewCommit commit) {
throws IntegrationException {
String refName = getDest().branch(); String refName = getDest().branch();
if (RefNames.REFS_CONFIG.equals(refName)) { if (RefNames.REFS_CONFIG.equals(refName)) {
logger.atFine().log("Loading new configuration from %s", RefNames.REFS_CONFIG); logger.atFine().log("Loading new configuration from %s", RefNames.REFS_CONFIG);
@@ -147,7 +146,7 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
ProjectConfig cfg = args.projectConfigFactory.create(getProject()); ProjectConfig cfg = args.projectConfigFactory.create(getProject());
cfg.load(ctx.getRevWalk(), commit); cfg.load(ctx.getRevWalk(), commit);
} catch (Exception e) { } catch (Exception e) {
throw new IntegrationException( throw new StorageException(
"Submit would store invalid" "Submit would store invalid"
+ " project configuration " + " project configuration "
+ commit.name() + commit.name()
@@ -542,7 +541,8 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
* *
* @param commit * @param commit
*/ */
protected CodeReviewCommit amendGitlink(CodeReviewCommit commit) throws IntegrationException { protected CodeReviewCommit amendGitlink(CodeReviewCommit commit)
throws IntegrationConflictException {
if (!args.submoduleOp.hasSubscription(args.destBranch)) { if (!args.submoduleOp.hasSubscription(args.destBranch)) {
return commit; return commit;
} }
@@ -550,8 +550,11 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
// Modify the commit with gitlink update // Modify the commit with gitlink update
try { try {
return args.submoduleOp.composeGitlinksCommit(args.destBranch, commit); return args.submoduleOp.composeGitlinksCommit(args.destBranch, commit);
} catch (SubmoduleException | IOException e) { } catch (IOException e) {
throw new IntegrationException( throw new StorageException(
"cannot update gitlink for the commit at branch: " + args.destBranch, e);
} catch (SubmoduleException e) {
throw new IntegrationConflictException(
"cannot update gitlink for the commit at branch: " + args.destBranch, e); "cannot update gitlink for the commit at branch: " + args.destBranch, e);
} }
} }

View File

@@ -27,11 +27,11 @@ import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.FooterConstants; import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.entities.PatchSet; import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.InheritableBoolean; import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType; import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.config.UrlFormatter; import com.google.gerrit.server.config.UrlFormatter;
import com.google.gerrit.server.git.ChangeMessageModifier; import com.google.gerrit.server.git.ChangeMessageModifier;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
@@ -129,8 +129,7 @@ public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
ChangeMessageModifier modifier2 = (msg, orig, tip, dest) -> msg + "A-footer: value\n"; ChangeMessageModifier modifier2 = (msg, orig, tip, dest) -> msg + "A-footer: value\n";
try (Registration registration = try (Registration registration =
extensionRegistry.newRegistration().add(modifier1).add(modifier2)) { extensionRegistry.newRegistration().add(modifier1).add(modifier2)) {
ResourceConflictException thrown = StorageException thrown = assertThrows(StorageException.class, () -> submitWithRebase());
assertThrows(ResourceConflictException.class, () -> submitWithRebase());
Throwable cause = Throwables.getRootCause(thrown); Throwable cause = Throwables.getRootCause(thrown);
assertThat(cause).isInstanceOf(RuntimeException.class); assertThat(cause).isInstanceOf(RuntimeException.class);
assertThat(cause).hasMessageThat().isEqualTo("boom"); assertThat(cause).hasMessageThat().isEqualTo("boom");
@@ -146,8 +145,7 @@ public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
.newRegistration() .newRegistration()
.add(modifier1, "modifier-1") .add(modifier1, "modifier-1")
.add(modifier2, "modifier-2")) { .add(modifier2, "modifier-2")) {
ResourceConflictException thrown = StorageException thrown = assertThrows(StorageException.class, () -> submitWithRebase());
assertThrows(ResourceConflictException.class, () -> submitWithRebase());
Throwable cause = Throwables.getRootCause(thrown); Throwable cause = Throwables.getRootCause(thrown);
assertThat(cause).isInstanceOf(RuntimeException.class); assertThat(cause).isInstanceOf(RuntimeException.class);
assertThat(cause) assertThat(cause)