Avoid 500s when a topic includes changes that are not visible
/action and /submitted_together requests are throwing OrmException
when they are not able to examine non-visible changes in the topic:
com.google.gwtorm.server.OrmException: Failed to get submit type for 12345: Patch set 12345,6 not found
at com.google.gerrit.server.git.MergeSuperSet.logErrorAndThrow(MergeSuperSet.java:218)
at com.google.gerrit.server.git.MergeSuperSet.completeChangeSetWithoutTopic(MergeSuperSet.java:122)
at com.google.gerrit.server.git.MergeSuperSet.completeChangeSetIncludingTopics(MergeSuperSet.java:180)
at com.google.gerrit.server.git.MergeSuperSet.completeChangeSet(MergeSuperSet.java:101)
at com.google.gerrit.server.change.SubmittedTogether.getForOpenChange(SubmittedTogether.java:105)
at com.google.gerrit.server.change.SubmittedTogether.apply(SubmittedTogether.java:78)
at com.google.gerrit.server.change.SubmittedTogether.apply(SubmittedTogether.java:46)
at com.google.gerrit.httpd.restapi.RestApiServlet.service(RestApiServlet.java:332)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
The result is a lightbox with a 500 when the user views a change in
the same topic as a draft change the user cannot see.
As a first step toward fixing that, use response code 403 instead.
This allows automated callers to get a better sense of what is going
on (it is a permissions error, not an internal server error) without
being confused by an unexpected response (e.g., an abbreviated list of
changes).
Change-Id: I560508c8d941fa6be140363f5bb103c3da4fac05
This commit is contained in:
@@ -111,6 +111,28 @@ public class ActionsIT extends AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void revisionActionsETagWithHiddenDraftInTopic() throws Exception {
|
||||||
|
String change = createChangeWithTopic().getChangeId();
|
||||||
|
approve(change);
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
String etag1 = getRevisionActions.getETag(parseCurrentRevisionResource(change));
|
||||||
|
|
||||||
|
setApiUser(admin);
|
||||||
|
String draft = createDraftWithTopic().getChangeId();
|
||||||
|
approve(draft);
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
String etag2 = getRevisionActions.getETag(parseCurrentRevisionResource(change));
|
||||||
|
|
||||||
|
if (isSubmitWholeTopicEnabled()) {
|
||||||
|
assertThat(etag2).isNotEqualTo(etag1);
|
||||||
|
} else {
|
||||||
|
assertThat(etag2).isEqualTo(etag1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void revisionActionsAnonymousETag() throws Exception {
|
public void revisionActionsAnonymousETag() throws Exception {
|
||||||
String parent = createChange().getChangeId();
|
String parent = createChange().getChangeId();
|
||||||
@@ -262,13 +284,20 @@ public class ActionsIT extends AbstractDaemonTest {
|
|||||||
assertThat(actions).containsKey("rebase");
|
assertThat(actions).containsKey("rebase");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createCommitAndPush(
|
||||||
|
TestRepository<InMemoryRepository> repo, String ref,
|
||||||
|
String commitMsg, String fileName, String content) throws Exception {
|
||||||
|
return pushFactory
|
||||||
|
.create(db, admin.getIdent(), repo, commitMsg, fileName, content)
|
||||||
|
.to(ref);
|
||||||
|
}
|
||||||
|
|
||||||
private PushOneCommit.Result createChangeWithTopic(
|
private PushOneCommit.Result createChangeWithTopic(
|
||||||
TestRepository<InMemoryRepository> repo, String topic,
|
TestRepository<InMemoryRepository> repo, String topic,
|
||||||
String commitMsg, String fileName, String content) throws Exception {
|
String commitMsg, String fileName, String content) throws Exception {
|
||||||
PushOneCommit push = pushFactory.create(db, admin.getIdent(),
|
|
||||||
repo, commitMsg, fileName, content);
|
|
||||||
assertThat(topic).isNotEmpty();
|
assertThat(topic).isNotEmpty();
|
||||||
return push.to("refs/for/master/" + name(topic));
|
return createCommitAndPush(repo, "refs/for/master/" + name(topic),
|
||||||
|
commitMsg, fileName, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PushOneCommit.Result createChangeWithTopic()
|
private PushOneCommit.Result createChangeWithTopic()
|
||||||
@@ -276,4 +305,10 @@ public class ActionsIT extends AbstractDaemonTest {
|
|||||||
return createChangeWithTopic(testRepo, "foo2",
|
return createChangeWithTopic(testRepo, "foo2",
|
||||||
"a message", "a.txt", "content\n");
|
"a message", "a.txt", "content\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PushOneCommit.Result createDraftWithTopic()
|
||||||
|
throws Exception {
|
||||||
|
return createCommitAndPush(testRepo, "refs/drafts/master/" + name("foo2"),
|
||||||
|
"a message", "a.txt", "content\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,21 +16,30 @@ package com.google.gerrit.acceptance.server.change;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.gerrit.acceptance.GitUtil.pushHead;
|
import static com.google.gerrit.acceptance.GitUtil.pushHead;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.acceptance.GitUtil;
|
import com.google.gerrit.acceptance.GitUtil;
|
||||||
import com.google.gerrit.acceptance.TestProjectInput;
|
import com.google.gerrit.acceptance.TestProjectInput;
|
||||||
import com.google.gerrit.extensions.client.ChangeStatus;
|
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||||
import com.google.gerrit.extensions.client.SubmitType;
|
import com.google.gerrit.extensions.client.SubmitType;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.testutil.ConfigSuite;
|
||||||
|
|
||||||
import org.eclipse.jgit.junit.TestRepository;
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class SubmittedTogetherIT extends AbstractDaemonTest {
|
public class SubmittedTogetherIT extends AbstractDaemonTest {
|
||||||
|
@ConfigSuite.Config
|
||||||
|
public static Config submitWholeTopicEnabled() {
|
||||||
|
return submitWholeTopicEnabledConfig();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void returnsAncestors() throws Exception {
|
public void returnsAncestors() throws Exception {
|
||||||
@@ -112,6 +121,34 @@ public class SubmittedTogetherIT extends AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hiddenDraftInTopic() throws Exception {
|
||||||
|
RevCommit initialHead = getRemoteHead();
|
||||||
|
RevCommit a = commitBuilder().add("a", "1").message("change 1").create();
|
||||||
|
pushHead(testRepo, "refs/for/master/" + name("topic"), false);
|
||||||
|
String id1 = getChangeId(a);
|
||||||
|
|
||||||
|
testRepo.reset(initialHead);
|
||||||
|
RevCommit b =
|
||||||
|
commitBuilder().add("b", "2").message("invisible change").create();
|
||||||
|
pushHead(testRepo, "refs/drafts/master/" + name("topic"), false);
|
||||||
|
|
||||||
|
setApiUser(user);
|
||||||
|
if (isSubmitWholeTopicEnabled()) {
|
||||||
|
try {
|
||||||
|
gApi.changes().id(id1).submittedTogether();
|
||||||
|
fail("Expected AuthException");
|
||||||
|
} catch (RestApiException e) {
|
||||||
|
// TODO(jrn): fix extension API not to wrap the RestApiException.
|
||||||
|
assertThat(e.getCause()).isInstanceOf(AuthException.class);
|
||||||
|
assertThat(e.getCause()).hasMessage(
|
||||||
|
"change would be submitted with a change that you cannot see");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assertSubmittedTogether(id1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTopicChaining() throws Exception {
|
public void testTopicChaining() throws Exception {
|
||||||
RevCommit initialHead = getRemoteHead();
|
RevCommit initialHead = getRemoteHead();
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public class GetRevisionActions implements ETagView<RevisionResource> {
|
|||||||
for (ChangeData cd : cs.changes()) {
|
for (ChangeData cd : cs.changes()) {
|
||||||
changeResourceFactory.create(cd.changeControl()).prepareETag(h, user);
|
changeResourceFactory.create(cd.changeControl()).prepareETag(h, user);
|
||||||
}
|
}
|
||||||
|
h.putBoolean(cs.furtherHiddenChanges());
|
||||||
} catch (IOException | OrmException e) {
|
} catch (IOException | OrmException e) {
|
||||||
throw new OrmRuntimeException(e);
|
throw new OrmRuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,6 +258,9 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
|||||||
try {
|
try {
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
ReviewDb db = dbProvider.get();
|
ReviewDb db = dbProvider.get();
|
||||||
|
if (cs.furtherHiddenChanges()) {
|
||||||
|
return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
|
||||||
|
}
|
||||||
for (ChangeData c : cs.changes()) {
|
for (ChangeData c : cs.changes()) {
|
||||||
ChangeControl changeControl = c.changeControl(user);
|
ChangeControl changeControl = c.changeControl(user);
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,12 @@ public class SubmittedTogether implements RestReadView<ChangeResource> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<ChangeData> getForOpenChange(Change c, CurrentUser user)
|
private List<ChangeData> getForOpenChange(Change c, CurrentUser user)
|
||||||
throws OrmException, IOException {
|
throws OrmException, IOException, AuthException {
|
||||||
ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(), c, user);
|
ChangeSet cs = mergeSuperSet.completeChangeSet(dbProvider.get(), c, user);
|
||||||
|
if (cs.furtherHiddenChanges()) {
|
||||||
|
throw new AuthException(
|
||||||
|
"change would be submitted with a change that you cannot see");
|
||||||
|
}
|
||||||
return cs.changes().asList();
|
return cs.changes().asList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,13 @@ import java.util.Set;
|
|||||||
public class ChangeSet {
|
public class ChangeSet {
|
||||||
private final ImmutableMap<Change.Id, ChangeData> changeData;
|
private final ImmutableMap<Change.Id, ChangeData> changeData;
|
||||||
|
|
||||||
public ChangeSet(Iterable<ChangeData> changes) {
|
/**
|
||||||
|
* Whether additional changes are not included in changeData because they
|
||||||
|
* are not visible to current user.
|
||||||
|
*/
|
||||||
|
private final boolean furtherHiddenChanges;
|
||||||
|
|
||||||
|
public ChangeSet(Iterable<ChangeData> changes, boolean furtherHiddenChanges) {
|
||||||
Map<Change.Id, ChangeData> cds = new LinkedHashMap<>();
|
Map<Change.Id, ChangeData> cds = new LinkedHashMap<>();
|
||||||
for (ChangeData cd : changes) {
|
for (ChangeData cd : changes) {
|
||||||
if (!cds.containsKey(cd.getId())) {
|
if (!cds.containsKey(cd.getId())) {
|
||||||
@@ -51,10 +57,11 @@ public class ChangeSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
changeData = ImmutableMap.copyOf(cds);
|
changeData = ImmutableMap.copyOf(cds);
|
||||||
|
this.furtherHiddenChanges = furtherHiddenChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeSet(ChangeData change) {
|
public ChangeSet(ChangeData change) {
|
||||||
this(ImmutableList.of(change));
|
this(ImmutableList.of(change), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutableSet<Change.Id> ids() {
|
public ImmutableSet<Change.Id> ids() {
|
||||||
@@ -107,12 +114,17 @@ public class ChangeSet {
|
|||||||
return changeData.values();
|
return changeData.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean furtherHiddenChanges() {
|
||||||
|
return furtherHiddenChanges;
|
||||||
|
}
|
||||||
|
|
||||||
public int size() {
|
public int size() {
|
||||||
return changeData.size();
|
return changeData.size() + (furtherHiddenChanges ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getClass().getSimpleName() + ids();
|
return getClass().getSimpleName() + ids()
|
||||||
|
+ (furtherHiddenChanges ? " and further hidden changes" : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git;
|
package com.google.gerrit.server.git;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ import com.google.gerrit.common.data.SubmitRecord;
|
|||||||
import com.google.gerrit.common.data.SubmitTypeRecord;
|
import com.google.gerrit.common.data.SubmitTypeRecord;
|
||||||
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.extensions.restapi.AuthException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.reviewdb.client.Branch;
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
@@ -113,6 +115,8 @@ public class MergeOp implements AutoCloseable {
|
|||||||
private final Multimap<Change.Id, String> problems;
|
private final Multimap<Change.Id, String> problems;
|
||||||
|
|
||||||
private CommitStatus(ChangeSet cs) throws OrmException {
|
private CommitStatus(ChangeSet cs) throws OrmException {
|
||||||
|
checkArgument(!cs.furtherHiddenChanges(),
|
||||||
|
"CommitStatus must not be called with hidden changes");
|
||||||
changes = cs.changesById();
|
changes = cs.changesById();
|
||||||
ImmutableSetMultimap.Builder<Branch.NameKey, Change.Id> bb =
|
ImmutableSetMultimap.Builder<Branch.NameKey, Change.Id> bb =
|
||||||
ImmutableSetMultimap.builder();
|
ImmutableSetMultimap.builder();
|
||||||
@@ -369,6 +373,8 @@ public class MergeOp implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkSubmitRulesAndState(ChangeSet cs) {
|
private void checkSubmitRulesAndState(ChangeSet cs) {
|
||||||
|
checkArgument(!cs.furtherHiddenChanges(),
|
||||||
|
"checkSubmitRulesAndState called for topic with hidden change");
|
||||||
for (ChangeData cd : cs.changes()) {
|
for (ChangeData cd : cs.changes()) {
|
||||||
try {
|
try {
|
||||||
if (cd.change().getStatus() != Change.Status.NEW) {
|
if (cd.change().getStatus() != Change.Status.NEW) {
|
||||||
@@ -388,6 +394,8 @@ public class MergeOp implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void bypassSubmitRules(ChangeSet cs) {
|
private void bypassSubmitRules(ChangeSet cs) {
|
||||||
|
checkArgument(!cs.furtherHiddenChanges(),
|
||||||
|
"cannot bypass submit rules for topic with hidden change");
|
||||||
for (ChangeData cd : cs.changes()) {
|
for (ChangeData cd : cs.changes()) {
|
||||||
List<SubmitRecord> records;
|
List<SubmitRecord> records;
|
||||||
try {
|
try {
|
||||||
@@ -426,6 +434,10 @@ public class MergeOp implements AutoCloseable {
|
|||||||
ChangeSet cs = mergeSuperSet.completeChangeSet(db, change, caller);
|
ChangeSet cs = mergeSuperSet.completeChangeSet(db, change, caller);
|
||||||
checkState(cs.ids().contains(change.getId()),
|
checkState(cs.ids().contains(change.getId()),
|
||||||
"change %s missing from %s", change.getId(), cs);
|
"change %s missing from %s", change.getId(), cs);
|
||||||
|
if (cs.furtherHiddenChanges()) {
|
||||||
|
throw new AuthException("A change to be submitted with "
|
||||||
|
+ change.getId() + " is not visible");
|
||||||
|
}
|
||||||
this.commits = new CommitStatus(cs);
|
this.commits = new CommitStatus(cs);
|
||||||
MergeSuperSet.reloadChanges(cs);
|
MergeSuperSet.reloadChanges(cs);
|
||||||
logDebug("Calculated to merge {}", cs);
|
logDebug("Calculated to merge {}", cs);
|
||||||
@@ -465,6 +477,8 @@ public class MergeOp implements AutoCloseable {
|
|||||||
|
|
||||||
private void integrateIntoHistory(ChangeSet cs)
|
private void integrateIntoHistory(ChangeSet cs)
|
||||||
throws IntegrationException, RestApiException {
|
throws IntegrationException, RestApiException {
|
||||||
|
checkArgument(!cs.furtherHiddenChanges(),
|
||||||
|
"cannot integrate hidden changes into history");
|
||||||
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");
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ public class MergeSuperSet {
|
|||||||
CurrentUser user) throws MissingObjectException,
|
CurrentUser user) throws MissingObjectException,
|
||||||
IncorrectObjectTypeException, IOException, OrmException {
|
IncorrectObjectTypeException, IOException, OrmException {
|
||||||
List<ChangeData> ret = new ArrayList<>();
|
List<ChangeData> ret = new ArrayList<>();
|
||||||
|
boolean sawHiddenChange = changes.furtherHiddenChanges();
|
||||||
|
|
||||||
Multimap<Project.NameKey, Change.Id> pc = changes.changesByProject();
|
Multimap<Project.NameKey, Change.Id> pc = changes.changesByProject();
|
||||||
for (Project.NameKey project : pc.keySet()) {
|
for (Project.NameKey project : pc.keySet()) {
|
||||||
@@ -114,7 +115,10 @@ public class MergeSuperSet {
|
|||||||
RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
|
RevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
|
||||||
for (Change.Id cId : pc.get(project)) {
|
for (Change.Id cId : pc.get(project)) {
|
||||||
ChangeData cd = changeDataFactory.create(db, project, cId);
|
ChangeData cd = changeDataFactory.create(db, project, cId);
|
||||||
cd.changeControl(user);
|
if (!cd.changeControl(user).isVisible(db, cd)) {
|
||||||
|
sawHiddenChange = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
SubmitTypeRecord str = cd.submitTypeRecord();
|
SubmitTypeRecord str = cd.submitTypeRecord();
|
||||||
if (!str.isOk()) {
|
if (!str.isOk()) {
|
||||||
@@ -167,7 +171,7 @@ public class MergeSuperSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ChangeSet(ret);
|
return new ChangeSet(ret, sawHiddenChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChangeSet completeChangeSetIncludingTopics(
|
private ChangeSet completeChangeSetIncludingTopics(
|
||||||
@@ -179,17 +183,24 @@ public class MergeSuperSet {
|
|||||||
ChangeSet newCs = completeChangeSetWithoutTopic(db, changes, user);
|
ChangeSet newCs = completeChangeSetWithoutTopic(db, changes, user);
|
||||||
while (!done) {
|
while (!done) {
|
||||||
List<ChangeData> chgs = new ArrayList<>();
|
List<ChangeData> chgs = new ArrayList<>();
|
||||||
|
boolean sawHiddenChange = newCs.furtherHiddenChanges();
|
||||||
done = true;
|
done = true;
|
||||||
for (ChangeData cd : newCs.changes()) {
|
for (ChangeData cd : newCs.changes()) {
|
||||||
chgs.add(cd);
|
chgs.add(cd);
|
||||||
String topic = cd.change().getTopic();
|
String topic = cd.change().getTopic();
|
||||||
if (!Strings.isNullOrEmpty(topic) && !topicsTraversed.contains(topic)) {
|
if (!Strings.isNullOrEmpty(topic) && !topicsTraversed.contains(topic)) {
|
||||||
chgs.addAll(query().byTopicOpen(topic));
|
for (ChangeData topicCd : query().byTopicOpen(topic)) {
|
||||||
|
if (!topicCd.changeControl(user).isVisible(db, topicCd)) {
|
||||||
|
sawHiddenChange = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
chgs.add(topicCd);
|
||||||
|
}
|
||||||
done = false;
|
done = false;
|
||||||
topicsTraversed.add(topic);
|
topicsTraversed.add(topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changes = new ChangeSet(chgs);
|
changes = new ChangeSet(chgs, sawHiddenChange);
|
||||||
newCs = completeChangeSetWithoutTopic(db, changes, user);
|
newCs = completeChangeSetWithoutTopic(db, changes, user);
|
||||||
}
|
}
|
||||||
return newCs;
|
return newCs;
|
||||||
|
|||||||
Reference in New Issue
Block a user