Merge branch 'stable-3.4-2021-07' into stable-3.4

* stable-3.4-2021-07:
  Set version to 3.4.2-SNAPSHOT
  Set version to 3.4.1
  Update Gitiles plugin
  Fix ListOfFilesDidNotChange
  IncludedIn: filter out non-visible branches
  Point gitiles submodule to gitiles security fix repo

Change-Id: I64c01c7d8845808638dc8a898ccf412c3535fbea
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index b6184d7..9d3446e 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -293,9 +293,9 @@
 patch-set is uploaded that has the same list of files as the previous
 patch-set.
 
-Renames are considered the same file when computing whether new files
-were added or old files were deleted. Hence, if there are only renames,
-scores will still be copied over.
+Renames are considered different files when computing whether new files
+were added or old files were deleted. Hence, if there are renames, scores will
+*NOT* be copied over.
 
 Defaults to false.
 
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index e30ce3a..8ecc7bc 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -2561,6 +2561,9 @@
 Retrieves the branches and tags in which a change is included. As result
 an link:rest-api-changes.html#included-in-info[IncludedInInfo] entity is returned.
 
+Branches that are not visible to the calling user according to the project's
+read permissions are filtered out from the result.
+
 .Request
 ----
   GET /projects/work%2Fmy-project/commits/a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96/in HTTP/1.0
diff --git a/java/com/google/gerrit/server/ApprovalInference.java b/java/com/google/gerrit/server/ApprovalInference.java
index d77427a..04d874c 100644
--- a/java/com/google/gerrit/server/ApprovalInference.java
+++ b/java/com/google/gerrit/server/ApprovalInference.java
@@ -29,11 +29,12 @@
 import com.google.gerrit.entities.Patch.ChangeType;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.client.ChangeKind;
-import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.server.change.ChangeKindCache;
 import com.google.gerrit.server.change.LabelNormalizer;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.TraceContext;
 import com.google.gerrit.server.logging.TraceContext.TraceTimer;
