# HG changeset patch
# User Georges Racinet <georges.racinet@octobus.net>
# Date 1686059210 -7200
#      Tue Jun 06 15:46:50 2023 +0200
# Branch stable
# Node ID bbca598ebf82d6f6ed17497f4c6b1de1cd8356b3
# Parent  d89850f0b6132b2bdf377b636e7a5da8cb2934bd
revision: precedence rules in case of collisions

As illustrated with the new assertions in `test_compare_find_commit`,
it turns out that in Gitaly, tags have precedence over branches
(unsurprisingly) and over shortened commit ids, but not on full commit
ids.

diff --git a/hgitaly/revision.py b/hgitaly/revision.py
--- a/hgitaly/revision.py
+++ b/hgitaly/revision.py
@@ -65,6 +65,14 @@
     :return: the changeset as a :class:`changectx` instance, or ``None``
              if not found.
     """
+    if CHANGESET_HASH_BYTES_REGEXP.match(revision):
+        try:
+            ctx = repo.unfiltered()[revision]
+        except error.RepoLookupError:
+            return None  # voluntarily explicit
+
+        return ctx if isinstance(ctx, changectx) else None
+
     if revision == b'HEAD':
         revision = get_default_gitlab_branch(repo)
         if revision is None:
@@ -93,15 +101,17 @@
     if ctx is not None:
         return ctx
 
+    # direct GitLab tag name
+    ctx = tagged_changeset(repo, revision)
+    if ctx is not None:
+        return ctx
+
     # direct GitLab branch name
     ctx = gitlab_branch_head(repo, revision)
     if ctx is not None:
         return ctx
 
     try:
-        # TODO we should probably give precedence to tags, as Mercurial
-        # does, although we should check what Git(aly) really does in
-        # case of naming conflicts
         ctx = scmutil.revsingle(repo.unfiltered(), revision)
         # ctx can be, e.g., workingctx (see heptapod#717)
         # The null node is not to be filtered (it may be used in diffs)
diff --git a/tests_with_gitaly/test_commit.py b/tests_with_gitaly/test_commit.py
--- a/tests_with_gitaly/test_commit.py
+++ b/tests_with_gitaly/test_commit.py
@@ -242,7 +242,38 @@
     rpc_helper.normalize_responses(hg_resp, git_resp)
     assert hg_resp == git_resp
 
-    # TODO cases of collision/shadowing
+    # collision between branch and tag (test validity corroborated by checking
+    # agreement also on the tag ref full path)
+    wrapper.command('tag', b'tagbook', rev=ctx1.hex())
+    wrapper.command('bookmark', b'tagbook', rev=ctx0.hex())
+    wrapper.command('tag', b'branch/default', rev=ctx0.hex())
+    wrapper.command('gitlab-mirror')
+    fixture.invalidate()  # for the hg->git map
+    assert_compare(revision=b"refs/tags/tagbook")
+    assert_compare(revision=b"tagbook")
+    assert_compare(revision=b"refs/tags/branch/default")
+    assert_compare(revision=b"branch/default")
+
+    # collision between tag and node id (full form and shortened)
+    # Notice how we do *not* run gitlab-mirror, as we need different tags on
+    # both sides. Also no sense using assert_compare() in that case either
+    # What matters here is that Gitaly and HGitaly behave identically,
+    # but for the sake of completeness, as of this writing, in Gitaly,
+    # tags have precedence over shortened commit ids, but not on full
+    # commit ids (same with a command-line Git, for what it's worth).
+    git_repo = fixture.git_repo
+    hg2git = rpc_helper.hg2git
+    hg_sha0, hg_sha1 = ctx0.hex(), ctx1.hex()
+    git_sha0, git_sha1 = hg2git(hg_sha0), hg2git(hg_sha1)
+
+    for hg_sha_tag, git_sha_tag in ((hg_sha0, git_sha0),
+                                    (hg_sha0[:10], git_sha0[:10])):
+        wrapper.command('tag', hg_sha_tag, rev=hg_sha1)
+        git_repo.write_ref(b'refs/tags/' + git_sha_tag, git_sha1)
+
+        assert rpc_helper.hg2git(
+            rpc_helper.rpc('hg', revision=hg_sha_tag).commit.id.encode()
+        ) == rpc_helper.rpc('git', revision=git_sha_tag).commit.id.encode()
 
 
 def test_compare_find_commits(gitaly_comparison):