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

branch: added new `deref` option to iterations.

Some gRPC methods don't need to resolve the commits. This
can be useful for performance reasons (a bit premature) but
also for robustness (case of `ListRefs`, being used as of
GitLab 14.8 for backups only).

Also the beginning of an effort to normalize HGitaly hexadecimal
node ids to `str`, as it is often what the protocol ends up
specifying, hence we have conversions littering the service methods.
parent 9fa346d4
No related branches found
No related tags found
1 merge request!101RefService.ListRefs: initial implementation
......@@ -143,6 +143,6 @@
return repo[bookmark_node]
def iter_gitlab_branches(repo):
def iter_gitlab_branches(repo, deref=True):
"""Iterate on all visible GitLab branches
......@@ -147,9 +147,13 @@
"""Iterate on all visible GitLab branches
Each iteration yields a pair ``(gl_branch, head)`` where ``gl_branch``
is the name of the GitLab branch and ``head`` is a :class:`changectx`
instance.
Each iteration yields a pair ``(gl_branch, target)`` where ``gl_branch``
is the name of the GitLab branch and ``target`` is a :class:`changectx`
instance or a hexadecimal node id, as a :class:`str` instance.
:param bool deref: if ``True``, resolves targets as :class:`changectx`
instance, ignoring lookup errors. If ``False``, yield targets as
hexadecimal node ids.
"""
gl_branches = gitlab_branches(repo)
if gl_branches is not GITLAB_BRANCHES_MISSING:
for branch, sha in gl_branches.items():
......@@ -152,14 +156,17 @@
"""
gl_branches = gitlab_branches(repo)
if gl_branches is not GITLAB_BRANCHES_MISSING:
for branch, sha in gl_branches.items():
try:
yield branch, repo[sha]
except error.RepoLookupError as exc:
logger.error("Unknown changeset ID in GitLab branches "
"statefile for repo at %r: %r",
repo.root, exc)
if deref:
try:
yield branch, repo[sha]
except error.RepoLookupError as exc:
logger.error("Unknown changeset ID in GitLab branches "
"statefile for repo at %r: %r",
repo.root, exc)
else:
yield branch, sha.decode('ascii')
else:
logger.warning("iter_gitlab_branches for %r: "
"no GitLab branches state file "
"defaulting to slow direct analysis.", repo.root)
......@@ -162,9 +169,11 @@
else:
logger.warning("iter_gitlab_branches for %r: "
"no GitLab branches state file "
"defaulting to slow direct analysis.", repo.root)
for branch, ctx in iter_compute_gitlab_branches(repo):
yield branch, ctx
for branch, target in iter_compute_gitlab_branches(repo):
if not deref:
target = target.hex().decode('ascii')
yield branch, target
def gitlab_branches_matcher(patterns):
......@@ -262,10 +271,11 @@
yield key, repo[node]
def iter_gitlab_branches_as_refs(repo):
"""Same as :func:`iter_gitlab_branches`, yielding full Git refs."""
for branch, ctx in iter_gitlab_branches(repo):
yield gitlab_branch_ref(branch), ctx
def iter_gitlab_branches_as_refs(repo, **kwargs):
"""Same as :func:`iter_gitlab_branches`, yielding full Git ref paths.
"""
for branch, target in iter_gitlab_branches(repo, **kwargs):
yield gitlab_branch_ref(branch), target
def _sort_key_ref_changeset_date_asc(ref_chgset):
......@@ -276,9 +286,9 @@
return -ref_chgset[1].date()[0]
def sorted_gitlab_branches_as_refs(repo, after=None, sort_by=None):
def sorted_gitlab_branches_as_refs(repo, after=None, sort_by=None, deref=True):
"""Return all visible, GitLab branches, sorted.
:param bytes after: full ref name of a branch. If not `None`,
return only branches which occur strictly after the given one in the
sort ordering used.
......@@ -280,10 +290,14 @@
"""Return all visible, GitLab branches, sorted.
:param bytes after: full ref name of a branch. If not `None`,
return only branches which occur strictly after the given one in the
sort ordering used.
:returns: iterable of pairs (full Git ref name, changectx). Can be just
an iterator.
:param bool deref: same as in :func:`iter_gitlab_branches`, except
that dereferencing may happen internally if the sort criterion
requires it.
:params kwargs: passed over to :func:`iter_gitlab_branches`
:returns: iterable of pairs (full Git ref name, changectx or str).
Can be just an iterator.
By default GitLab sorts branches lexicographically.
Since Heptapod is using `/` separated special names,
......@@ -304,5 +318,8 @@
elif sort_by == BranchSortBy.UPDATED_DESC:
sort_key = _sort_key_ref_changeset_date_desc
ref_chgsets = sorted(iter_gitlab_branches_as_refs(repo), key=sort_key)
inner_deref = deref if sort_by == BranchSortBy.FULL_REF_NAME else True
ref_targets = sorted(iter_gitlab_branches_as_refs(repo, deref=inner_deref),
key=sort_key)
if after is not None:
......@@ -308,4 +325,5 @@
if after is not None:
ref_chgsets = (ref_chgset for ref_chgset in ref_chgsets
if ref_chgset[0] > after)
# TODO BUG this works for lexicographic sorting only.
ref_targets = (ref_target for ref_target in ref_targets
if ref_target[0] > after)
......@@ -311,5 +329,8 @@
yield from ref_chgsets
if inner_deref and not deref:
ref_targets = ((ref, target.hex().decode('ascii'))
for ref, target in ref_targets)
yield from ref_targets
def ensure_gitlab_branches_state_file(repo):
......
......@@ -5,9 +5,10 @@
#
# SPDX-License-Identifier: GPL-2.0-or-later
import re
import time
from heptapod.testhelpers import (
LocalRepoWrapper,
)
from ..branch import (
......@@ -8,10 +9,11 @@
from heptapod.testhelpers import (
LocalRepoWrapper,
)
from ..branch import (
BranchSortBy,
ensure_gitlab_branches_state_file,
gitlab_branch_head,
gitlab_branches_matcher,
iter_gitlab_branches,
......@@ -14,5 +16,6 @@
ensure_gitlab_branches_state_file,
gitlab_branch_head,
gitlab_branches_matcher,
iter_gitlab_branches,
iter_gitlab_branches_as_refs,
iter_gitlab_branches_matching,
......@@ -18,4 +21,5 @@
iter_gitlab_branches_matching,
sorted_gitlab_branches_as_refs,
)
from hgext3rd.heptapod.branch import (
......@@ -33,6 +37,68 @@
))
def test_iter_no_state_file(tmpdir):
wrapper = make_repo(tmpdir)
repo = wrapper.repo
default_branch = b'branch/default'
default_branch_ref = b'refs/heads/branch/default'
base = wrapper.write_commit('foo')
base_sha = base.hex().decode()
assert list(iter_gitlab_branches(repo)) == [(default_branch, base)]
assert list(iter_gitlab_branches(repo, deref=False)) == [
(default_branch, base_sha)]
assert list(iter_gitlab_branches_as_refs(repo)) == [
(default_branch_ref, base)]
assert list(iter_gitlab_branches_as_refs(repo, deref=False)
) == [(default_branch_ref, base_sha)]
another = wrapper.write_commit('bar', branch='another',
utc_timestamp=time.time() + 10)
another_sha = another.hex().decode()
another_branch_ref = b'refs/heads/branch/another'
assert list(sorted_gitlab_branches_as_refs(
repo, sort_by=BranchSortBy.FULL_REF_NAME
)) == [
(another_branch_ref, another),
(default_branch_ref, base),
]
assert list(sorted_gitlab_branches_as_refs(
repo,
sort_by=BranchSortBy.FULL_REF_NAME,
deref=False,
)) == [
(another_branch_ref, another_sha),
(default_branch_ref, base_sha),
]
assert list(sorted_gitlab_branches_as_refs(
repo,
sort_by=BranchSortBy.UPDATED_ASC,
deref=True,
)) == [
(default_branch_ref, base),
(another_branch_ref, another),
]
assert list(sorted_gitlab_branches_as_refs(
repo,
sort_by=BranchSortBy.UPDATED_ASC,
deref=False,
)) == [
(default_branch_ref, base_sha),
(another_branch_ref, another_sha),
]
assert list(sorted_gitlab_branches_as_refs(
repo,
sort_by=BranchSortBy.UPDATED_DESC,
)) == [
(another_branch_ref, another),
(default_branch_ref, base),
]
def test_named_branch_multiple_heads(tmpdir):
wrapper = make_repo(tmpdir)
repo = wrapper.repo
......@@ -154,6 +220,11 @@
b'topic/default/zztop': topic,
}
assert dict(iter_gitlab_branches(repo, deref=False)) == {
b'branch/default': default.hex().decode(),
b'topic/default/zztop': topic.hex().decode(),
}
# for self coverage (will be also covered by service tests)
assert dict(iter_gitlab_branches_matching(repo, [b'branch/*'])) == {
b'branch/default': default,
......
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