@@ -46,10 +47,14 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
+import java.util.stream.Collectors;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevWalk;
 
 /**
@@ -68,17 +73,20 @@
   private final ChangeKindCache changeKindCache;
   private final LabelNormalizer labelNormalizer;
   private final PatchListCache patchListCache;
+  private final GitRepositoryManager repositoryManager;
 
   @Inject
   ApprovalInference(
       ProjectCache projectCache,
       ChangeKindCache changeKindCache,
       LabelNormalizer labelNormalizer,
-      PatchListCache patchListCache) {
+      PatchListCache patchListCache,
+      GitRepositoryManager repositoryManager) {
     this.projectCache = projectCache;
     this.changeKindCache = changeKindCache;
     this.labelNormalizer = labelNormalizer;
     this.patchListCache = patchListCache;
+    this.repositoryManager = repositoryManager;
   }
 
   /**
@@ -111,7 +119,8 @@
       PatchSet.Id psId,
       ChangeKind kind,
       LabelType type,
-      @Nullable PatchList patchList) {
+      @Nullable PatchList patchListCurrentPatchset,
+      @Nullable PatchList patchListPriorPatchset) {
     int n = psa.key().patchSetId().get();
     checkArgument(n != psId.get());
 
@@ -172,11 +181,7 @@
           project.getName());
       return true;
     } else if (type.isCopyAllScoresIfListOfFilesDidNotChange()
-        && patchList.getPatches().stream()
-            .noneMatch(
-                p ->
-                    p.getChangeType() == ChangeType.ADDED
-                        || p.getChangeType() == ChangeType.DELETED)) {
+        && didListOfFilesNotChange(patchListCurrentPatchset, patchListPriorPatchset)) {
       logger.atFine().log(
           "approval %d on label %s of patch set %d of change %d can be copied"
               + " to patch set %d because the label has set "
@@ -308,6 +313,20 @@
     }
   }
 
+  private static boolean didListOfFilesNotChange(PatchList oldPatchList, PatchList newPatchList) {
+    Map<String, ChangeType> fileToChangeTypePs1 = getFileToChangeType(oldPatchList);
+    Map<String, ChangeType> fileToChangeTypePs2 = getFileToChangeType(newPatchList);
+    return fileToChangeTypePs1.equals(fileToChangeTypePs2);
+  }
+
+  private static Map<String, ChangeType> getFileToChangeType(PatchList ps) {
+    return ps.getPatches().stream()
+        .collect(
+            Collectors.toMap(
+                f -> f.getNewName() != null ? f.getNewName() : f.getOldName(),
+                f -> f.getChangeType()));
+  }
+
   private Collection<PatchSetApproval> getForPatchSetWithoutNormalization(
       ChangeNotes notes,
       ProjectState project,
@@ -368,7 +387,8 @@
     logger.atFine().log(
         "change kind for patch set %d of change %d against prior patch set %s is %s",
         ps.id().get(), ps.id().changeId().get(), priorPatchSet.getValue().id().changeId(), kind);
-    PatchList patchList = null;
+    PatchList patchListCurrentPatchset = null;
+    PatchList patchListPriorPatchset = null;
     LabelTypes labelTypes = project.getLabelTypes();
     for (PatchSetApproval psa : priorApprovals) {
       if (resultByUser.contains(psa.label(), psa.accountId())) {
@@ -376,10 +396,14 @@
       }
       LabelType type = labelTypes.byLabel(psa.labelId());
       // Only compute patchList if there is a relevant label, since this is expensive.
-      if (patchList == null && type != null && type.isCopyAllScoresIfListOfFilesDidNotChange()) {
-        patchList = getPatchList(project, ps, priorPatchSet);
+      if (patchListCurrentPatchset == null
+          && type != null
+          && type.isCopyAllScoresIfListOfFilesDidNotChange()) {
+        patchListCurrentPatchset = getPatchList(project, ps);
+        patchListPriorPatchset = getPatchList(project, priorPatchSet.getValue());
       }
-      if (!canCopy(project, psa, ps.id(), kind, type, patchList)) {
+      if (!canCopy(
+          project, psa, ps.id(), kind, type, patchListCurrentPatchset, patchListPriorPatchset)) {
         continue;
       }
       resultByUser.put(psa.label(), psa.accountId(), psa.copyWithPatchSet(ps.id()));
@@ -388,16 +412,18 @@
   }
 
   /**
-   * Gets the {@link PatchList} between the two latest patch-sets. Can be used to compute difference
-   * in files between those two patch-sets .
+   * Gets the {@link PatchList} between a patch-set and the base. Can be used to compute difference
+   * in files between two patch-sets by using both {@link PatchList}s of those 2 patch-sets.
    */
-  private PatchList getPatchList(
-      ProjectState project, PatchSet ps, Map.Entry<PatchSet.Id, PatchSet> priorPatchSet) {
+  private PatchList getPatchList(ProjectState project, PatchSet ps) {
+    // Compare against the base:
+    // * For merge commits the comparison is done against the 1st parent, which is the destination
+    //   branch.
+    // * For non-merge commits the comparison is done against the only parent, or an empty base if
+    //   no parent exists.
     PatchListKey key =
-        PatchListKey.againstCommit(
-            priorPatchSet.getValue().commitId(),
-            ps.commitId(),
-            DiffPreferencesInfo.Whitespace.IGNORE_NONE);
+        PatchListKey.againstBase(
+            ps.commitId(), getParentCount(project.getNameKey(), ps.commitId()));
     try {
       return patchListCache.get(key, project.getNameKey());
     } catch (PatchListNotAvailableException ex) {
@@ -408,4 +434,13 @@
           ex);
     }
   }
