Skip to content

Commit 76f63ee

Browse files
dfinkelgopherbot
authored andcommitted
cmd/go: add support for git sha256 hashes
Git's supported SHA 256 object hashes since 2.29[1] in 2021, and Gitlab now has experimental support for sha256 repos. Take rsc@'s suggestion of checking the of the length of the hashes from git ls-remote to determine whether a git repo is using sha256 hashes and decide whether to pass --object-format=sha256 to git init. Unfortunately, just passing --object-format=sha256 wasn't quite enough, though. We also need to decide whether the hash-length is 64 hex bytes or 40 hex bytes when resolving refs to decide whether we've been passed a full commit-hash. To that end, we use git config extensions.objectformat to decide whether the (now guaranteed local) repo is using sha256 hashes and hence 64-hex-byte strings. [1]: lost experimental status in 2.42 from Aug 2023 (https://lore.kernel.org/git/[email protected]/) For: #68359 Change-Id: I47f480ab8334128c5d17570fe76722367d0d8ed8 Reviewed-on: https://go-review.googlesource.com/c/go/+/636475 Auto-Submit: Sam Thanawalla <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Sam Thanawalla <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-by: David Finkel <[email protected]>
1 parent 4aeb9ba commit 76f63ee

File tree

5 files changed

+370
-7
lines changed

5 files changed

+370
-7
lines changed

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

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func newGitRepo(ctx context.Context, remote string, local bool) (Repo, error) {
5959
}
6060
r.dir = remote
6161
r.mu.Path = r.dir + ".lock"
62+
r.sha256Hashes = r.checkConfigSHA256(ctx)
6263
return r, nil
6364
}
6465
// This is a remote path lookup.
@@ -81,7 +82,20 @@ func newGitRepo(ctx context.Context, remote string, local bool) (Repo, error) {
8182
defer unlock()
8283

8384
if _, err := os.Stat(filepath.Join(r.dir, "objects")); err != nil {
84-
if _, err := Run(ctx, r.dir, "git", "init", "--bare"); err != nil {
85+
repoSha256Hash := false
86+
if refs, lrErr := r.loadRefs(ctx); lrErr == nil {
87+
// Check any ref's hash, it doesn't matter which; they won't be mixed
88+
// between sha1 and sha256 for the moment.
89+
for _, refHash := range refs {
90+
repoSha256Hash = len(refHash) == (256 / 4)
91+
break
92+
}
93+
}
94+
objFormatFlag := []string{}
95+
if repoSha256Hash {
96+
objFormatFlag = []string{"--object-format=sha256"}
97+
}
98+
if _, err := Run(ctx, r.dir, "git", "init", "--bare", objFormatFlag); err != nil {
8599
os.RemoveAll(r.dir)
86100
return nil, err
87101
}
@@ -109,6 +123,7 @@ func newGitRepo(ctx context.Context, remote string, local bool) (Repo, error) {
109123
}
110124
}
111125
}
126+
r.sha256Hashes = r.checkConfigSHA256(ctx)
112127
r.remoteURL = r.remote
113128
r.remote = "origin"
114129
return r, nil
@@ -121,6 +136,9 @@ type gitRepo struct {
121136
local bool // local only lookups; no remote fetches
122137
dir string
123138

139+
// Repo uses the SHA256 for hashes, so expect the hashes to be 256/4 == 64-bytes in hex.
140+
sha256Hashes bool
141+
124142
mu lockedfile.Mutex // protects fetchLevel and git repo state
125143

126144
fetchLevel int
@@ -386,6 +404,32 @@ func (r *gitRepo) findRef(ctx context.Context, hash string) (ref string, ok bool
386404
return "", false
387405
}
388406

407+
func (r *gitRepo) checkConfigSHA256(ctx context.Context) bool {
408+
if hashType, sha256CfgErr := r.runGit(ctx, "git", "config", "extensions.objectformat"); sha256CfgErr == nil {
409+
return "sha256" == strings.TrimSpace(string(hashType))
410+
}
411+
return false
412+
}
413+
414+
func (r *gitRepo) hexHashLen() int {
415+
if !r.sha256Hashes {
416+
return 160 / 4
417+
}
418+
return 256 / 4
419+
}
420+
421+
// shortenObjectHash shortens a SHA1 or SHA256 hash (40 or 64 hex digits) to
422+
// the canonical length used in pseudo-versions (12 hex digits).
423+
func (r *gitRepo) shortenObjectHash(rev string) string {
424+
if !r.sha256Hashes {
425+
return ShortenSHA1(rev)
426+
}
427+
if AllHex(rev) && len(rev) == 256/4 {
428+
return rev[:12]
429+
}
430+
return rev
431+
}
432+
389433
// minHashDigits is the minimum number of digits to require
390434
// before accepting a hex digit sequence as potentially identifying
391435
// a specific commit in a git repo. (Of course, users can always
@@ -399,7 +443,7 @@ const minHashDigits = 7
399443
func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err error) {
400444
// Fast path: maybe rev is a hash we already have locally.
401445
didStatLocal := false
402-
if len(rev) >= minHashDigits && len(rev) <= 40 && AllHex(rev) {
446+
if len(rev) >= minHashDigits && len(rev) <= r.hexHashLen() && AllHex(rev) {
403447
if info, err := r.statLocal(ctx, rev, rev); err == nil {
404448
return info, nil
405449
}
@@ -415,7 +459,8 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
415459

416460
// Maybe rev is the name of a tag or branch on the remote server.
417461
// Or maybe it's the prefix of a hash of a named ref.
418-
// Try to resolve to both a ref (git name) and full (40-hex-digit) commit hash.
462+
// Try to resolve to both a ref (git name) and full (40-hex-digit for
463+
// sha1 64 for sha256) commit hash.
419464
refs, err := r.loadRefs(ctx)
420465
if err != nil {
421466
return nil, err
@@ -436,7 +481,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
436481
ref = "HEAD"
437482
hash = refs[ref]
438483
rev = hash // Replace rev, because meaning of HEAD can change.
439-
} else if len(rev) >= minHashDigits && len(rev) <= 40 && AllHex(rev) {
484+
} else if len(rev) >= minHashDigits && len(rev) <= r.hexHashLen() && AllHex(rev) {
440485
// At the least, we have a hash prefix we can look up after the fetch below.
441486
// Maybe we can map it to a full hash using the known refs.
442487
prefix := rev
@@ -455,7 +500,7 @@ func (r *gitRepo) stat(ctx context.Context, rev string) (info *RevInfo, err erro
455500
hash = h
456501
}
457502
}
458-
if hash == "" && len(rev) == 40 { // Didn't find a ref, but rev is a full hash.
503+
if hash == "" && len(rev) == r.hexHashLen() { // Didn't find a ref, but rev is a full hash.
459504
hash = rev
460505
}
461506
} else {
@@ -631,7 +676,7 @@ func (r *gitRepo) statLocal(ctx context.Context, version, rev string) (*RevInfo,
631676
Hash: hash,
632677
},
633678
Name: hash,
634-
Short: ShortenSHA1(hash),
679+
Short: r.shortenObjectHash(hash),
635680
Time: time.Unix(t, 0).UTC(),
636681
Version: hash,
637682
}

0 commit comments

Comments
 (0)