Merge changes I756c55f8,Ic9f0dd6d,I05a330de into stable-3.5

* changes:
  Add REST endpoint for commits included in refs
  Use JGit's RevWalk.getMergedInto instead of IncludedInResolver
  Update JGit to 60b81c5a9280e44fa48d533a61f915382b2b9ce2
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 92c6dbf..2d54518 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1541,6 +1541,47 @@
   }
 ----
 
+[[commits-included-in]]
+=== Get Commits Included In Refs
+--
+'GET /projects/link:#project-name[\{project-name\}]/commits:in'
+--
+
+Gets refs in which the specified commits were merged into. Returns a map of commits
+to sets of full ref names.
+
+One or more `commit` query parameters are required and each specifies a
+commit-id (SHA-1 in 40 digit hex representation). Commits will not be contained
+in the map if they do not exist or are not reachable from visible, specified refs.
+
+One or more `ref` query parameters are required and each specifies a full ref name.
+Refs which are not visible to the calling user according to the project's read
+permissions and refs which do not exist will be filtered out from the result.
+
+.Request
+----
+  GET /projects/work%2Fmy-project/commits:in?commit=a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96&commit=6d2a3adb10e844c33617fc948dbeb88e868d396e&ref=refs/heads/master&ref=refs/heads/branch1 HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96": [
+      "refs/heads/master"
+    ],
+    "6d2a3adb10e844c33617fc948dbeb88e868d396e": [
+      "refs/heads/branch1",
+      "refs/heads/master"
+    ]
+  }
+
+----
+
 [[branch-endpoints]]
 == Branch Endpoints
 
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 9873995..59475a4 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -24,7 +24,10 @@
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public interface ProjectApi {
   ProjectApi create() throws RestApiException;
@@ -51,6 +54,9 @@
 
   ConfigInfo config(ConfigInput in) throws RestApiException;
 
+  Map<String, Set<String>> commitsIn(Collection<String> commits, Collection<String> refs)
+      throws RestApiException;
+
   ListRefsRequest<BranchInfo> branches();
 
   ListRefsRequest<TagInfo> tags();
@@ -289,6 +295,12 @@
     }
 
     @Override
+    public Map<String, Set<String>> commitsIn(Collection<String> commits, Collection<String> refs)
+        throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public void description(DescriptionInput in) throws RestApiException {
       throw new NotImplementedException();
     }
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 6d7fc15..9521759 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -63,6 +63,7 @@
 import com.google.gerrit.server.restapi.project.CheckAccess;
 import com.google.gerrit.server.restapi.project.ChildProjectsCollection;
 import com.google.gerrit.server.restapi.project.CommitsCollection;
+import com.google.gerrit.server.restapi.project.CommitsIncludedInRefs;
 import com.google.gerrit.server.restapi.project.CreateAccessChange;
 import com.google.gerrit.server.restapi.project.CreateProject;
 import com.google.gerrit.server.restapi.project.DeleteBranches;