+
+  private int getParentCount(Project.NameKey project, ObjectId objectId) {
+    try (Repository repo = repositoryManager.openRepository(project);
+        RevWalk revWalk = new RevWalk(repo)) {
+      return revWalk.parseCommit(objectId).getParentCount();
+    } catch (IOException ex) {
+      throw new StorageException(ex);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/change/IncludedIn.java b/java/com/google/gerrit/server/change/IncludedIn.java
index 3c66c2c..c06ce82 100644
--- a/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/java/com/google/gerrit/server/change/IncludedIn.java
@@ -14,6 +14,12 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
+import static java.util.Comparator.naturalOrder;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
 import com.google.gerrit.entities.Project;
@@ -23,13 +29,18 @@
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import java.util.Collection;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -37,17 +48,21 @@
 @Singleton
 public class IncludedIn {
   private final GitRepositoryManager repoManager;
+  private final PermissionBackend permissionBackend;
   private final PluginSetContext<ExternalIncludedIn> externalIncludedIn;
 
   @Inject
   IncludedIn(
-      GitRepositoryManager repoManager, PluginSetContext<ExternalIncludedIn> externalIncludedIn) {
+      GitRepositoryManager repoManager,
+      PermissionBackend permissionBackend,
+      PluginSetContext<ExternalIncludedIn> externalIncludedIn) {
     this.repoManager = repoManager;
+    this.permissionBackend = permissionBackend;
     this.externalIncludedIn = externalIncludedIn;
   }
 
   public IncludedInInfo apply(Project.NameKey project, String revisionId)
-      throws RestApiException, IOException {
+      throws RestApiException, IOException, PermissionBackendException {
     try (Repository r = repoManager.openRepository(project);
         RevWalk rw = new RevWalk(r)) {
       rw.setRetainBody(false);
@@ -61,18 +76,48 @@
       }
 
       IncludedInResolver.Result d = IncludedInResolver.resolve(r, rw, rev);
+
+      // Filter branches and tags according to their visbility by the user
+      ImmutableSortedSet<String> filteredBranches =
+          sortedShortNames(filterReadableRefs(project, d.branches()));
+      ImmutableSortedSet<String> filteredTags =
+          sortedShortNames(filterReadableRefs(project, d.tags()));
+
       ListMultimap<String, String> external = MultimapBuilder.hashKeys().arrayListValues().build();
       externalIncludedIn.runEach(
           ext -> {
             ListMultimap<String, String> extIncludedIns =
-                ext.getIncludedIn(project.get(), rev.name(), d.tags(), d.branches());
+                ext.getIncludedIn(project.get(), rev.name(), filteredBranches, filteredTags);
             if (extIncludedIns != null) {
               external.putAll(extIncludedIns);
             }
           });
 
       return new IncludedInInfo(
-          d.branches(), d.tags(), (!external.isEmpty() ? external.asMap() : null));
+          filteredBranches, filteredTags, (!external.isEmpty() ? external.asMap() : null));
     }
   }
+
+  /**
+   * Filter readable branches or tags according to the caller's refs visibility.
+   *
+   * @param project specific Gerrit project.
+   * @param inputRefs a list of branches (in short name) as strings
+   */
+  private Collection<String> filterReadableRefs(
+      Project.NameKey project, ImmutableList<Ref> inputRefs)
+      throws IOException, PermissionBackendException {
+    PermissionBackend.ForProject perm = permissionBackend.currentUser().project(project);
+    try (Repository repo = repoManager.openRepository(project)) {
+      return perm.filter(inputRefs, repo, RefFilterOptions.defaults()).stream()
+          .map(Ref::getName)
+          .collect(toImmutableList());
+    }
+  }
+
+  private ImmutableSortedSet<String> sortedShortNames(Collection<String> refs) {
+    return refs.stream()
+        .map(Repository::shortenRefName)
+        .collect(toImmutableSortedSet(naturalOrder()));
+  }
 }
diff --git a/java/com/google/gerrit/server/change/IncludedInResolver.java b/java/com/google/gerrit/server/change/IncludedInResolver.java
index 09ca258..3891700 100644
--- a/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -14,13 +14,11 @@
 
 package com.google.gerrit.server.change;
 
-import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
 import static java.util.Comparator.comparing;
-import static java.util.Comparator.naturalOrder;
 import static java.util.stream.Collectors.toList;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
@@ -171,13 +169,12 @@
    * Returns the short names of refs which are as well in the matchingRefs list as well as in the
    * allRef list.
    */
-  private static ImmutableSortedSet<String> getMatchingRefNames(
+  private static ImmutableList<Ref> getMatchingRefNames(
       Set<String> matchingRefs, Collection<Ref> allRefs) {
     return allRefs.stream()
-        .map(Ref::getName)
-        .filter(matchingRefs::contains)
-        .map(Repository::shortenRefName)
-        .collect(toImmutableSortedSet(naturalOrder()));
+        .filter(r -> matchingRefs.contains(r.getName()))
+        .distinct()
+        .collect(ImmutableList.toImmutableList());
   }
 
   /** Parse commit of ref and store the relation between ref and commit. */
@@ -211,8 +208,8 @@
 
   @AutoValue
   public abstract static class Result {
-    public abstract ImmutableSortedSet<String> branches();
+    public abstract ImmutableList<Ref> branches();
 
-    public abstract ImmutableSortedSet<String> tags();
+    public abstract ImmutableList<Ref> tags();
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
index 67b5870..517fbdf 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeIncludedIn.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.IncludedIn;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -38,7 +39,8 @@
   }
 
   @Override
-  public Response<IncludedInInfo> apply(ChangeResource rsrc) throws RestApiException, IOException {
+  public Response<IncludedInInfo> apply(ChangeResource rsrc)
+      throws RestApiException, IOException, PermissionBackendException {
     PatchSet ps = psUtil.current(rsrc.getNotes());
     return Response.ok(includedIn.apply(rsrc.getProject(), ps.commitId().name()));
   }
diff --git a/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java b/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
index a4a82ce..e566858 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitIncludedIn.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.change.IncludedIn;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.CommitResource;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -36,7 +37,8 @@
   }
 
   @Override
-  public Response<IncludedInInfo> apply(CommitResource rsrc) throws RestApiException, IOException {
+  public Response<IncludedInInfo> apply(CommitResource rsrc)
+      throws RestApiException, IOException, PermissionBackendException {
     RevCommit commit = rsrc.getCommit();
     Project.NameKey project = rsrc.getProjectState().getNameKey();
     return Response.ok(includedIn.apply(project, commit.getId().getName()));
diff --git a/java/com/google/gerrit/truth/NullAwareCorrespondence.java b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
index 687ad94..5b107a6 100644
--- a/java/com/google/gerrit/truth/NullAwareCorrespondence.java
+++ b/java/com/google/gerrit/truth/NullAwareCorrespondence.java
@@ -7,15 +7,6 @@
 // http://www.apache.org/licenses/LICENSE-2.0
 //
 // Unless required by applicable law or agreed to in writing, software
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index 5c59129..4e47bb1 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -360,6 +361,46 @@
   }
 
   @Test
+  public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileAlreadyExists()
+      throws Exception {
+    try (ProjectConfigUpdate u = updateProject(project)) {
+      u.getConfig()
+          .updateLabelType(
+              LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true));
+      u.save();
+    }
+
+    // create "existing file" and submit it.
+    String existingFile = "existing file";
+    Change.Id prep =
+        changeOperations
+            .newChange()
+            .project(project)
+            .file(existingFile)
+            .content("content")
+            .create();
+    vote(admin, prep.toString(), 2, 1);
+    gApi.changes().id(prep.get()).current().submit();
+
+    Change.Id changeId = changeOperations.newChange().project(project).create();
+    vote(admin, changeId.toString(), 2, 1);
+    vote(user, changeId.toString(), -2, -1);
+
+    changeOperations
+        .change(changeId)
+        .newPatchset()
+        .file(existingFile)
+        .content("new content")
+        .create();
+    ChangeInfo c = detailedChange(changeId.toString());
+
+    // no votes are copied since the list of files changed ("existing file" was added to the
+    // change).
+    assertVotes(c, admin, 0, 0);
+    assertVotes(c, user, 0, 0);
+  }
+
+  @Test
   public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsDeleted()
       throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
@@ -405,7 +446,32 @@
   }
 
   @Test
-  public void stickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsRenamed() throws Exception {
+  @TestProjectInput(createEmptyCommit = false)
+  public void stickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsModifiedAsInitialCommit()
+      throws Exception {
+    try (ProjectConfigUpdate u = updateProject(project)) {
+      u.getConfig()
+          .updateLabelType(
+              LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true));
+      u.save();
+    }
+    Change.Id changeId =
+        changeOperations.newChange().project(project).file("file").content("content").create();
+    vote(admin, changeId.toString(), 2, 1);
+    vote(user, changeId.toString(), -2, -1);
+
+    changeOperations.change(changeId).newPatchset().file("file").content("new content").create();
+    ChangeInfo c = detailedChange(changeId.toString());
+
+    // only code review votes are copied since copyAllScoresIfListOfFilesDidNotChange is
+    // configured for that label, and list of files didn't change.
+    assertVotes(c, admin, 2, 0);
+    assertVotes(c, user, -2, 0);
+  }
+
+  @Test
+  public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsRenamed()
+      throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
           .updateLabelType(
@@ -420,10 +486,9 @@
     changeOperations.change(changeId).newPatchset().file("file").renameTo("new_file").create();
     ChangeInfo c = detailedChange(changeId.toString());
 
-    // only code review votes are copied since copyAllScoresIfListOfFilesDidNotChange is
-    // configured for that label, and list of files didn't change (rename is still the same file).
-    assertVotes(c, admin, 2, 0);
-    assertVotes(c, user, -2, 0);
+    // no votes are copied since the list of files changed (rename).
+    assertVotes(c, admin, 0, 0);
+    assertVotes(c, user, 0, 0);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
index bdb03d2..18e192d 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CommitIT.java
@@ -15,7 +15,10 @@
 package com.google.gerrit.acceptance.api.project;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static java.util.stream.Collectors.toList;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
@@ -28,6 +31,7 @@
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
 import com.google.gerrit.extensions.api.changes.IncludedInInfo;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -99,6 +103,53 @@
   }
 
   @Test
+  public void includedInMergedChange_filtersOutNonVisibleBranches() throws Exception {
+    Result baseChange = createAndSubmitChange("refs/for/master");
+
+    createBranch(BranchNameKey.create(project, "test-branch-1"));
+    createBranch(BranchNameKey.create(project, "test-branch-2"));
+    createAndSubmitChange("refs/for/test-branch-1");
+    createAndSubmitChange("refs/for/test-branch-2");
+
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
+        .containsExactly("master", "test-branch-1", "test-branch-2");
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
+        .update();
+
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).branches)
+        .containsExactly("master", "test-branch-2");
+  }
+
+  @Test
+  public void includedInMergedChange_filtersOutNonVisibleTags() throws Exception {
+    String tagBase = "tag_base";
+    String tagBranch1 = "tag_1";
+
+    Result baseChange = createAndSubmitChange("refs/for/master");
+    createLightWeightTag(tagBase);
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
+
+    createBranch(BranchNameKey.create(project, "test-branch-1"));
+    createAndSubmitChange("refs/for/test-branch-1");
+    createLightWeightTag(tagBranch1);
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).tags)
+        .containsExactly(tagBase, tagBranch1);
+
+    projectOperations
+        .project(project)
+        .forUpdate()
+        // Tag permissions are controlled by read permissions on branches. Blocking read permission
+        // on test-branch-1 so that tagBranch1 becomes non-visible
+        .add(block(Permission.READ).ref("refs/heads/test-branch-1").group(REGISTERED_USERS))
+        .update();
+    assertThat(getIncludedIn(baseChange.getCommit().getId()).tags).containsExactly(tagBase);
+  }
+
+  @Test
   public void cherryPickWithoutMessageSameBranch() throws Exception {
     String destBranch = "master";
 
@@ -390,4 +441,15 @@
     assertThat(actual.email).isEqualTo(expected.email());
     assertThat(actual.name).isEqualTo(expected.fullName());
   }
