fix(gitutil): fall back to git CLI for push when no token is set
go-git's HTTPS transport does not use the system credential store, so pushes to remotes that require credentials fail silently or with "authentication required" when no token is provided. When token is empty, delegate to the system git binary so that credential helpers, SSH agents, and netrc all work as expected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ package gitutil
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -231,9 +233,17 @@ func CreateTag(repo *gogit.Repository, tagName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push pushes the given branch and tag to the "origin" remote.
|
// Push pushes the given branch and tag to the "origin" remote.
|
||||||
// If token is non-empty, HTTPS basic auth (oauth2/token) is used.
|
// When token is non-empty, go-git is used with HTTPS basic auth (oauth2/token) — suitable for CI.
|
||||||
// Passing an empty token lets go-git use the system credential helper or SSH agent.
|
// When token is empty, the system git binary is invoked so that credential helpers,
|
||||||
|
// SSH agents, and netrc are all available as they would be for a regular git push.
|
||||||
func Push(repo *gogit.Repository, branchName, tagName, token string) error {
|
func Push(repo *gogit.Repository, branchName, tagName, token string) error {
|
||||||
|
if token != "" {
|
||||||
|
return pushWithGoGit(repo, branchName, tagName, token)
|
||||||
|
}
|
||||||
|
return pushWithCLI(repo, branchName, tagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushWithGoGit(repo *gogit.Repository, branchName, tagName, token string) error {
|
||||||
remote, err := repo.Remote("origin")
|
remote, err := repo.Remote("origin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("remote origin not found: %w", err)
|
return fmt.Errorf("remote origin not found: %w", err)
|
||||||
@@ -244,13 +254,10 @@ func Push(repo *gogit.Repository, branchName, tagName, token string) error {
|
|||||||
gitconfig.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branchName, branchName)),
|
gitconfig.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branchName, branchName)),
|
||||||
gitconfig.RefSpec(fmt.Sprintf("refs/tags/%s:refs/tags/%s", tagName, tagName)),
|
gitconfig.RefSpec(fmt.Sprintf("refs/tags/%s:refs/tags/%s", tagName, tagName)),
|
||||||
},
|
},
|
||||||
}
|
Auth: &githttp.BasicAuth{
|
||||||
|
|
||||||
if token != "" {
|
|
||||||
opts.Auth = &githttp.BasicAuth{
|
|
||||||
Username: "oauth2",
|
Username: "oauth2",
|
||||||
Password: token,
|
Password: token,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := remote.Push(opts); err != nil && !errors.Is(err, gogit.NoErrAlreadyUpToDate) {
|
if err := remote.Push(opts); err != nil && !errors.Is(err, gogit.NoErrAlreadyUpToDate) {
|
||||||
@@ -259,6 +266,24 @@ func Push(repo *gogit.Repository, branchName, tagName, token string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pushWithCLI(repo *gogit.Repository, branchName, tagName string) error {
|
||||||
|
wt, err := repo.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get worktree: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("git", "-C", wt.Filesystem.Root(), "push", "origin",
|
||||||
|
fmt.Sprintf("HEAD:refs/heads/%s", branchName),
|
||||||
|
fmt.Sprintf("refs/tags/%s:refs/tags/%s", tagName, tagName),
|
||||||
|
)
|
||||||
|
cmd.Stdout = os.Stderr // git push status goes to stderr conventionally
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("git push: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// resolveTagToCommit follows tag objects until it reaches a commit.
|
// resolveTagToCommit follows tag objects until it reaches a commit.
|
||||||
// Handles both lightweight tags (ref → commit) and annotated tags (ref → tag object → … → commit).
|
// Handles both lightweight tags (ref → commit) and annotated tags (ref → tag object → … → commit).
|
||||||
func resolveTagToCommit(repo *gogit.Repository, ref *plumbing.Reference) (plumbing.Hash, error) {
|
func resolveTagToCommit(repo *gogit.Repository, ref *plumbing.Reference) (plumbing.Hash, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user