Submodule updates: Differentiate between conflicts and server errors
If a submodule operation cannot be performed we currently always throw a SubmoduleException. According to its javadoc SubmoduleException messages are user-visible. SubmoduleException is thrown in 2 cases: 1. the submodule operation cannot be performed due to conflicts 2. the submodule operation cannot be performed due to an error in Gerrit For 1. we should return '409 Conflict’, for 2. rather ‘500 Internal Server Error'. In case of 2. the exception message should not be returned to users. To fix this we introduce a SubmoduleConflictException as subclass of ResourceConflictException that is thrown if there is a conflict when performing the submodule operation 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 a bit. Signed-off-by: Edwin Kempin <ekempin@google.com> Change-Id: Icd60fa14a67944543ccf06df38ffee03c5d0c722
This commit is contained in:
@@ -163,7 +163,6 @@ 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.MergeOp;
|
import com.google.gerrit.server.submit.MergeOp;
|
||||||
import com.google.gerrit.server.submit.MergeOpRepoManager;
|
import com.google.gerrit.server.submit.MergeOpRepoManager;
|
||||||
import com.google.gerrit.server.submit.SubmoduleException;
|
|
||||||
import com.google.gerrit.server.submit.SubmoduleOp;
|
import com.google.gerrit.server.submit.SubmoduleOp;
|
||||||
import com.google.gerrit.server.update.BatchUpdate;
|
import com.google.gerrit.server.update.BatchUpdate;
|
||||||
import com.google.gerrit.server.update.BatchUpdateOp;
|
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||||
@@ -763,8 +762,8 @@ class ReceiveCommits {
|
|||||||
orm.setContext(TimeUtil.nowTs(), user, NotifyResolver.Result.none());
|
orm.setContext(TimeUtil.nowTs(), user, NotifyResolver.Result.none());
|
||||||
SubmoduleOp op = subOpFactory.create(branches, orm);
|
SubmoduleOp op = subOpFactory.create(branches, orm);
|
||||||
op.updateSuperProjects();
|
op.updateSuperProjects();
|
||||||
} catch (SubmoduleException e) {
|
} catch (RestApiException e) {
|
||||||
logger.atSevere().withCause(e).log("Can't update the superprojects");
|
logger.atWarning().withCause(e).log("Can't update the superprojects");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -622,7 +622,7 @@ public class MergeOp implements AutoCloseable {
|
|||||||
throw new ResourceNotFoundException(e.getMessage());
|
throw new ResourceNotFoundException(e.getMessage());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new StorageException(e);
|
throw new StorageException(e);
|
||||||
} catch (SubmoduleException e) {
|
} catch (SubmoduleConflictException e) {
|
||||||
throw new IntegrationConflictException(e.getMessage(), e);
|
throw new IntegrationConflictException(e.getMessage(), e);
|
||||||
} catch (UpdateException e) {
|
} catch (UpdateException e) {
|
||||||
if (e.getCause() instanceof LockFailureException) {
|
if (e.getCause() instanceof LockFailureException) {
|
||||||
|
@@ -552,10 +552,13 @@ abstract class SubmitStrategyOp implements BatchUpdateOp {
|
|||||||
return args.submoduleOp.composeGitlinksCommit(args.destBranch, commit);
|
return args.submoduleOp.composeGitlinksCommit(args.destBranch, commit);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new StorageException(
|
throw new StorageException(
|
||||||
"cannot update gitlink for the commit at branch: " + args.destBranch, e);
|
String.format("cannot update gitlink for the commit at branch %s", args.destBranch), e);
|
||||||
} catch (SubmoduleException e) {
|
} catch (SubmoduleConflictException e) {
|
||||||
throw new IntegrationConflictException(
|
throw new IntegrationConflictException(
|
||||||
"cannot update gitlink for the commit at branch: " + args.destBranch, e);
|
String.format(
|
||||||
|
"cannot update gitlink for the commit at branch %s: %s",
|
||||||
|
args.destBranch, e.getMessage()),
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2011 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,18 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.submit;
|
package com.google.gerrit.server.submit;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the gitlink's update cannot be processed at this time.
|
* Exception to be thrown if any submodule operation is not possible due to conflicts.
|
||||||
*
|
*
|
||||||
* <p>Message should be considered user-visible.
|
* <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 SubmoduleException extends Exception {
|
public class SubmoduleConflictException extends ResourceConflictException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
SubmoduleException(String msg) {
|
public SubmoduleConflictException(String msg) {
|
||||||
super(msg, null);
|
super(msg);
|
||||||
}
|
|
||||||
|
|
||||||
SubmoduleException(String msg, Throwable why) {
|
|
||||||
super(msg, why);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -28,6 +28,7 @@ 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.entities.SubmoduleSubscription;
|
import com.google.gerrit.entities.SubmoduleSubscription;
|
||||||
|
import com.google.gerrit.exceptions.StorageException;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
@@ -116,7 +117,7 @@ public class SubmoduleOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SubmoduleOp create(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
|
public SubmoduleOp create(Set<BranchNameKey> updatedBranches, MergeOpRepoManager orm)
|
||||||
throws SubmoduleException {
|
throws SubmoduleConflictException {
|
||||||
return new SubmoduleOp(
|
return new SubmoduleOp(
|
||||||
gitmodulesFactory, serverIdent.get(), cfg, projectCache, updatedBranches, orm);
|
gitmodulesFactory, serverIdent.get(), cfg, projectCache, updatedBranches, orm);
|
||||||
}
|
}
|
||||||
@@ -166,7 +167,7 @@ public class SubmoduleOp {
|
|||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
Set<BranchNameKey> updatedBranches,
|
Set<BranchNameKey> updatedBranches,
|
||||||
MergeOpRepoManager orm)
|
MergeOpRepoManager orm)
|
||||||
throws SubmoduleException {
|
throws SubmoduleConflictException {
|
||||||
this.gitmodulesFactory = gitmodulesFactory;
|
this.gitmodulesFactory = gitmodulesFactory;
|
||||||
this.myIdent = myIdent;
|
this.myIdent = myIdent;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
@@ -199,7 +200,6 @@ public class SubmoduleOp {
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @return the ordered set to be stored in {@link #sortedBranches}.
|
* @return the ordered set to be stored in {@link #sortedBranches}.
|
||||||
* @throws SubmoduleException if an error occurred walking projects.
|
|
||||||
*/
|
*/
|
||||||
// TODO(dborowitz): This setup process is hard to follow, in large part due to the accumulation of
|
// TODO(dborowitz): This setup process is hard to follow, in large part due to the accumulation of
|
||||||
// mutable maps, which makes this whole class difficult to understand.
|
// mutable maps, which makes this whole class difficult to understand.
|
||||||
@@ -215,7 +215,8 @@ public class SubmoduleOp {
|
|||||||
//
|
//
|
||||||
// In addition to improving readability, this approach has the advantage of making (1) and (2)
|
// In addition to improving readability, this approach has the advantage of making (1) and (2)
|
||||||
// testable using small tests.
|
// testable using small tests.
|
||||||
private ImmutableSet<BranchNameKey> calculateSubscriptionMaps() throws SubmoduleException {
|
private ImmutableSet<BranchNameKey> calculateSubscriptionMaps()
|
||||||
|
throws SubmoduleConflictException {
|
||||||
if (!enableSuperProjectSubscriptions) {
|
if (!enableSuperProjectSubscriptions) {
|
||||||
logger.atFine().log("Updating superprojects disabled");
|
logger.atFine().log("Updating superprojects disabled");
|
||||||
return null;
|
return null;
|
||||||
@@ -244,11 +245,11 @@ public class SubmoduleOp {
|
|||||||
BranchNameKey current,
|
BranchNameKey current,
|
||||||
LinkedHashSet<BranchNameKey> currentVisited,
|
LinkedHashSet<BranchNameKey> currentVisited,
|
||||||
LinkedHashSet<BranchNameKey> allVisited)
|
LinkedHashSet<BranchNameKey> allVisited)
|
||||||
throws SubmoduleException {
|
throws SubmoduleConflictException {
|
||||||
logger.atFine().log("Now processing %s", current);
|
logger.atFine().log("Now processing %s", current);
|
||||||
|
|
||||||
if (currentVisited.contains(current)) {
|
if (currentVisited.contains(current)) {
|
||||||
throw new SubmoduleException(
|
throw new SubmoduleConflictException(
|
||||||
"Branch level circular subscriptions detected: "
|
"Branch level circular subscriptions detected: "
|
||||||
+ printCircularPath(currentVisited, current));
|
+ printCircularPath(currentVisited, current));
|
||||||
}
|
}
|
||||||
@@ -270,7 +271,7 @@ public class SubmoduleOp {
|
|||||||
affectedBranches.add(sub.getSubmodule());
|
affectedBranches.add(sub.getSubmodule());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new SubmoduleException("Cannot find superprojects for " + current, e);
|
throw new StorageException("Cannot find superprojects for " + current, e);
|
||||||
}
|
}
|
||||||
currentVisited.remove(current);
|
currentVisited.remove(current);
|
||||||
allVisited.add(current);
|
allVisited.add(current);
|
||||||
@@ -396,7 +397,7 @@ public class SubmoduleOp {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateSuperProjects() throws SubmoduleException {
|
public void updateSuperProjects() throws RestApiException {
|
||||||
ImmutableSet<Project.NameKey> projects = getProjectsInOrder();
|
ImmutableSet<Project.NameKey> projects = getProjectsInOrder();
|
||||||
if (projects == null) {
|
if (projects == null) {
|
||||||
return;
|
return;
|
||||||
@@ -416,19 +417,19 @@ public class SubmoduleOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
BatchUpdate.execute(orm.batchUpdates(superProjects), BatchUpdateListener.NONE, false);
|
BatchUpdate.execute(orm.batchUpdates(superProjects), BatchUpdateListener.NONE, false);
|
||||||
} catch (RestApiException | UpdateException | IOException | NoSuchProjectException e) {
|
} catch (UpdateException | IOException | NoSuchProjectException e) {
|
||||||
throw new SubmoduleException("Cannot update gitlinks", e);
|
throw new StorageException("Cannot update gitlinks", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a separate gitlink commit */
|
/** Create a separate gitlink commit */
|
||||||
private CodeReviewCommit composeGitlinksCommit(BranchNameKey subscriber)
|
private CodeReviewCommit composeGitlinksCommit(BranchNameKey subscriber)
|
||||||
throws IOException, SubmoduleException {
|
throws IOException, SubmoduleConflictException {
|
||||||
OpenRepo or;
|
OpenRepo or;
|
||||||
try {
|
try {
|
||||||
or = orm.getRepo(subscriber.project());
|
or = orm.getRepo(subscriber.project());
|
||||||
} catch (NoSuchProjectException | IOException e) {
|
} catch (NoSuchProjectException | IOException e) {
|
||||||
throw new SubmoduleException("Cannot access superproject", e);
|
throw new StorageException("Cannot access superproject", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeReviewCommit currentCommit;
|
CodeReviewCommit currentCommit;
|
||||||
@@ -437,7 +438,7 @@ public class SubmoduleOp {
|
|||||||
} else {
|
} else {
|
||||||
Ref r = or.repo.exactRef(subscriber.branch());
|
Ref r = or.repo.exactRef(subscriber.branch());
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
throw new SubmoduleException(
|
throw new SubmoduleConflictException(
|
||||||
"The branch was probably deleted from the subscriber repository");
|
"The branch was probably deleted from the subscriber repository");
|
||||||
}
|
}
|
||||||
currentCommit = or.rw.parseCommit(r.getObjectId());
|
currentCommit = or.rw.parseCommit(r.getObjectId());
|
||||||
@@ -493,12 +494,12 @@ public class SubmoduleOp {
|
|||||||
|
|
||||||
/** Amend an existing commit with gitlink updates */
|
/** Amend an existing commit with gitlink updates */
|
||||||
CodeReviewCommit composeGitlinksCommit(BranchNameKey subscriber, CodeReviewCommit currentCommit)
|
CodeReviewCommit composeGitlinksCommit(BranchNameKey subscriber, CodeReviewCommit currentCommit)
|
||||||
throws IOException, SubmoduleException {
|
throws IOException, SubmoduleConflictException {
|
||||||
OpenRepo or;
|
OpenRepo or;
|
||||||
try {
|
try {
|
||||||
or = orm.getRepo(subscriber.project());
|
or = orm.getRepo(subscriber.project());
|
||||||
} catch (NoSuchProjectException | IOException e) {
|
} catch (NoSuchProjectException | IOException e) {
|
||||||
throw new SubmoduleException("Cannot access superproject", e);
|
throw new StorageException("Cannot access superproject", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder msgbuf = new StringBuilder();
|
StringBuilder msgbuf = new StringBuilder();
|
||||||
@@ -534,13 +535,13 @@ public class SubmoduleOp {
|
|||||||
|
|
||||||
private RevCommit updateSubmodule(
|
private RevCommit updateSubmodule(
|
||||||
DirCache dc, DirCacheEditor ed, StringBuilder msgbuf, SubmoduleSubscription s)
|
DirCache dc, DirCacheEditor ed, StringBuilder msgbuf, SubmoduleSubscription s)
|
||||||
throws SubmoduleException, IOException {
|
throws SubmoduleConflictException, IOException {
|
||||||
logger.atFine().log("Updating gitlink for %s", s);
|
logger.atFine().log("Updating gitlink for %s", s);
|
||||||
OpenRepo subOr;
|
OpenRepo subOr;
|
||||||
try {
|
try {
|
||||||
subOr = orm.getRepo(s.getSubmodule().project());
|
subOr = orm.getRepo(s.getSubmodule().project());
|
||||||
} catch (NoSuchProjectException | IOException e) {
|
} catch (NoSuchProjectException | IOException e) {
|
||||||
throw new SubmoduleException("Cannot access submodule", e);
|
throw new StorageException("Cannot access submodule", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
DirCacheEntry dce = dc.getEntry(s.getPath());
|
DirCacheEntry dce = dc.getEntry(s.getPath());
|
||||||
@@ -554,7 +555,7 @@ public class SubmoduleOp {
|
|||||||
+ s.getSubmodule().project().get()
|
+ s.getSubmodule().project().get()
|
||||||
+ " but entry "
|
+ " but entry "
|
||||||
+ "doesn't have gitlink file mode.";
|
+ "doesn't have gitlink file mode.";
|
||||||
throw new SubmoduleException(errMsg);
|
throw new SubmoduleConflictException(errMsg);
|
||||||
}
|
}
|
||||||
// Parse the current gitlink entry commit in the subproject repo. This is used to add a
|
// Parse the current gitlink entry commit in the subproject repo. This is used to add a
|
||||||
// shortlog for this submodule to the commit message in the superproject.
|
// shortlog for this submodule to the commit message in the superproject.
|
||||||
@@ -617,8 +618,7 @@ public class SubmoduleOp {
|
|||||||
SubmoduleSubscription s,
|
SubmoduleSubscription s,
|
||||||
OpenRepo subOr,
|
OpenRepo subOr,
|
||||||
RevCommit newCommit,
|
RevCommit newCommit,
|
||||||
RevCommit oldCommit)
|
RevCommit oldCommit) {
|
||||||
throws SubmoduleException {
|
|
||||||
msgbuf.append("* Update ");
|
msgbuf.append("* Update ");
|
||||||
msgbuf.append(s.getPath());
|
msgbuf.append(s.getPath());
|
||||||
msgbuf.append(" from branch '");
|
msgbuf.append(" from branch '");
|
||||||
@@ -659,7 +659,7 @@ public class SubmoduleOp {
|
|||||||
msgbuf.append(message);
|
msgbuf.append(message);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new SubmoduleException(
|
throw new StorageException(
|
||||||
"Could not perform a revwalk to create superproject commit message", e);
|
"Could not perform a revwalk to create superproject commit message", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,7 +676,7 @@ public class SubmoduleOp {
|
|||||||
return dc;
|
return dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImmutableSet<Project.NameKey> getProjectsInOrder() throws SubmoduleException {
|
ImmutableSet<Project.NameKey> getProjectsInOrder() throws SubmoduleConflictException {
|
||||||
LinkedHashSet<Project.NameKey> projects = new LinkedHashSet<>();
|
LinkedHashSet<Project.NameKey> projects = new LinkedHashSet<>();
|
||||||
for (Project.NameKey project : branchesByProject.keySet()) {
|
for (Project.NameKey project : branchesByProject.keySet()) {
|
||||||
addAllSubmoduleProjects(project, new LinkedHashSet<>(), projects);
|
addAllSubmoduleProjects(project, new LinkedHashSet<>(), projects);
|
||||||
@@ -692,9 +692,9 @@ public class SubmoduleOp {
|
|||||||
Project.NameKey project,
|
Project.NameKey project,
|
||||||
LinkedHashSet<Project.NameKey> current,
|
LinkedHashSet<Project.NameKey> current,
|
||||||
LinkedHashSet<Project.NameKey> projects)
|
LinkedHashSet<Project.NameKey> projects)
|
||||||
throws SubmoduleException {
|
throws SubmoduleConflictException {
|
||||||
if (current.contains(project)) {
|
if (current.contains(project)) {
|
||||||
throw new SubmoduleException(
|
throw new SubmoduleConflictException(
|
||||||
"Project level circular subscriptions detected: " + printCircularPath(current, project));
|
"Project level circular subscriptions detected: " + printCircularPath(current, project));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Submodule plugins/delete-project updated: da0811ef34...e8a249ae1b
Reference in New Issue
Block a user