+
+  private Result createAndSubmitChange(String branch) throws Exception {
+    Result r = createChange(branch);
+    approve(r.getChangeId());
+    gApi.changes().id(r.getChangeId()).current().submit();
+    return r;
+  }
+
+  private void createLightWeightTag(String tagName) throws Exception {
+    pushHead(testRepo, RefNames.REFS_TAGS + tagName, false, false);
+  }
 }
diff --git a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
index 19c479d..b69a894 100644
--- a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
+++ b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
@@ -17,9 +17,13 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.entities.RefNames.REFS_TAGS;
 
+import com.google.common.truth.Correspondence;
+import com.google.gerrit.truth.NullAwareCorrespondence;
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTag;
@@ -112,8 +116,12 @@
     IncludedInResolver.Result detail = resolve(commit_v2_5);
 
     // Check that only tags and branches which refer the tip are returned
-    assertThat(detail.tags()).containsExactly(TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
-    assertThat(detail.branches()).containsExactly(BRANCH_2_5);
+    assertThat(detail.tags())
+        .comparingElementsUsing(hasShortName())
+        .containsExactly(TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
+    assertThat(detail.branches())
+        .comparingElementsUsing(hasShortName())
+        .containsExactly(BRANCH_2_5);
   }
 
   @Test
@@ -123,6 +131,7 @@
 
     // Check whether all tags and branches are returned
     assertThat(detail.tags())
+        .comparingElementsUsing(hasShortName())
         .containsExactly(
             TAG_1_0,
             TAG_1_0_1,
@@ -133,6 +142,7 @@
             TAG_2_5_ANNOTATED,
             TAG_2_5_ANNOTATED_TWICE);
     assertThat(detail.branches())
+        .comparingElementsUsing(hasShortName())
         .containsExactly(BRANCH_MASTER, BRANCH_1_0, BRANCH_1_3, BRANCH_2_0, BRANCH_2_5);
   }
 
