77 lines
2.9 KiB
Go
77 lines
2.9 KiB
Go
package git
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
type TaggedSource struct {
|
|
Head string
|
|
Status string
|
|
TagObject string
|
|
TagCommit string
|
|
TagObjectType string
|
|
}
|
|
|
|
// InspectTaggedSource reads the local HEAD, worktree state, and selected tag.
|
|
// Both lightweight historical tags and annotated tags are supported.
|
|
func InspectTaggedSource(ctx context.Context, repositoryDir, version string) (TaggedSource, error) {
|
|
if !validVersionRef(version) {
|
|
return TaggedSource{}, fmt.Errorf("invalid tag version %q", version)
|
|
}
|
|
var source TaggedSource
|
|
head, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", "HEAD^{commit}")
|
|
if err != nil {
|
|
return TaggedSource{}, fmt.Errorf("read checkout HEAD: %w", err)
|
|
}
|
|
source.Head = strings.TrimSpace(head)
|
|
if !validObjectID(source.Head) {
|
|
return TaggedSource{}, fmt.Errorf("Git returned an invalid checkout commit %q", source.Head)
|
|
}
|
|
status, err := run(ctx, repositoryDir, nil, "status", "--porcelain", "--untracked-files=all")
|
|
if err != nil {
|
|
return TaggedSource{}, fmt.Errorf("inspect tagged checkout state: %w", err)
|
|
}
|
|
source.Status = strings.TrimSpace(status)
|
|
ref := "refs/tags/" + version
|
|
object, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", ref)
|
|
if err != nil {
|
|
return TaggedSource{}, fmt.Errorf("read local tag %s: %w", version, err)
|
|
}
|
|
source.TagObject = strings.TrimSpace(object)
|
|
if !validObjectID(source.TagObject) {
|
|
return TaggedSource{}, fmt.Errorf("Git returned an invalid tag object %q", source.TagObject)
|
|
}
|
|
peeled, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", ref+"^{commit}")
|
|
if err != nil {
|
|
return TaggedSource{}, fmt.Errorf("resolve local tag %s to a commit: %w", version, err)
|
|
}
|
|
source.TagCommit = strings.TrimSpace(peeled)
|
|
if !validObjectID(source.TagCommit) {
|
|
return TaggedSource{}, fmt.Errorf("Git returned an invalid tagged commit %q", source.TagCommit)
|
|
}
|
|
tagType, err := run(ctx, repositoryDir, nil, "cat-file", "-t", ref)
|
|
if err != nil {
|
|
return TaggedSource{}, fmt.Errorf("inspect local tag %s: %w", version, err)
|
|
}
|
|
source.TagObjectType = strings.TrimSpace(tagType)
|
|
if source.TagObjectType != "tag" && source.TagObjectType != "commit" {
|
|
return TaggedSource{}, fmt.Errorf("local tag %s has unsupported object type %q", version, source.TagObjectType)
|
|
}
|
|
return source, nil
|
|
}
|
|
|
|
// ReadCommitFile reads a repository-relative file from a specific commit.
|
|
func ReadCommitFile(ctx context.Context, repositoryDir, commit, name string) ([]byte, error) {
|
|
if !validObjectID(commit) || name == "" || path.IsAbs(name) || path.Clean(name) != name || strings.HasPrefix(name, "../") || name == ".." || strings.Contains(name, "\\") {
|
|
return nil, fmt.Errorf("invalid committed file identity %q at %q", name, commit)
|
|
}
|
|
content, err := run(ctx, repositoryDir, nil, "show", commit+":"+name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read committed file %q: %w", name, err)
|
|
}
|
|
return []byte(content), nil
|
|
}
|