@@ -88,8 +89,11 @@
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 public class ProjectApiImpl implements ProjectApi {
   interface Factory {
@@ -116,6 +120,7 @@
   private final CreateAccessChange createAccessChange;
   private final GetConfig getConfig;
   private final PutConfig putConfig;
+  private final CommitsIncludedInRefs commitsIncludedInRefs;
   private final Provider<ListBranches> listBranches;
   private final Provider<ListTags> listTags;
   private final DeleteBranches deleteBranches;
@@ -154,6 +159,7 @@
       CreateAccessChange createAccessChange,
       GetConfig getConfig,
       PutConfig putConfig,
+      CommitsIncludedInRefs commitsIncludedInRefs,
       Provider<ListBranches> listBranches,
       Provider<ListTags> listTags,
       DeleteBranches deleteBranches,
@@ -191,6 +197,7 @@
         createAccessChange,
         getConfig,
         putConfig,
+        commitsIncludedInRefs,
         listBranches,
         listTags,
         deleteBranches,
@@ -232,6 +239,7 @@
       CreateAccessChange createAccessChange,
       GetConfig getConfig,
       PutConfig putConfig,
+      CommitsIncludedInRefs commitsIncludedInRefs,
       Provider<ListBranches> listBranches,
       Provider<ListTags> listTags,
       DeleteBranches deleteBranches,
@@ -269,6 +277,7 @@
         createAccessChange,
         getConfig,
         putConfig,
+        commitsIncludedInRefs,
         listBranches,
         listTags,
         deleteBranches,
@@ -309,6 +318,7 @@
       CreateAccessChange createAccessChange,
       GetConfig getConfig,
       PutConfig putConfig,
+      CommitsIncludedInRefs commitsIncludedInRefs,
       Provider<ListBranches> listBranches,
       Provider<ListTags> listTags,
       DeleteBranches deleteBranches,
@@ -346,6 +356,7 @@
     this.setAccess = setAccess;
     this.getConfig = getConfig;
     this.putConfig = putConfig;
+    this.commitsIncludedInRefs = commitsIncludedInRefs;
     this.listBranches = listBranches;
     this.listTags = listTags;
     this.deleteBranches = deleteBranches;
@@ -483,6 +494,18 @@
   }
 
   @Override
+  public Map<String, Set<String>> commitsIn(Collection<String> commits, Collection<String> refs)
+      throws RestApiException {
+    try {
+      commitsIncludedInRefs.addCommits(commits);
+      commitsIncludedInRefs.addRefs(refs);
+      return commitsIncludedInRefs.apply(project).value();
+    } catch (Exception e) {
+      throw asRestApiException("Cannot list commits included in refs", e);
+    }
+  }
+
+  @Override
   public ListRefsRequest<BranchInfo> branches() {
     return new ListRefsRequest<BranchInfo>() {
       @Override
diff --git a/java/com/google/gerrit/server/change/IncludedIn.java b/java/com/google/gerrit/server/change/IncludedIn.java
index c06ce82..94498d7 100644
--- a/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/java/com/google/gerrit/server/change/IncludedIn.java
@@ -21,6 +21,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
 import com.google.common.collect.MultimapBuilder;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.changes.IncludedInInfo;
@@ -37,10 +38,15 @@
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -75,13 +81,26 @@
         throw new ResourceConflictException(err.getMessage());
       }
 
-      IncludedInResolver.Result d = IncludedInResolver.resolve(r, rw, rev);
+      RefDatabase refDb = r.getRefDatabase();
+      Collection<Ref> tags = refDb.getRefsByPrefix(Constants.R_TAGS);
+      Collection<Ref> branches = refDb.getRefsByPrefix(Constants.R_HEADS);
+      List<Ref> allTagsAndBranches = Lists.newArrayListWithCapacity(tags.size() + branches.size());
+      allTagsAndBranches.addAll(tags);
+      allTagsAndBranches.addAll(branches);
+
+      Set<String> allMatchingTagsAndBranches =
+          rw.getMergedInto(rev, IncludedInUtil.getSortedRefs(allTagsAndBranches, rw)).stream()
+              .map(Ref::getName)
+              .collect(Collectors.toSet());
 
       // Filter branches and tags according to their visbility by the user
       ImmutableSortedSet<String> filteredBranches =
-          sortedShortNames(filterReadableRefs(project, d.branches()));
+          sortedShortNames(
+              filterReadableRefs(
+                  project, getMatchingRefNames(allMatchingTagsAndBranches, branches)));
       ImmutableSortedSet<String> filteredTags =
-          sortedShortNames(filterReadableRefs(project, d.tags()));
+          sortedShortNames(
+              filterReadableRefs(project, getMatchingRefNames(allMatchingTagsAndBranches, tags)));
 
       ListMultimap<String, String> external = MultimapBuilder.hashKeys().arrayListValues().build();
       externalIncludedIn.runEach(
@@ -115,6 +134,18 @@
     }
   }
 
+  /**
+   * Returns the short names of refs which are as well in the matchingRefs list as well as in the
+   * allRef list.
+   */
+  private static ImmutableList<Ref> getMatchingRefNames(
+      Set<String> matchingRefs, Collection<Ref> allRefs) {
+    return allRefs.stream()
+        .filter(r -> matchingRefs.contains(r.getName()))
+        .distinct()
+        .collect(toImmutableList());
+  }
+
   private ImmutableSortedSet<String> sortedShortNames(Collection<String> refs) {
     return refs.stream()
         .map(Repository::shortenRefName)
diff --git a/java/com/google/gerrit/server/change/IncludedInRefs.java b/java/com/google/gerrit/server/change/IncludedInRefs.java
new file mode 100644
index 0000000..f069251
--- /dev/null
+++ b/java/com/google/gerrit/server/change/IncludedInRefs.java
@@ -0,0 +1,139 @@
+// Copyright (C) 2021 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
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static java.util.stream.Collectors.toSet;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+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.inject.Inject;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class IncludedInRefs {
+  protected final GitRepositoryManager repoManager;
+  protected final PermissionBackend permissionBackend;
+
+  @Inject
+  IncludedInRefs(GitRepositoryManager repoManager, PermissionBackend permissionBackend) {
+    this.repoManager = repoManager;
+    this.permissionBackend = permissionBackend;
+  }
+
+  public Map<String, Set<String>> apply(
+      Project.NameKey project, Set<String> commits, Set<String> refNames)
+      throws ResourceConflictException, BadRequestException, IOException,
+          PermissionBackendException, ResourceNotFoundException, AuthException {
+    try (Repository repo = repoManager.openRepository(project)) {
+      Set<Ref> visibleRefs = getVisibleRefs(repo, refNames, project);
+
+      if (!visibleRefs.isEmpty()) {
+        try (RevWalk revWalk = new RevWalk(repo)) {
+          revWalk.setRetainBody(false);
+          Set<RevCommit> revCommits = getRevCommits(commits, revWalk);
+
+          if (!revCommits.isEmpty()) {
+            return commitsIncludedIn(
+                revCommits, IncludedInUtil.getSortedRefs(visibleRefs, revWalk), revWalk);
+          }
+        }
+      }
+    }
+    return Collections.EMPTY_MAP;
+  }
+
+  private Set<Ref> getVisibleRefs(Repository repo, Set<String> refNames, Project.NameKey project)
+      throws PermissionBackendException {
+    RefDatabase refDb = repo.getRefDatabase();
+    Set<Ref> refs = new HashSet<>();
+    for (String refName : refNames) {
+      try {
+        Ref ref = refDb.exactRef(refName);
+        if (ref != null) {
+          refs.add(ref);
+        }
+      } catch (IOException e) {
+        // Ignore and continue to process rest of the refs so as to keep
+        // the behavior similar to the ref not being visible to the user.
+        // This will ensure that there is no information leak about the
+        // ref when the ref is corrupted and is not visible to the user.
+      }
+    }
+    return filterReadableRefs(project, refs, repo);
+  }
+
+  private Set<RevCommit> getRevCommits(Set<String> commits, RevWalk revWalk) throws IOException {
+    Set<RevCommit> revCommits = new HashSet<>();
+    for (String commit : commits) {
+      try {
+        revCommits.add(revWalk.parseCommit(ObjectId.fromString(commit)));
+      } catch (MissingObjectException | IncorrectObjectTypeException | IllegalArgumentException e) {
+        // Ignore and continue to process the rest of the commits so as to keep
+        // the behavior similar to the commit not being included in any of the
+        // visible specified refs. This will ensure that there is no information
+        // leak about the commit when the commit is not visible to the user.
+      }
+    }
+    return revCommits;
+  }
+
+  private Map<String, Set<String>> commitsIncludedIn(
+      Collection<RevCommit> commits, Collection<Ref> refs, RevWalk revWalk) throws IOException {
+    Map<String, Set<String>> refsByCommit = new HashMap<>();
+    for (RevCommit commit : commits) {
+      List<Ref> matchingRefs = revWalk.getMergedInto(commit, refs);
+      if (matchingRefs.size() > 0) {
+        refsByCommit.put(
+            commit.getName(), matchingRefs.stream().map(Ref::getName).collect(toSet()));
+      }
+    }
+    return refsByCommit;
+  }
+
+  /**
+   * Filter readable refs according to the caller's refs visibility.
+   *
+   * @param project specific Gerrit project.
+   * @param inputRefs a list of refs
+   * @param repo repository opened for the Gerrit project.
+   * @return set of visible refs to the caller
+   */
+  private Set<Ref> filterReadableRefs(Project.NameKey project, Set<Ref> inputRefs, Repository repo)
+      throws PermissionBackendException {
+    PermissionBackend.ForProject perm = permissionBackend.currentUser().project(project);
+    return perm.filter(inputRefs, repo, RefFilterOptions.defaults()).stream().collect(toSet());
+  }
+}
diff --git a/java/com/google/gerrit/server/change/IncludedInResolver.java b/java/com/google/gerrit/server/change/IncludedInResolver.java
deleted file mode 100644
index b2b0a64..0000000
--- a/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (C) 2013 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
-// limitations under the License.
-
-package com.google.gerrit.server.change;
-
-import static java.util.Comparator.comparing;
-import static java.util.stream.Collectors.toList;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.flogger.FluentLogger;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-/** Resolve in which tags and branches a commit is included. */
-public class IncludedInResolver {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  public static Result resolve(Repository repo, RevWalk rw, RevCommit commit) throws IOException {
-    RevFlag flag = newFlag(rw);
-    try {
-      return new IncludedInResolver(repo, rw, commit, flag).resolve();
-    } finally {
-      rw.disposeFlag(flag);
-    }
-  }
-
-  private static RevFlag newFlag(RevWalk rw) {
-    return rw.newFlag("CONTAINS_TARGET");
-  }
-
-  private final Repository repo;
-  private final RevWalk rw;
-  private final RevCommit target;
-
-  private final RevFlag containsTarget;
-  private ListMultimap<RevCommit, String> commitToRef;
-  private List<RevCommit> tipsByCommitTime;
-
-  private IncludedInResolver(
-      Repository repo, RevWalk rw, RevCommit target, RevFlag containsTarget) {
-    this.repo = repo;
-    this.rw = rw;
-    this.target = target;
-    this.containsTarget = containsTarget;
-  }
-
-  private Result resolve() throws IOException {
-    RefDatabase refDb = repo.getRefDatabase();
-    Collection<Ref> tags = refDb.getRefsByPrefix(Constants.R_TAGS);
-    Collection<Ref> branches = refDb.getRefsByPrefix(Constants.R_HEADS);
-    List<Ref> allTagsAndBranches = Lists.newArrayListWithCapacity(tags.size() + branches.size());
-    allTagsAndBranches.addAll(tags);
-    allTagsAndBranches.addAll(branches);
-    parseCommits(allTagsAndBranches);
-    Set<String> allMatchingTagsAndBranches = includedIn(tipsByCommitTime, 0);
-
-    return new AutoValue_IncludedInResolver_Result(
-        getMatchingRefNames(allMatchingTagsAndBranches, branches),
-        getMatchingRefNames(allMatchingTagsAndBranches, tags));
-  }
-
-  /** Resolves which tip refs include the target commit. */
-  private Set<String> includedIn(Collection<RevCommit> tips, int limit)
-      throws IOException, MissingObjectException, IncorrectObjectTypeException {
-    Set<String> result = new HashSet<>();
-    for (RevCommit tip : tips) {
-      boolean commitFound = false;
-      rw.resetRetain(RevFlag.UNINTERESTING, containsTarget);
-      rw.markStart(tip);
-      for (RevCommit commit : rw) {
-        if (commit.equals(target) || commit.has(containsTarget)) {
-          commitFound = true;
-          tip.add(containsTarget);
-          result.addAll(commitToRef.get(tip));
-          break;
-        }
-      }
-      if (!commitFound) {
-        rw.markUninteresting(tip);
-      } else if (0 < limit && limit < result.size()) {
-        break;
-      }
-    }
-    return result;
-  }
-
-  /**
-   * Returns the short names of refs which are as well in the matchingRefs list as well as in the
-   * allRef list.
-   */
-  private static ImmutableList<Ref> getMatchingRefNames(
-      Set<String> matchingRefs, Collection<Ref> allRefs) {
-    return allRefs.stream()
-        .filter(r -> matchingRefs.contains(r.getName()))
-        .distinct()
-        .collect(ImmutableList.toImmutableList());
-  }
-
-  /** Parse commit of ref and store the relation between ref and commit. */
-  private void parseCommits(Collection<Ref> refs) throws IOException {
-    if (commitToRef != null) {
-      return;
-    }
-    commitToRef = LinkedListMultimap.create();
-    for (Ref ref : refs) {
-      final RevCommit commit;
-      try {
-        commit = rw.parseCommit(ref.getObjectId());
-      } catch (IncorrectObjectTypeException notCommit) {
-        // Its OK for a tag reference to point to a blob or a tree, this
-        // is common in the Linux kernel or git.git repository.
-        //
-        continue;
-      } catch (MissingObjectException notHere) {
-        // Log the problem with this branch, but keep processing.
-        //
-        logger.atWarning().log(
-            "Reference %s in %s points to dangling object %s",
-            ref.getName(), repo.getDirectory(), ref.getObjectId());
-        continue;
-      }
-      commitToRef.put(commit, ref.getName());
-    }
-    tipsByCommitTime =
-        commitToRef.keySet().stream().sorted(comparing(RevCommit::getCommitTime)).collect(toList());
-  }
-
-  @AutoValue
-  public abstract static class Result {
-    public abstract ImmutableList<Ref> branches();
-
-    public abstract ImmutableList<Ref> tags();
-  }
-}
diff --git a/java/com/google/gerrit/server/change/IncludedInUtil.java b/java/com/google/gerrit/server/change/IncludedInUtil.java
new file mode 100644
index 0000000..6f75e0f
--- /dev/null
+++ b/java/com/google/gerrit/server/change/IncludedInUtil.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2021 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
+// limitations under the License.
+
+package com.google.gerrit.server.change;
+
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class IncludedInUtil {
+
+  /**
+   * Sorts the collection of {@code Ref} instances by its tip commit time.
+   *
+   * @param refs collection to be sorted
+   * @param revWalk {@code RevWalk} instance for parsing ref's tip commit
+   * @return sorted list of refs
+   */
+  public static List<Ref> getSortedRefs(Collection<Ref> refs, RevWalk revWalk) {
+    return refs.stream()
+        .sorted(
+            comparing(
+                ref -> {
+                  try {
+                    return revWalk.parseCommit(ref.getObjectId()).getCommitTime();
+                  } catch (IOException e) {
+                    // Ignore and continue to sort
+                  }
+                  return 0;
+                }))
+        .collect(toList());
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitsIncludedInRefs.java b/java/com/google/gerrit/server/restapi/project/CommitsIncludedInRefs.java
new file mode 100644
index 0000000..05ec28e
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/CommitsIncludedInRefs.java
@@ -0,0 +1,85 @@
+// Copyright (C) 2021 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
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.project;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.IncludedInRefs;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.kohsuke.args4j.Option;
+
+public class CommitsIncludedInRefs implements RestReadView<ProjectResource> {
+  protected final IncludedInRefs includedInRefs;
+
+  protected Set<String> commits = new HashSet<>();
+  protected Set<String> refs = new HashSet<>();
+
+  @Option(
+      name = "--commit",
+      aliases = {"-c"},
+      required = true,
+      metaVar = "COMMIT",
+      usage = "commit sha1")
+  protected void addCommit(String commit) {
+    commits.add(commit);
+  }
+
+  @Option(
+      name = "--ref",
+      aliases = {"-r"},
+      required = true,
+      metaVar = "REF",
+      usage = "full ref name")
+  protected void addRef(String ref) {
+    refs.add(ref);
+  }
+
+  public void addCommits(Collection<String> commits) {
+    this.commits.addAll(commits);
+  }
+
+  public void addRefs(Collection<String> refs) {
+    this.refs.addAll(refs);
+  }
+
+  @Inject
+  public CommitsIncludedInRefs(IncludedInRefs includedInRefs) {
+    this.includedInRefs = includedInRefs;
+  }
+
+  @Override
+  public Response<Map<String, Set<String>>> apply(ProjectResource resource)
+      throws ResourceConflictException, BadRequestException, IOException,
+          PermissionBackendException, ResourceNotFoundException, AuthException {
+    if (commits.isEmpty()) {
+      throw new BadRequestException("commit is required");
+    }
+    if (refs.isEmpty()) {
+      throw new BadRequestException("ref is required");
+    }
+    return Response.ok(includedInRefs.apply(resource.getNameKey(), commits, refs));
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java b/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java
index 410a88c8..e50a494 100644
--- a/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java
+++ b/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java
@@ -99,6 +99,7 @@
     get(FILE_KIND, "content").to(GetContent.class);
 
     child(PROJECT_KIND, "commits").to(CommitsCollection.class);
+    get(PROJECT_KIND, "commits:in").to(CommitsIncludedInRefs.class);
     get(COMMIT_KIND).to(GetCommit.class);
     get(COMMIT_KIND, "in").to(CommitIncludedIn.class);
     child(COMMIT_KIND, "files").to(FilesInCommitCollection.class);
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index c42628c..6b2f4f1 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -16,20 +16,25 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.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.server.project.ProjectState.INHERITED_FROM_GLOBAL;
 import static com.google.gerrit.server.project.ProjectState.INHERITED_FROM_PARENT;
 import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_GLOBAL;
 import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_PARENT;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static java.util.stream.Collectors.toSet;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.AtomicLongMap;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.ExtensionRegistry;
@@ -41,6 +46,7 @@
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.GroupReference;
 import com.google.gerrit.entities.LabelId;
@@ -74,9 +80,13 @@
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Module;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
@@ -92,6 +102,7 @@
   private static final String JIRA = "jira";
   private static final String JIRA_LINK = "http://jira.example.com/?id=$2";
   private static final String JIRA_MATCH = "(jira\\\\s+#?)(\\\\d+)";
+  private static final String R_HEADS_MASTER = R_HEADS + "master";
 
   @Inject private ProjectOperations projectOperations;
   @Inject private RequestScopeOperations requestScopeOperations;
@@ -1000,6 +1011,156 @@
     assertThat(afterRename).hasValue(newName);
   }
 
+  @Test
+  public void commitsIncludedInRefsEmptyCommitAndEmptyRefs() throws Exception {
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class,
+            () -> getCommitsIncludedInRefs(Collections.emptyList(), Arrays.asList(R_HEADS_MASTER)));
+    assertThat(thrown).hasMessageThat().contains("commit is required");
+    PushOneCommit.Result result = createChange();
+    thrown =
+        assertThrows(
+            BadRequestException.class,
+            () -> getCommitsIncludedInRefs(result.getCommit().getName(), Collections.emptyList()));
+    assertThat(thrown).hasMessageThat().contains("ref is required");
+  }
+
+  @Test
+  public void commitsIncludedInRefsNonExistentCommits() throws Exception {
+    assertThat(
+            getCommitsIncludedInRefs(
+                Arrays.asList("foo", "4fa12ab8f257034ec793dacb2ae2752ae2e9f5f3"),
+                Arrays.asList(R_HEADS_MASTER)))
+        .isEmpty();
+  }
+
+  @Test
+  public void commitsIncludedInRefsNonExistentRefs() throws Exception {
+    PushOneCommit.Result change = createChange();
+    assertThat(
+            getCommitsIncludedInRefs(
+                Arrays.asList(change.getCommit().getName()), Arrays.asList(R_HEADS + "foo")))
+        .isEmpty();
+  }
+
+  @Test
+  public void commitsIncludedInRefsOpenChange() throws Exception {
+    PushOneCommit.Result change1 = createChange();
+    testRepo.reset(change1.getCommit().getParent(0));
+    PushOneCommit.Result change2 = createChange();
+
+    Map<String, Set<String>> refsByCommit =
+        getCommitsIncludedInRefs(
+            Arrays.asList(change1.getCommit().getName(), change2.getCommit().getName()),
+            Arrays.asList(
+                R_HEADS_MASTER, change1.getPatchSet().refName(), change2.getPatchSet().refName()));
+    assertThat(refsByCommit.get(change2.getCommit().getName()))
+        .containsExactly(change2.getPatchSet().refName());
+    assertThat(refsByCommit.get(change1.getCommit().getName()))
+        .containsExactly(change1.getPatchSet().refName());
+  }
+
+  @Test
+  public void commitsIncludedInRefsMergedChange() throws Exception {
+    String branchWithoutChanges = R_HEADS + "branch-without-changes";
+    String tagWithChange1 = R_TAGS + "tag-with-change1";
+    String branchWithChange1 = R_HEADS + "branch-with-change1";
+
+    createBranch(BranchNameKey.create(project, "branch-without-changes"));
+    PushOneCommit.Result change1 = createAndSubmitChange("refs/for/master");
+
+    assertThat(
+            getCommitsIncludedInRefs(change1.getCommit().getName(), Arrays.asList(R_HEADS_MASTER)))
+        .containsExactly(R_HEADS_MASTER);
+    assertThat(
+            getCommitsIncludedInRefs(
+                change1.getCommit().getName(), Arrays.asList(R_HEADS_MASTER, branchWithoutChanges)))
+        .containsExactly(R_HEADS_MASTER);
+
+    pushHead(testRepo, tagWithChange1, false, false);
+    createBranch(BranchNameKey.create(project, "branch-with-change1"));
+
+    PushOneCommit.Result change2 = createAndSubmitChange("refs/for/master");
+    Map<String, Set<String>> refsByCommit =
+        getCommitsIncludedInRefs(
+            Arrays.asList(change1.getCommit().getName(), change2.getCommit().getName()),
+            Arrays.asList(R_HEADS_MASTER, tagWithChange1, branchWithoutChanges, branchWithChange1));
+    assertThat(refsByCommit.get(change1.getCommit().getName()))
+        .containsExactly(R_HEADS_MASTER, tagWithChange1, branchWithChange1);
+    assertThat(refsByCommit.get(change2.getCommit().getName())).containsExactly(R_HEADS_MASTER);
+  }
+
+  @Test
+  public void commitsIncludedInRefsMergedChangeNonTipCommit() throws Exception {
+    String branchWithChange1 = R_HEADS + "branch-with-change1";
+    String tagWithChange1 = R_TAGS + "tag-with-change1";
+    String branchWithChange1Change2 = R_HEADS + "branch-with-change1-change2";
+    String tagWithChange1Change2 = R_TAGS + "tag-with-change1-change2";
+
+    createBranch(BranchNameKey.create(project, "branch-with-change1"));
+    PushOneCommit.Result change1 = createAndSubmitChange("refs/for/branch-with-change1");
+    pushHead(testRepo, tagWithChange1, false, false);
+    createBranch(BranchNameKey.create(project, "branch-with-change1-change2"));
+    PushOneCommit.Result change2 = createAndSubmitChange("refs/for/branch-with-change1-change2");
+    pushHead(testRepo, tagWithChange1Change2, false, false);
+
+    Map<String, Set<String>> refsByCommit =
+        getCommitsIncludedInRefs(
+            Arrays.asList(change1.getCommit().getName(), change2.getCommit().getName()),
+            Arrays.asList(
+                branchWithChange1,
+                branchWithChange1Change2,
+                tagWithChange1,
+                tagWithChange1Change2));
+    assertThat(refsByCommit.get(change1.getCommit().getName()))
+        .containsExactly(
+            branchWithChange1, branchWithChange1Change2, tagWithChange1, tagWithChange1Change2);
+    assertThat(refsByCommit.get(change2.getCommit().getName()))
+        .containsExactly(branchWithChange1Change2, tagWithChange1Change2);
+  }
+
+  @Test
+  public void commitsIncludedInRefsMergedChangeFilterNonVisibleBranchRef() throws Exception {
+    String nonVisibleBranch = R_HEADS + "non-visible-branch";
+    PushOneCommit.Result change = createAndSubmitChange("refs/for/master");
+    createBranch(BranchNameKey.create(project, "non-visible-branch"));
+    blockReadPermission(nonVisibleBranch);
+
+    assertThat(
+            getCommitsIncludedInRefs(
+                change.getCommit().getName(), Arrays.asList(R_HEADS_MASTER, nonVisibleBranch)))
+        .containsExactly(R_HEADS_MASTER);
+  }
+
+  @Test
+  public void commitsIncludedInRefsMergedChangeFilterNonVisibleTagRef() throws Exception {
+    String nonVisibleTag = R_TAGS + "non-visible-tag";
+    PushOneCommit.Result change = createAndSubmitChange("refs/for/master");
+    pushHead(testRepo, nonVisibleTag, false, false);
+    // Tag permissions are controlled by read permissions on branches. Blocking read permission
+    // on master so that tag-with-change becomes non-visible
+    blockReadPermission(R_HEADS_MASTER);
+
+    assertThat(
+            getCommitsIncludedInRefs(
+                change.getCommit().getName(), Arrays.asList(R_HEADS_MASTER, nonVisibleTag)))
+        .isNull();
+  }
+
+  @Test
+  public void commitsIncludedInRefsFilterNonVisibleChangeRef() throws Exception {
+    PushOneCommit.Result change = createChange("refs/for/master");
+    // change ref permissions are controlled by read permissions on destination branch.
+    // Blocking read permission on master so that refs/changes/01/1/1 becomes non-visible
+    blockReadPermission(R_HEADS_MASTER);
+
+    assertThat(
+            getCommitsIncludedInRefs(
+                change.getCommit().getName(), Arrays.asList(change.getPatchSet().refName())))
+        .isNull();
+  }
+
   private CommentLinkInfo commentLinkInfo(String name, String match, String link) {
     CommentLinkInfo info = new CommentLinkInfo();
     info.name = name;
@@ -1039,6 +1200,30 @@
     return getConfig(project);
   }
 
+  private Set<String> getCommitsIncludedInRefs(String commit, List<String> refs) throws Exception {
+    return getCommitsIncludedInRefs(Lists.newArrayList(commit), refs).get(commit);
+  }
+
+  private Map<String, Set<String>> getCommitsIncludedInRefs(List<String> commits, List<String> refs)
+      throws Exception {
+    return gApi.projects().name(project.get()).commitsIn(commits, refs);
+  }
+
+  private PushOneCommit.Result createAndSubmitChange(String branch) throws Exception {
+    PushOneCommit.Result r = createChange(branch);
+    approve(r.getChangeId());
+    gApi.changes().id(r.getChangeId()).current().submit();
+    return r;
+  }
+
+  private void blockReadPermission(String ref) {
+    projectOperations
+        .project(project)
+        .forUpdate()
+        .add(block(Permission.READ).ref(ref).group(REGISTERED_USERS))
+        .update();
+  }
+
   private ConfigInput createTestConfigInput() {
     ConfigInput input = new ConfigInput();
     input.description = "some description";
diff --git a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java b/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
deleted file mode 100644
index b69a894..0000000
--- a/javatests/com/google/gerrit/server/change/IncludedInResolverTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (C) 2013 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
-// limitations under the License.
-
-package com.google.gerrit.server.change;
-
-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;
-import org.junit.Before;
-import org.junit.Test;
-
-public class IncludedInResolverTest {
-  // Branch names
-  private static final String BRANCH_MASTER = "master";
-  private static final String BRANCH_1_0 = "rel-1.0";
-  private static final String BRANCH_1_3 = "rel-1.3";
-  private static final String BRANCH_2_0 = "rel-2.0";
-  private static final String BRANCH_2_5 = "rel-2.5";
-
-  // Tag names
-  private static final String TAG_1_0 = "1.0";
-  private static final String TAG_1_0_1 = "1.0.1";
-  private static final String TAG_1_3 = "1.3";
-  private static final String TAG_2_0_1 = "2.0.1";
-  private static final String TAG_2_0 = "2.0";
-  private static final String TAG_2_5 = "2.5";
-  private static final String TAG_2_5_ANNOTATED = "2.5-annotated";
-  private static final String TAG_2_5_ANNOTATED_TWICE = "2.5-annotated_twice";
-
-  // Commits
-  private RevCommit commit_initial;
-  private RevCommit commit_v1_3;
-  private RevCommit commit_v2_5;
-
-  private TestRepository<?> tr;
-
-  @Before
-  public void setUp() throws Exception {
-    tr = new TestRepository<>(new InMemoryRepository(new DfsRepositoryDescription("repo")));
-
-    /*- The following graph will be created.
-
-     o   tag 2.5, 2.5_annotated, 2.5_annotated_twice
-     |\
-     | o tag 2.0.1
-     | o tag 2.0
-     o | tag 1.3
-     |/
-     o   c3
-
-     | o tag 1.0.1
-     |/
-     o   tag 1.0
-     o   c2
-     o   c1
-
-    */
-
-    // Version 1.0
-    commit_initial = tr.branch(BRANCH_MASTER).commit().message("c1").create();
-    tr.branch(BRANCH_MASTER).commit().message("c2").create();
-    RevCommit commit_v1_0 = tr.branch(BRANCH_MASTER).commit().message("version 1.0").create();
-    tag(TAG_1_0, commit_v1_0);
-    RevCommit c3 = tr.branch(BRANCH_MASTER).commit().message("c3").create();
-
-    // Version 1.01
-    tr.branch(BRANCH_1_0).update(commit_v1_0);
-    RevCommit commit_v1_0_1 = tr.branch(BRANCH_1_0).commit().message("version 1.0.1").create();
-    tag(TAG_1_0_1, commit_v1_0_1);
-
-    // Version 1.3
-    tr.branch(BRANCH_1_3).update(c3);
-    commit_v1_3 = tr.branch(BRANCH_1_3).commit().message("version 1.3").create();
-    tag(TAG_1_3, commit_v1_3);
-
-    // Version 2.0
-    tr.branch(BRANCH_2_0).update(c3);
-    RevCommit commit_v2_0 = tr.branch(BRANCH_2_0).commit().message("version 2.0").create();
-    tag(TAG_2_0, commit_v2_0);
-    RevCommit commit_v2_0_1 = tr.branch(BRANCH_2_0).commit().message("version 2.0.1").create();
-    tag(TAG_2_0_1, commit_v2_0_1);
-
-    // Version 2.5
-    tr.branch(BRANCH_2_5).update(commit_v1_3);
-    tr.branch(BRANCH_2_5).commit().parent(commit_v2_0_1).create(); // Merge v2.0.1
-    commit_v2_5 = tr.branch(BRANCH_2_5).commit().message("version 2.5").create();
-    tr.update(REFS_TAGS + TAG_2_5, commit_v2_5);
-    RevTag tag_2_5_annotated = tag(TAG_2_5_ANNOTATED, commit_v2_5);
-    tag(TAG_2_5_ANNOTATED_TWICE, tag_2_5_annotated);
-  }
-
-  @Test
-  public void resolveLatestCommit() throws Exception {
-    // Check tip commit
-    IncludedInResolver.Result detail = resolve(commit_v2_5);
-
-    // Check that only tags and branches which refer the tip are returned
-    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
-  public void resolveFirstCommit() throws Exception {
-    // Check first commit
-    IncludedInResolver.Result detail = resolve(commit_initial);
-
-    // Check whether all tags and branches are returned
-    assertThat(detail.tags())
-        .comparingElementsUsing(hasShortName())
-        .containsExactly(
-            TAG_1_0,
-            TAG_1_0_1,
-            TAG_1_3,
-            TAG_2_0,
-            TAG_2_0_1,
-            TAG_2_5,
-            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);
-  }
-
-  @Test
-  public void resolveBetwixtCommit() throws Exception {
-    // Check a commit somewhere in the middle
-    IncludedInResolver.Result detail = resolve(commit_v1_3);
-
-    // 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())
-        .comparingElementsUsing(hasShortName())
-        .containsExactly(BRANCH_1_3, BRANCH_2_5);
-  }
-
-  private IncludedInResolver.Result resolve(RevCommit commit) throws Exception {
-    return IncludedInResolver.resolve(tr.getRepository(), tr.getRevWalk(), commit);
-  }
-
-  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/modules/jgit b/modules/jgit
index 1cbfea9..60b81c5 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 1cbfea9ece03b40669377a7f858218f6994562ea
+Subproject commit 60b81c5a9280e44fa48d533a61f915382b2b9ce2