summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Fick <mfick@codeaurora.org>2021-10-28 20:35:21 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2021-10-28 20:35:21 +0000
commit5c9ed3b9f15f378739a834bca87043c037134979 (patch)
tree1a56dab597cba219fa4d0ec12832ce5fc5ad26aa
parent4327dafac1823926298fe8e263ac98d1a30d5e3e (diff)
parent559132d8fd699f2a52c8f5d744cf6b29f1fc47d0 (diff)
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
-rw-r--r--Documentation/rest-api-projects.txt41
-rw-r--r--java/com/google/gerrit/extensions/api/projects/ProjectApi.java12
-rw-r--r--java/com/google/gerrit/server/api/projects/ProjectApiImpl.java23
-rw-r--r--java/com/google/gerrit/server/change/IncludedIn.java37
-rw-r--r--java/com/google/gerrit/server/change/IncludedInRefs.java139
-rw-r--r--java/com/google/gerrit/server/change/IncludedInResolver.java161
-rw-r--r--java/com/google/gerrit/server/change/IncludedInUtil.java49
-rw-r--r--java/com/google/gerrit/server/restapi/project/CommitsIncludedInRefs.java85
-rw-r--r--java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java1
-rw-r--r--javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java185
-rw-r--r--javatests/com/google/gerrit/server/change/IncludedInResolverTest.java175
m---------modules/jgit0
12 files changed, 569 insertions, 339 deletions
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 92c6dbf13c..2d5451872c 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1541,6 +1541,47 @@ entity is returned that results for the consistency checks.
}
----
+[[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 987399501c..59475a46dd 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.LabelDefinitionInfo;
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 @@ public interface ProjectApi {
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 @@ public interface ProjectApi {
}
@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 6d7fc15d40..9521759cce 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.Check;
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.gerrit.server.restapi.project.SetParent;
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 @@ public class ProjectApiImpl implements ProjectApi {
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 @@ public class ProjectApiImpl implements ProjectApi {
CreateAccessChange createAccessChange,
GetConfig getConfig,
PutConfig putConfig,
+ CommitsIncludedInRefs commitsIncludedInRefs,
Provider<ListBranches> listBranches,
Provider<ListTags> listTags,
DeleteBranches deleteBranches,
@@ -191,6 +197,7 @@ public class ProjectApiImpl implements ProjectApi {
createAccessChange,
getConfig,
putConfig,
+ commitsIncludedInRefs,
listBranches,
listTags,
deleteBranches,
@@ -232,6 +239,7 @@ public class ProjectApiImpl implements ProjectApi {
CreateAccessChange createAccessChange,
GetConfig getConfig,
PutConfig putConfig,
+ CommitsIncludedInRefs commitsIncludedInRefs,
Provider<ListBranches> listBranches,
Provider<ListTags> listTags,
DeleteBranches deleteBranches,
@@ -269,6 +277,7 @@ public class ProjectApiImpl implements ProjectApi {
createAccessChange,
getConfig,
putConfig,
+ commitsIncludedInRefs,
listBranches,
listTags,
deleteBranches,
@@ -309,6 +318,7 @@ public class ProjectApiImpl implements ProjectApi {
CreateAccessChange createAccessChange,
GetConfig getConfig,
PutConfig putConfig,
+ CommitsIncludedInRefs commitsIncludedInRefs,
Provider<ListBranches> listBranches,
Provider<ListTags> listTags,
DeleteBranches deleteBranches,
@@ -346,6 +356,7 @@ public class ProjectApiImpl implements ProjectApi {
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 @@ public class ProjectApiImpl implements ProjectApi {
}
@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 c06ce82c05..94498d7f49 100644
--- a/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/java/com/google/gerrit/server/change/IncludedIn.java
@@ -21,6 +21,7 @@ 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.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.Inject;
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 @@ public class IncludedIn {
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 @@ public class IncludedIn {
}
}
+ /**
+ * 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 0000000000..f06925129c
--- /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 b2b0a64fa3..0000000000
--- 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 0000000000..6f75e0f402
--- /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 0000000000..05ec28ee14
--- /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 410a88c8e7..e50a494922 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 @@ public class ProjectRestApiModule extends RestApiModule {
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 c42628c9db..6b2f4f14e4 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 @@ package com.google.gerrit.acceptance.api.project;
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.config.GerritConfig;
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.gerrit.server.project.ProjectConfig;
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 @@ public class ProjectIT extends AbstractDaemonTest {
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 @@ public class ProjectIT extends AbstractDaemonTest {
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 @@ public class ProjectIT extends AbstractDaemonTest {
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 b69a894b14..0000000000
--- 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
-Subproject 1cbfea9ece03b40669377a7f858218f6994562e
+Subproject 60b81c5a9280e44fa48d533a61f915382b2b9ce