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