@@ -143,8 +153,11 @@
 
     // Check whether all succeeding tags and branches are returned
     assertThat(detail.tags())
+        .comparingElementsUsing(hasShortName())
         .containsExactly(TAG_1_3, TAG_2_5, TAG_2_5_ANNOTATED, TAG_2_5_ANNOTATED_TWICE);
-    assertThat(detail.branches()).containsExactly(BRANCH_1_3, BRANCH_2_5);
+    assertThat(detail.branches())
+        .comparingElementsUsing(hasShortName())
+        .containsExactly(BRANCH_1_3, BRANCH_2_5);
   }
 
   private IncludedInResolver.Result resolve(RevCommit commit) throws Exception {
@@ -154,4 +167,9 @@
   private RevTag tag(String name, RevObject dest) throws Exception {
     return tr.update(REFS_TAGS + name, tr.tag(name, dest));
   }
+
+  private static Correspondence<Ref, String> hasShortName() {
+    return NullAwareCorrespondence.transforming(
+        ref -> Repository.shortenRefName(ref.getName()), "has short name");
+  }
 }
diff --git a/plugins/gitiles b/plugins/gitiles
index b196dd5..e7a4e55 160000
--- a/plugins/gitiles
+++ b/plugins/gitiles
@@ -1 +1 @@
-Subproject commit b196dd5b6fcfd50518a6625a64cb93424c084620
+Subproject commit e7a4e5521d59abaf6942c48f52bbe9e4bec1bf29
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index b70be05..fa7c443 100644
--- a/tools/maven/gerrit-acceptance-framework_pom.xml
+++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-acceptance-framework</artifactId>
-  <version>3.4.1-SNAPSHOT</version>
+  <version>3.4.2-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Acceptance Test Framework</name>
   <description>Framework for Gerrit's acceptance tests</description>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml
index b5c1c5c..cc56eb9 100644
--- a/tools/maven/gerrit-extension-api_pom.xml
+++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-extension-api</artifactId>
-  <version>3.4.1-SNAPSHOT</version>
+  <version>3.4.2-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Extension API</name>
   <description>API for Gerrit Extensions</description>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
index 7d7a63e..cc6871e 100644
--- a/tools/maven/gerrit-plugin-api_pom.xml
+++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-api</artifactId>
-  <version>3.4.1-SNAPSHOT</version>
+  <version>3.4.2-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index 14d1037..08c7909 100644
--- a/tools/maven/gerrit-war_pom.xml
+++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-war</artifactId>
-  <version>3.4.1-SNAPSHOT</version>
+  <version>3.4.2-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
diff --git a/version.bzl b/version.bzl
index c4b984b..4880e98 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = "3.4.1-SNAPSHOT"
+GERRIT_VERSION = "3.4.2-SNAPSHOT"