Derive module release identity and accept full Git tags

This commit is contained in:
2026-10-10 02:10:18 +00:00
parent dbfae9ea81
commit f99fb59cd4
9 changed files with 475 additions and 79 deletions

View File

@@ -18,7 +18,7 @@ var (
ErrEndpointChanged = errors.New("Git publication endpoint changed")
ErrRemoteMainMissing = errors.New("authoritative remote has no main branch")
ErrRemoteMainConflict = errors.New("authoritative main does not identify the candidate commit")
ErrRemoteTagConflict = errors.New("version tag already exists on the authoritative remote")
ErrRemoteTagConflict = errors.New("tag already exists on the authoritative remote")
ErrRemoteStateUnreadable = errors.New("authoritative remote state is unreadable")
)
@@ -50,7 +50,7 @@ type RemoteRef struct {
Peeled string
}
// Snapshot is the result of querying refs/heads/main and one exact version tag.
// Snapshot is the result of querying refs/heads/main and one exact tag.
type Snapshot struct {
Main RemoteRef
Tag RemoteRef
@@ -97,14 +97,14 @@ func (endpoint Endpoint) Recheck(ctx context.Context, repositoryDir string) erro
return endpoint.verifyScopedURLs(ctx, repositoryDir)
}
// Query reads exact main and version-tag refs. A command failure is unreadable,
// Query reads exact main and tag refs. A command failure is unreadable,
// never evidence that a ref is absent.
func (endpoint Endpoint) Query(ctx context.Context, repositoryDir, version string) (Snapshot, error) {
if !validVersionRef(version) {
return Snapshot{}, fmt.Errorf("invalid version tag name %q", version)
func (endpoint Endpoint) Query(ctx context.Context, repositoryDir, tag string) (Snapshot, error) {
if err := checkTagRef(ctx, repositoryDir, tag); err != nil {
return Snapshot{}, err
}
mainRef := "refs/heads/main"
tagRef := "refs/tags/" + version
tagRef := "refs/tags/" + tag
peeledRef := tagRef + "^{}"
output, err := run(ctx, repositoryDir, endpoint.scopedConfig(), "ls-remote", endpoint.ScopedName, mainRef, tagRef, peeledRef)
if err != nil {
@@ -112,7 +112,7 @@ func (endpoint Endpoint) Query(ctx context.Context, repositoryDir, version strin
return snapshot, fmt.Errorf("%w: query authoritative refs at %s: %w", ErrRemoteStateUnreadable, endpoint.PushURL, err)
}
main := RemoteRef{State: RefAbsent}
tag := RemoteRef{State: RefAbsent}
remoteTag := RemoteRef{State: RefAbsent}
for _, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
if line == "" {
continue
@@ -125,19 +125,19 @@ func (endpoint Endpoint) Query(ctx context.Context, repositoryDir, version strin
case mainRef:
main = mergeRef(main, object, false)
case tagRef:
tag = mergeRef(tag, object, false)
remoteTag = mergeRef(remoteTag, object, false)
case peeledRef:
tag = mergeRef(tag, object, true)
remoteTag = mergeRef(remoteTag, object, true)
}
}
return Snapshot{Main: main, Tag: tag}, nil
return Snapshot{Main: main, Tag: remoteTag}, nil
}
// RequireCandidate requires exact main equality and an absent remote version
// RequireCandidate requires exact main equality and an absent remote
// tag. It deliberately does not accept a main branch that merely contains the
// candidate as an ancestor.
func (endpoint Endpoint) RequireCandidate(ctx context.Context, repositoryDir, version, commit string) (Snapshot, error) {
snapshot, err := endpoint.Query(ctx, repositoryDir, version)
func (endpoint Endpoint) RequireCandidate(ctx context.Context, repositoryDir, tag, commit string) (Snapshot, error) {
snapshot, err := endpoint.Query(ctx, repositoryDir, tag)
if err != nil {
return snapshot, err
}
@@ -294,25 +294,14 @@ func mergeRef(ref RemoteRef, object string, peeled bool) RemoteRef {
return ref
}
func validVersionRef(version string) bool {
if len(version) < 6 || !strings.HasPrefix(version, "v") {
return false
func checkTagRef(ctx context.Context, repositoryDir, tag string) error {
if tag == "" {
return fmt.Errorf("invalid Git tag name %q", tag)
}
parts := strings.Split(version[1:], ".")
if len(parts) != 3 {
return false
if _, err := run(ctx, repositoryDir, nil, "check-ref-format", "refs/tags/"+tag); err != nil {
return fmt.Errorf("invalid Git tag name %q: %w", tag, err)
}
for _, part := range parts {
if part == "" || (len(part) > 1 && part[0] == '0') {
return false
}
for _, char := range part {
if char < '0' || char > '9' {
return false
}
}
}
return true
return nil
}
func validObjectID(object string) bool {