Derive module release identity and accept full Git tags
This commit is contained in:
@@ -8,23 +8,26 @@ import (
|
||||
|
||||
// PrepareTaggedCheckout fetches one explicit tag ref, checks its peeled commit
|
||||
// against expectedCommit, and checks out that commit detached.
|
||||
func PrepareTaggedCheckout(ctx context.Context, repositoryDir, version, remote, expectedCommit string) error {
|
||||
if !validVersionRef(version) || !validObjectID(expectedCommit) {
|
||||
return fmt.Errorf("invalid tagged checkout identity %q at %q", expectedCommit, version)
|
||||
func PrepareTaggedCheckout(ctx context.Context, repositoryDir, tag, remote, expectedCommit string) error {
|
||||
if err := checkTagRef(ctx, repositoryDir, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
ref := "refs/tags/" + version
|
||||
if !validObjectID(expectedCommit) {
|
||||
return fmt.Errorf("invalid tagged checkout commit %q for tag %q", expectedCommit, tag)
|
||||
}
|
||||
ref := "refs/tags/" + tag
|
||||
if _, err := run(ctx, repositoryDir, nil, "fetch", "--no-tags", remote, ref+":"+ref); err != nil {
|
||||
return fmt.Errorf("fetch tag %s from remote %s: %w", version, remote, err)
|
||||
return fmt.Errorf("fetch tag %s from remote %s: %w", tag, remote, err)
|
||||
}
|
||||
source, err := InspectTaggedSource(ctx, repositoryDir, version)
|
||||
source, err := InspectTaggedSource(ctx, repositoryDir, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.EqualFold(source.TagCommit, expectedCommit) {
|
||||
return fmt.Errorf("tag %s resolves to commit %s, expected %s", version, source.TagCommit, expectedCommit)
|
||||
return fmt.Errorf("tag %s resolves to commit %s, expected %s", tag, source.TagCommit, expectedCommit)
|
||||
}
|
||||
if _, err := run(ctx, repositoryDir, nil, "checkout", "--detach", source.TagCommit); err != nil {
|
||||
return fmt.Errorf("check out tag %s at %s: %w", version, source.TagCommit, err)
|
||||
return fmt.Errorf("check out tag %s at %s: %w", tag, source.TagCommit, err)
|
||||
}
|
||||
head, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", "HEAD^{commit}")
|
||||
if err != nil {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -42,6 +42,32 @@ func TestResolveUsesPushURLAndQueriesWithoutMutation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteTagOperationsAcceptSlashContainingRefs(t *testing.T) {
|
||||
fixture := newRemoteFixture(t)
|
||||
endpoint, err := Resolve(context.Background(), fixture.work, "origin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tag := "integrations/check/v1.2.3"
|
||||
if _, err := endpoint.RequireCandidate(context.Background(), fixture.work, tag, fixture.commit); err != nil {
|
||||
t.Fatalf("RequireCandidate(%q): %v", tag, err)
|
||||
}
|
||||
object, err := CreateAnnotatedTag(context.Background(), fixture.work, tag, fixture.commit, []byte("release note\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := endpoint.PushTag(context.Background(), fixture.work, tag, object); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
remote, err := endpoint.QueryTag(context.Background(), fixture.work, tag)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if remote.State != RefPresent || remote.Object != object || remote.Peeled != fixture.commit {
|
||||
t.Fatalf("remote tag = %#v", remote)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveAppliesGitURLRewrites(t *testing.T) {
|
||||
for _, kind := range []string{"insteadOf", "pushInsteadOf"} {
|
||||
t.Run(kind, func(t *testing.T) {
|
||||
|
||||
@@ -32,9 +32,9 @@ type TaggedSource struct {
|
||||
|
||||
// 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)
|
||||
func InspectTaggedSource(ctx context.Context, repositoryDir, tag string) (TaggedSource, error) {
|
||||
if err := checkTagRef(ctx, repositoryDir, tag); err != nil {
|
||||
return TaggedSource{}, err
|
||||
}
|
||||
var source TaggedSource
|
||||
head, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", "HEAD^{commit}")
|
||||
@@ -50,10 +50,10 @@ func InspectTaggedSource(ctx context.Context, repositoryDir, version string) (Ta
|
||||
return TaggedSource{}, fmt.Errorf("inspect tagged checkout state: %w", err)
|
||||
}
|
||||
source.Status = strings.TrimSpace(status)
|
||||
ref := "refs/tags/" + version
|
||||
ref := "refs/tags/" + tag
|
||||
object, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", ref)
|
||||
if err != nil {
|
||||
return TaggedSource{}, fmt.Errorf("read local tag %s: %w", version, err)
|
||||
return TaggedSource{}, fmt.Errorf("read local tag %s: %w", tag, err)
|
||||
}
|
||||
source.TagObject = strings.TrimSpace(object)
|
||||
if !validObjectID(source.TagObject) {
|
||||
@@ -61,7 +61,7 @@ func InspectTaggedSource(ctx context.Context, repositoryDir, version string) (Ta
|
||||
}
|
||||
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)
|
||||
return TaggedSource{}, fmt.Errorf("resolve local tag %s to a commit: %w", tag, err)
|
||||
}
|
||||
source.TagCommit = strings.TrimSpace(peeled)
|
||||
if !validObjectID(source.TagCommit) {
|
||||
@@ -69,11 +69,11 @@ func InspectTaggedSource(ctx context.Context, repositoryDir, version string) (Ta
|
||||
}
|
||||
tagType, err := run(ctx, repositoryDir, nil, "cat-file", "-t", ref)
|
||||
if err != nil {
|
||||
return TaggedSource{}, fmt.Errorf("inspect local tag %s: %w", version, err)
|
||||
return TaggedSource{}, fmt.Errorf("inspect local tag %s: %w", tag, 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 TaggedSource{}, fmt.Errorf("local tag %s has unsupported object type %q", tag, source.TagObjectType)
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
@@ -32,6 +32,37 @@ func TestInspectTaggedSourceSupportsLightweightAndAnnotatedTags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagOperationsAcceptFullSlashContainingRefs(t *testing.T) {
|
||||
fixture := newRemoteFixture(t)
|
||||
tag := "integrations/check/v2.3.4"
|
||||
if err := checkTagRef(context.Background(), fixture.work, tag); err != nil {
|
||||
t.Fatalf("checkTagRef(%q): %v", tag, err)
|
||||
}
|
||||
object, err := CreateAnnotatedTag(context.Background(), fixture.work, tag, fixture.commit, []byte("release note\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := InspectLocalTag(context.Background(), fixture.work, tag, object); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
source, err := InspectTaggedSource(context.Background(), fixture.work, tag)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if source.TagCommit != fixture.commit || source.TagObject != object || source.TagObjectType != "tag" {
|
||||
t.Fatalf("source for %q = %#v", tag, source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagReferenceValidationUsesGitRules(t *testing.T) {
|
||||
fixture := newRemoteFixture(t)
|
||||
for _, tag := range []string{"", "v1..2.3", "v1.2.3.lock", "v1.2.3/", "v1.2.3\\bad"} {
|
||||
if err := checkTagRef(context.Background(), fixture.work, tag); err == nil {
|
||||
t.Errorf("checkTagRef(%q) succeeded", tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidateObjectReadsStayLocalInPartialClones(t *testing.T) {
|
||||
fixture := newRemoteFixture(t)
|
||||
gitRun(t, fixture.work, "config", "uploadpack.allowFilter", "true")
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
|
||||
// CreateAnnotatedTag records the captured note as an unsigned annotated tag
|
||||
// on the captured commit and returns the tag object's ID.
|
||||
func CreateAnnotatedTag(ctx context.Context, repositoryDir, version, commit string, note []byte) (string, error) {
|
||||
if !validVersionRef(version) {
|
||||
return "", fmt.Errorf("invalid version tag name %q", version)
|
||||
func CreateAnnotatedTag(ctx context.Context, repositoryDir, tag, commit string, note []byte) (string, error) {
|
||||
if err := checkTagRef(ctx, repositoryDir, tag); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !validObjectID(commit) {
|
||||
return "", fmt.Errorf("invalid candidate commit object ID %q", commit)
|
||||
@@ -30,64 +30,67 @@ func CreateAnnotatedTag(ctx context.Context, repositoryDir, version, commit stri
|
||||
if err := message.Close(); err != nil {
|
||||
return "", fmt.Errorf("close temporary release note: %w", err)
|
||||
}
|
||||
if _, err := run(ctx, repositoryDir, []string{"tag.gpgSign=false"}, "tag", "--annotate", "--cleanup=verbatim", "--file", messagePath, version, commit); err != nil {
|
||||
return "", fmt.Errorf("create annotated tag %s: %w", version, err)
|
||||
if _, err := run(ctx, repositoryDir, []string{"tag.gpgSign=false"}, "tag", "--annotate", "--cleanup=verbatim", "--file", messagePath, tag, commit); err != nil {
|
||||
return "", fmt.Errorf("create annotated tag %s: %w", tag, err)
|
||||
}
|
||||
|
||||
ref := "refs/tags/" + version
|
||||
ref := "refs/tags/" + tag
|
||||
tagType, err := run(ctx, repositoryDir, nil, "cat-file", "-t", ref)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("inspect created tag %s: %w", version, err)
|
||||
return "", fmt.Errorf("inspect created tag %s: %w", tag, err)
|
||||
}
|
||||
if strings.TrimSpace(tagType) != "tag" {
|
||||
return "", fmt.Errorf("created tag %s has object type %q, want tag", version, strings.TrimSpace(tagType))
|
||||
return "", fmt.Errorf("created tag %s has object type %q, want tag", tag, strings.TrimSpace(tagType))
|
||||
}
|
||||
tagObject, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", ref)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read created tag object %s: %w", version, err)
|
||||
return "", fmt.Errorf("read created tag object %s: %w", tag, err)
|
||||
}
|
||||
tagObject = strings.TrimSpace(tagObject)
|
||||
if !validObjectID(tagObject) {
|
||||
return "", fmt.Errorf("created tag %s has invalid object ID %q", version, tagObject)
|
||||
return "", fmt.Errorf("created tag %s has invalid object ID %q", tag, tagObject)
|
||||
}
|
||||
peeled, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", ref+"^{}")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read peeled commit for tag %s: %w", version, err)
|
||||
return "", fmt.Errorf("read peeled commit for tag %s: %w", tag, err)
|
||||
}
|
||||
if strings.TrimSpace(peeled) != commit {
|
||||
return "", fmt.Errorf("created tag %s peels to %s, want candidate %s", version, strings.TrimSpace(peeled), commit)
|
||||
return "", fmt.Errorf("created tag %s peels to %s, want candidate %s", tag, strings.TrimSpace(peeled), commit)
|
||||
}
|
||||
tagContents, err := run(ctx, repositoryDir, nil, "cat-file", "tag", ref)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read annotated tag %s: %w", version, err)
|
||||
return "", fmt.Errorf("read annotated tag %s: %w", tag, err)
|
||||
}
|
||||
separator := bytes.Index([]byte(tagContents), []byte("\n\n"))
|
||||
if separator < 0 || !bytes.Equal([]byte(tagContents[separator+2:]), note) {
|
||||
return "", fmt.Errorf("annotated tag %s message does not match the captured release note", version)
|
||||
return "", fmt.Errorf("annotated tag %s message does not match the captured release note", tag)
|
||||
}
|
||||
return tagObject, nil
|
||||
}
|
||||
|
||||
// InspectLocalTag confirms the selected tag still points to the exact
|
||||
// annotated tag object captured before publication.
|
||||
func InspectLocalTag(ctx context.Context, repositoryDir, version, expectedObject string) error {
|
||||
if !validVersionRef(version) || !validObjectID(expectedObject) {
|
||||
return fmt.Errorf("invalid expected tag identity %q at %q", expectedObject, version)
|
||||
func InspectLocalTag(ctx context.Context, repositoryDir, tag, expectedObject string) error {
|
||||
if err := checkTagRef(ctx, repositoryDir, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
ref := "refs/tags/" + version
|
||||
if !validObjectID(expectedObject) {
|
||||
return fmt.Errorf("invalid expected tag object identity %q at %q", expectedObject, tag)
|
||||
}
|
||||
ref := "refs/tags/" + tag
|
||||
tagType, err := run(ctx, repositoryDir, nil, "cat-file", "-t", ref)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspect local tag %s: %w", version, err)
|
||||
return fmt.Errorf("inspect local tag %s: %w", tag, err)
|
||||
}
|
||||
if strings.TrimSpace(tagType) != "tag" {
|
||||
return fmt.Errorf("local tag %s has type %q, want annotated tag", version, strings.TrimSpace(tagType))
|
||||
return fmt.Errorf("local tag %s has type %q, want annotated tag", tag, strings.TrimSpace(tagType))
|
||||
}
|
||||
object, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", ref)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read local tag %s: %w", version, err)
|
||||
return fmt.Errorf("read local tag %s: %w", tag, err)
|
||||
}
|
||||
if strings.TrimSpace(object) != expectedObject {
|
||||
return fmt.Errorf("local tag %s identifies %s, want %s", version, strings.TrimSpace(object), expectedObject)
|
||||
return fmt.Errorf("local tag %s identifies %s, want %s", tag, strings.TrimSpace(object), expectedObject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -95,18 +98,18 @@ func InspectLocalTag(ctx context.Context, repositoryDir, version, expectedObject
|
||||
// QueryTag verifies the captured endpoint still resolves to itself, then reads
|
||||
// one exact authoritative version tag and its peeled commit. Failed
|
||||
// verification or reads are unreadable and never interpreted as absence.
|
||||
func (endpoint Endpoint) QueryTag(ctx context.Context, repositoryDir, version string) (RemoteRef, error) {
|
||||
if !validVersionRef(version) {
|
||||
return RemoteRef{}, fmt.Errorf("invalid version tag name %q", version)
|
||||
func (endpoint Endpoint) QueryTag(ctx context.Context, repositoryDir, tag string) (RemoteRef, error) {
|
||||
if err := checkTagRef(ctx, repositoryDir, tag); err != nil {
|
||||
return RemoteRef{}, err
|
||||
}
|
||||
if err := endpoint.verifyScopedURLs(ctx, repositoryDir); err != nil {
|
||||
return RemoteRef{State: RefUnreadable}, fmt.Errorf("%w: verify authoritative endpoint before tag query: %w", ErrRemoteStateUnreadable, err)
|
||||
}
|
||||
ref := "refs/tags/" + version
|
||||
ref := "refs/tags/" + tag
|
||||
peeledRef := ref + "^{}"
|
||||
output, err := run(ctx, repositoryDir, endpoint.scopedConfig(), "ls-remote", endpoint.ScopedName, ref, peeledRef)
|
||||
if err != nil {
|
||||
return RemoteRef{State: RefUnreadable}, fmt.Errorf("%w: query authoritative tag %s at %s: %w", ErrRemoteStateUnreadable, version, endpoint.PushURL, err)
|
||||
return RemoteRef{State: RefUnreadable}, fmt.Errorf("%w: query authoritative tag %s at %s: %w", ErrRemoteStateUnreadable, tag, endpoint.PushURL, err)
|
||||
}
|
||||
remoteTag := RemoteRef{State: RefAbsent}
|
||||
for _, line := range outputLines(output) {
|
||||
@@ -125,13 +128,16 @@ func (endpoint Endpoint) QueryTag(ctx context.Context, repositoryDir, version st
|
||||
}
|
||||
|
||||
// PushTag publishes only the captured tag object to its exact tag ref.
|
||||
func (endpoint Endpoint) PushTag(ctx context.Context, repositoryDir, version, tagObject string) error {
|
||||
if !validVersionRef(version) || !validObjectID(tagObject) {
|
||||
return fmt.Errorf("invalid tag publication identity %q at %q", tagObject, version)
|
||||
func (endpoint Endpoint) PushTag(ctx context.Context, repositoryDir, tag, tagObject string) error {
|
||||
if err := checkTagRef(ctx, repositoryDir, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
refspec := tagObject + ":refs/tags/" + version
|
||||
if !validObjectID(tagObject) {
|
||||
return fmt.Errorf("invalid tag publication object %q at %q", tagObject, tag)
|
||||
}
|
||||
refspec := tagObject + ":refs/tags/" + tag
|
||||
if _, err := run(ctx, repositoryDir, endpoint.scopedConfig(), "push", "--no-follow-tags", "--porcelain", endpoint.ScopedName, refspec); err != nil {
|
||||
return fmt.Errorf("push annotated tag %s to %s: %w", version, endpoint.PushURL, err)
|
||||
return fmt.Errorf("push annotated tag %s to %s: %w", tag, endpoint.PushURL, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user