Skip to content

Commit 426ef30

Browse files
rscgopherbot
authored andcommitted
cmd/go: implement -reuse for Mercurial repos
When we added -reuse in CL 411398, we only handled Git repos. This was partly because we were focused on Git traffic, partly because Git is the dominant module VCS, and partly because I couldn't see how to retrieve the metadata needed in other version control systems. This CL adds -reuse support for Mercurial, the second most popular VCS for modules, now that I see how to implement it. Although the Mercurial command line does not have sufficient information, the Mercurial Python API does, so we ship and invoke a Mercurial extension written in Python that can compute a hash of the remote repo without downloading it entirely, as well as resolve a remote name to a hash or check the continued existence of a hash. Then we can avoid downloading the repo at all if it hasn't changed since the last check or if the specific reference we need still resolves or exists. Fixes #75119. Change-Id: Ia47d89b15c1091c44efef9d325270fc400a412c4 Reviewed-on: https://go-review.googlesource.com/c/go/+/718382 Auto-Submit: Russ Cox <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-by: Michael Matloob <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 5241d11 commit 426ef30

File tree

12 files changed

+435
-250
lines changed

12 files changed

+435
-250
lines changed

lib/hg/goreposum.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2025 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
# Mercurial extension to add a 'goreposum' command that
6+
# computes a hash of a remote repo's tag state.
7+
# Tag definitions can come from the .hgtags file stored in
8+
# any head of any branch, and the server protocol does not
9+
# expose the tags directly. However, the protocol does expose
10+
# the hashes of all the branch heads, so we can use a hash of
11+
# all those branch names and heads as a conservative snapshot
12+
# of the entire remote repo state, and use that as the tag sum.
13+
# Any change on the server then invalidates the tag sum,
14+
# even if it didn't have anything to do with tags, but at least
15+
# we will avoid re-cloning a server when there have been no
16+
# changes at all.
17+
#
18+
# Separately, this extension also adds a 'golookup' command that
19+
# returns the hash of a specific reference, like 'default' or a tag.
20+
# And golookup of a hash confirms that it still exists on the server.
21+
# We can use that to revalidate that specific versions still exist and
22+
# have the same meaning they did the last time we checked.
23+
#
24+
# Usage:
25+
#
26+
# hg --config "extensions.goreposum=$GOROOT/lib/hg/goreposum.py" goreposum REPOURL
27+
28+
import base64, hashlib, sys
29+
from mercurial import registrar, ui, hg, node
30+
from mercurial.i18n import _
31+
cmdtable = {}
32+
command = registrar.command(cmdtable)
33+
@command(b'goreposum', [], _('url'), norepo=True)
34+
def goreposum(ui, url):
35+
"""
36+
goreposum computes a checksum of all the named state in the remote repo.
37+
It hashes together all the branch names and hashes
38+
and then all the bookmark names and hashes.
39+
Tags are stored in .hgtags files in any of the branches,
40+
so the branch metadata includes the tags as well.
41+
"""
42+
h = hashlib.sha256()
43+
peer = hg.peer(ui, {}, url)
44+
for name, revs in peer.branchmap().items():
45+
h.update(name)
46+
for r in revs:
47+
h.update(b' ')
48+
h.update(r)
49+
h.update(b'\n')
50+
if (b'bookmarks' in peer.listkeys(b'namespaces')):
51+
for name, rev in peer.listkeys(b'bookmarks').items():
52+
h.update(name)
53+
h.update(b'=')
54+
h.update(rev)
55+
h.update(b'\n')
56+
print('r1:'+base64.standard_b64encode(h.digest()).decode('utf-8'))
57+
58+
@command(b'golookup', [], _('url rev'), norepo=True)
59+
def golookup(ui, url, rev):
60+
"""
61+
golookup looks up a single identifier in the repo,
62+
printing its hash.
63+
"""
64+
print(node.hex(hg.peer(ui, {}, url).lookup(rev)).decode('utf-8'))

src/cmd/go/internal/modfetch/cache.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,10 +622,11 @@ func writeDiskStat(ctx context.Context, file string, info *RevInfo) error {
622622
o := *info.Origin
623623
info.Origin = &o
624624

625-
// Tags never matter if you are starting with a semver version,
625+
// Tags and RepoSum never matter if you are starting with a semver version,
626626
// as we would be when finding this cache entry.
627627
o.TagSum = ""
628628
o.TagPrefix = ""
629+
o.RepoSum = ""
629630
// Ref doesn't matter if you have a pseudoversion.
630631
if module.IsPseudoVersion(info.Version) {
631632
o.Ref = ""

src/cmd/go/internal/modfetch/codehost/codehost.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,12 @@ type Origin struct {
116116
Ref string `json:",omitempty"`
117117

118118
// If RepoSum is non-empty, then the resolution of this module version
119-
// failed due to the repo being available but the version not being present.
120-
// This depends on the entire state of the repo, which RepoSum summarizes.
121-
// For Git, this is a hash of all the refs and their hashes.
119+
// depends on the entire state of the repo, which RepoSum summarizes.
120+
// For Git, this is a hash of all the refs and their hashes, and the RepoSum
121+
// is only needed for module versions that don't exist.
122+
// For Mercurial, this is a hash of all the branches and their heads' hashes,
123+
// since the set of available tags is dervied from .hgtags files in those branches,
124+
// and the RepoSum is used for all module versions, available and not,
122125
RepoSum string `json:",omitempty"`
123126
}
124127

src/cmd/go/internal/modfetch/codehost/git_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ func TestLatest(t *testing.T) {
383383
Origin: &Origin{
384384
VCS: "hg",
385385
URL: hgrepo1,
386+
Ref: "tip",
386387
Hash: "745aacc8b24decc44ac2b13870f5472b479f4d72",
387388
},
388389
Name: "745aacc8b24decc44ac2b13870f5472b479f4d72",

0 commit comments

Comments
 (0)