From db53df102c57ccea9f46bfa88cfc9456afa771e4 Mon Sep 17 00:00:00 2001 From: meiji163 Date: Wed, 2 Mar 2022 19:25:25 -0800 Subject: [PATCH] pinning script exts --- pkg/cmd/extension/command.go | 14 +++++++++++-- pkg/cmd/extension/http.go | 40 +++++++++++++++++++++++++++++++++++- pkg/cmd/extension/manager.go | 31 ++++++++++++++++++++++++---- 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index 50aad7a40..ee0dbd512 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -116,18 +116,28 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { return err } + cs := io.ColorScheme() if err := m.Install(repo, pinFlag); err != nil { + if errors.Is(err, releaseNotFoundErr) { + return fmt.Errorf("%s Could not find a release of %s for %s", + cs.FailureIcon(), args[0], cs.Cyan(pinFlag)) + } else if errors.Is(err, commitNotFoundErr) { + return fmt.Errorf("%s %s does not exist in %s", + cs.FailureIcon(), cs.Cyan(pinFlag), args[0]) + } return err } if io.IsStdoutTTY() { - cs := io.ColorScheme() fmt.Fprintf(io.Out, "%s Installed extension %s\n", cs.SuccessIcon(), args[0]) + if pinFlag != "" { + fmt.Fprintf(io.Out, "%s Pinned extension at %s\n", cs.SuccessIcon(), cs.Cyan(pinFlag)) + } } return nil }, } - cmd.Flags().StringVar(&pinFlag, "pin", "", "pin extension to a release tag or commit sha") + cmd.Flags().StringVar(&pinFlag, "pin", "", "pin extension to a release tag or commit ref") return cmd }(), func() *cobra.Command { diff --git a/pkg/cmd/extension/http.go b/pkg/cmd/extension/http.go index d1bbe84e7..38ba9c5e5 100644 --- a/pkg/cmd/extension/http.go +++ b/pkg/cmd/extension/http.go @@ -2,6 +2,7 @@ package extension import ( "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -80,6 +81,9 @@ func downloadAsset(httpClient *http.Client, asset releaseAsset, destPath string) return err } +var releaseNotFoundErr = errors.New("release not found") +var commitNotFoundErr = errors.New("commit not found") + // fetchLatestRelease finds the latest published release for a repository. func fetchLatestRelease(httpClient *http.Client, baseRepo ghrepo.Interface) (*release, error) { path := fmt.Sprintf("repos/%s/%s/releases/latest", baseRepo.RepoOwner(), baseRepo.RepoName()) @@ -113,7 +117,7 @@ func fetchLatestRelease(httpClient *http.Client, baseRepo ghrepo.Interface) (*re return &r, nil } -// fetchRelease finds release by tag name for a repository +// fetchReleaseFromTag finds release by tag name for a repository func fetchReleaseFromTag(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*release, error) { fullRepoName := fmt.Sprintf("%s/%s", baseRepo.RepoOwner(), baseRepo.RepoName()) path := fmt.Sprintf("repos/%s/releases/tags/%s", fullRepoName, tagName) @@ -129,6 +133,9 @@ func fetchReleaseFromTag(httpClient *http.Client, baseRepo ghrepo.Interface, tag return nil, err } + if resp.StatusCode == 404 { + return nil, releaseNotFoundErr + } if resp.StatusCode > 299 { return nil, api.HandleHTTPError(resp) } @@ -146,3 +153,34 @@ func fetchReleaseFromTag(httpClient *http.Client, baseRepo ghrepo.Interface, tag return &r, nil } + +// fetchCommitSHA finds full commit SHA from a target ref in a repo +func fetchCommitSHA(httpClient *http.Client, baseRepo ghrepo.Interface, targetRef string) (string, error) { + path := fmt.Sprintf("repos/%s/%s/commits/%s", baseRepo.RepoOwner(), baseRepo.RepoName(), targetRef) + url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", err + } + + req.Header.Set("Accept", "application/vnd.github.VERSION.sha") + resp, err := httpClient.Do(req) + defer resp.Body.Close() + if err != nil { + return "", err + } + + if resp.StatusCode == 422 { + return "", commitNotFoundErr + } + if resp.StatusCode > 299 { + return "", api.HandleHTTPError(resp) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(body), nil +} diff --git a/pkg/cmd/extension/manager.go b/pkg/cmd/extension/manager.go index 41dab528d..aa45ee48c 100644 --- a/pkg/cmd/extension/manager.go +++ b/pkg/cmd/extension/manager.go @@ -8,6 +8,7 @@ import ( "io" "io/fs" "io/ioutil" + "log" "net/http" "os" "os/exec" @@ -320,6 +321,7 @@ type binManifest struct { Path string } +// Install an extension from repo, and pin to commitish if provided func (m *Manager) Install(repo ghrepo.Interface, targetCommitish string) error { isBin, err := isBinExtension(m.client, repo) if err != nil { @@ -337,11 +339,11 @@ func (m *Manager) Install(repo ghrepo.Interface, targetCommitish string) error { return errors.New("extension is not installable: missing executable") } - protocol, _ := m.config.GetOrDefault(repo.RepoHost(), "git_protocol") - return m.installGit(ghrepo.FormatRemoteURL(repo, protocol), m.io.Out, m.io.ErrOut) + return m.installGit(repo, targetCommitish, m.io.Out, m.io.ErrOut) } func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) error { + log.Println("Installing binary extension") var r *release var err error if targetCommitish == "" { @@ -413,19 +415,40 @@ func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) erro return nil } -func (m *Manager) installGit(cloneURL string, stdout, stderr io.Writer) error { +func (m *Manager) installGit(repo ghrepo.Interface, targetCommitish string, stdout, stderr io.Writer) error { + protocol, _ := m.config.GetOrDefault(repo.RepoHost(), "git_protocol") + cloneURL := ghrepo.FormatRemoteURL(repo, protocol) + exe, err := m.lookPath("git") if err != nil { return err } + var commitSHA string + if targetCommitish != "" { + commitSHA, err = fetchCommitSHA(m.client, repo, targetCommitish) + if err != nil { + return err + } + } + name := strings.TrimSuffix(path.Base(cloneURL), ".git") targetDir := filepath.Join(m.installDir(), name) externalCmd := m.newCommand(exe, "clone", cloneURL, targetDir) externalCmd.Stdout = stdout externalCmd.Stderr = stderr - return externalCmd.Run() + if err := externalCmd.Run(); err != nil { + return err + } + if commitSHA == "" { + return nil + } + + checkoutCmd := m.newCommand(exe, "-C", targetDir, "checkout", commitSHA) + checkoutCmd.Stdout = stdout + checkoutCmd.Stderr = stderr + return checkoutCmd.Run() } var localExtensionUpgradeError = errors.New("local extensions can not be upgraded")