Skip to content
Snippets Groups Projects
Commit b1dc70f8 authored by Georges Racinet's avatar Georges Racinet
Browse files

ref service: ListBranchNamesContainingCommits

At this point, the ordering of results is arbitrary.
We don't expect that to be a problem until after the
first end-to-end experiments.

Implicitely expected ordering is specified in comment.

Implementation is probably somewhat inefficient. It's a bit
frustrating that in Mercurial we know right away to which
branch a commit relates (contrary to Git) but still have to
do all these ancestry computations.

But it would be easy to be wrong if trying to be smarter.
parent 3346666b
No related branches found
No related tags found
No related merge requests found
......@@ -38,6 +38,8 @@
FindBranchResponse,
DeleteRefsRequest,
DeleteRefsResponse,
ListBranchNamesContainingCommitRequest,
ListBranchNamesContainingCommitResponse,
)
from ..stub.ref_pb2_grpc import RefServiceServicer
......@@ -205,6 +207,35 @@
return DeleteRefsResponse(
git_error="Refs cannot be manually deleted in Mercurial")
def ListBranchNamesContainingCommit(
self,
request: ListBranchNamesContainingCommitRequest,
context) -> ListBranchNamesContainingCommitResponse:
repo = self.load_repo(request.repository)
gl_branches_by_heads = {}
for gl_branch, head in iter_gitlab_branches(repo):
rev = head.rev()
gl_branches_by_heads.setdefault(rev, []).append(gl_branch)
heads = repo.revs(b'%ld and %s::', gl_branches_by_heads,
request.commit_id)
# TODO SPEC since there's a limit, we'll have to know what is
# the expected ordering.
#
# In Gitaly sources, this is in refnames.go, which in turns call
# `git for-each-ref` without a `sort` option. Then according to
# the man page:
# --sort=<key>
# A field name to sort on. Prefix - to sort in descending order
# of the value. When unspecified, refname is used.
for chunk in chunked((gl_branch
for head in heads
for gl_branch in gl_branches_by_heads[head]),
limit=request.limit):
yield ListBranchNamesContainingCommitResponse(
branch_names=chunk)
def flbr_author(commit: GitCommit):
"""Extract commit intro specific fields of FindLocalBranchCommitAuthor."""
......
......@@ -4,6 +4,9 @@
# GNU General Public License version 2 or any later version.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from mercurial import (
pycompat,
)
from .common import make_empty_repo
from ..stub.ref_pb2 import (
......@@ -15,6 +18,7 @@
FindAllRemoteBranchesRequest,
FindAllBranchesRequest,
DeleteRefsRequest,
ListBranchNamesContainingCommitRequest,
)
from ..stub.ref_pb2_grpc import RefServiceStub
......@@ -154,3 +158,82 @@
resp = ref_stub.DeleteRefs(DeleteRefsRequest(repository=grpc_repo,
refs=[b'branch/default']))
assert resp.git_error
def test_list_branch_names_containing_commit(grpc_channel, server_repos_root):
"""Test ListBranchNamesContainingCommit on a repo a bit more spread
Here's the graphlog:
$ hg log -G -T '{desc}\nbranch & topic: {branch}:{topic}\n\n'
@ Topic head
| branch & topic: default:zzetop
|
o Topic first
| branch & topic: default:zzetop
|
| o Other main wild head
| | branch & topic: other:
| |
| | o Other wild
| |/ branch & topic: other:
| |
| o Start other
|/ branch & topic: other:
|
| o Head of default
|/ branch & topic: default:
|
o Base
branch & topic: default:
"""
ref_stub = RefServiceStub(grpc_channel)
wrapper, grpc_repo = make_empty_repo(server_repos_root,
relative_path='lbncc')
base = wrapper.write_commit('foo', message='Base')
default = wrapper.write_commit('foo', message='Head of default')
other_base = wrapper.write_commit('foo', message='Start other',
branch='other', parent=base)
wild1 = wrapper.write_commit('foo', message='Other wild',
branch='other')
wild2 = wrapper.write_commit('foo', message='Other main wild head',
branch='other', parent=other_base)
top1 = wrapper.write_commit('foo', message='Topic first',
branch='default', topic='zzetop',
parent=base)
top2 = wrapper.write_commit('foo', message='Topic head')
def do_list(ctx, limit=0):
chunks_iter = ref_stub.ListBranchNamesContainingCommit(
ListBranchNamesContainingCommitRequest(
repository=grpc_repo,
commit_id=pycompat.sysstr(ctx.hex()),
limit=limit,
))
return [pycompat.sysstr(gl_branch) for chunk in chunks_iter
for gl_branch in chunk.branch_names]
wild_branch1 = 'wild/' + pycompat.sysstr(wild1.hex())
wild_branch2 = 'wild/' + pycompat.sysstr(wild2.hex())
assert all(do_list(ctx) == ['topic/default/zzetop']
for ctx in [top1, top2])
assert do_list(wild1) == [wild_branch1]
assert set(do_list(wild2)) == {wild_branch2, 'branch/other'}
assert set(do_list(other_base)) == {'branch/other',
wild_branch1,
wild_branch2,
}
for top in (top1, top2):
assert do_list(top) == ['topic/default/zzetop']
assert do_list(default) == ['branch/default']
all_branches = {'branch/other',
'branch/default',
'topic/default/zzetop',
wild_branch1,
wild_branch2,
}
assert set(do_list(base)) == all_branches
limited = set(do_list(base, limit=3))
assert len(limited) == 3
# until we have the ordering, we can only assert sub set.
assert limited.issubset(all_branches)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment