Skip to content

Commit fe5eca1

Browse files
committed
pinning binary exts
1 parent eaa64df commit fe5eca1

File tree

5 files changed

+90
-40
lines changed

5 files changed

+90
-40
lines changed

pkg/cmd/extension/command.go

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,12 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
7070
return t.Render()
7171
},
7272
},
73-
&cobra.Command{
74-
Use: "install <repository>",
75-
Short: "Install a gh extension from a repository",
76-
Long: heredoc.Doc(`
73+
func() *cobra.Command {
74+
var pinFlag string
75+
cmd := &cobra.Command{
76+
Use: "install <repository>",
77+
Short: "Install a gh extension from a repository",
78+
Long: heredoc.Doc(`
7779
Install a GitHub repository locally as a GitHub CLI extension.
7880
7981
The repository argument can be specified in "owner/repo" format as well as a full URL.
@@ -84,41 +86,44 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
8486
8587
See the list of available extensions at <https://github.com/topics/gh-extension>
8688
`),
87-
Example: heredoc.Doc(`
89+
Example: heredoc.Doc(`
8890
$ gh extension install owner/gh-extension
8991
$ gh extension install https://git.example.com/owner/gh-extension
9092
$ gh extension install .
9193
`),
92-
Args: cmdutil.MinimumArgs(1, "must specify a repository to install from"),
93-
RunE: func(cmd *cobra.Command, args []string) error {
94-
if args[0] == "." {
95-
wd, err := os.Getwd()
94+
Args: cmdutil.MinimumArgs(1, "must specify a repository to install from"),
95+
RunE: func(cmd *cobra.Command, args []string) error {
96+
if args[0] == "." {
97+
wd, err := os.Getwd()
98+
if err != nil {
99+
return err
100+
}
101+
return m.InstallLocal(wd)
102+
}
103+
104+
repo, err := ghrepo.FromFullName(args[0])
96105
if err != nil {
97106
return err
98107
}
99-
return m.InstallLocal(wd)
100-
}
101-
102-
repo, err := ghrepo.FromFullName(args[0])
103-
if err != nil {
104-
return err
105-
}
106108

107-
if err := checkValidExtension(cmd.Root(), m, repo.RepoName()); err != nil {
108-
return err
109-
}
109+
if err := checkValidExtension(cmd.Root(), m, repo.RepoName()); err != nil {
110+
return err
111+
}
110112

111-
if err := m.Install(repo); err != nil {
112-
return err
113-
}
113+
if err := m.Install(repo, pinFlag); err != nil {
114+
return err
115+
}
114116

115-
if io.IsStdoutTTY() {
116-
cs := io.ColorScheme()
117-
fmt.Fprintf(io.Out, "%s Installed extension %s\n", cs.SuccessIcon(), args[0])
118-
}
119-
return nil
120-
},
121-
},
117+
if io.IsStdoutTTY() {
118+
cs := io.ColorScheme()
119+
fmt.Fprintf(io.Out, "%s Installed extension %s\n", cs.SuccessIcon(), args[0])
120+
}
121+
return nil
122+
},
123+
}
124+
cmd.Flags().StringVar(&pinFlag, "pin", "", "pin extension to a release tag or commit sha")
125+
return cmd
126+
}(),
122127
func() *cobra.Command {
123128
var flagAll bool
124129
var flagForce bool

pkg/cmd/extension/http.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,37 @@ func fetchLatestRelease(httpClient *http.Client, baseRepo ghrepo.Interface) (*re
112112

113113
return &r, nil
114114
}
115+
116+
// fetchRelease finds release by tag name for a repository
117+
func fetchReleaseFromTag(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*release, error) {
118+
fullRepoName := fmt.Sprintf("%s/%s", baseRepo.RepoOwner(), baseRepo.RepoName())
119+
path := fmt.Sprintf("repos/%s/releases/tags/%s", fullRepoName, tagName)
120+
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
121+
req, err := http.NewRequest("GET", url, nil)
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
resp, err := httpClient.Do(req)
127+
defer resp.Body.Close()
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
if resp.StatusCode > 299 {
133+
return nil, api.HandleHTTPError(resp)
134+
}
135+
136+
b, err := ioutil.ReadAll(resp.Body)
137+
if err != nil {
138+
return nil, err
139+
}
140+
141+
var r release
142+
err = json.Unmarshal(b, &r)
143+
if err != nil {
144+
return nil, err
145+
}
146+
147+
return &r, nil
148+
}

pkg/cmd/extension/manager.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,13 @@ type binManifest struct {
320320
Path string
321321
}
322322

323-
func (m *Manager) Install(repo ghrepo.Interface) error {
323+
func (m *Manager) Install(repo ghrepo.Interface, targetCommitish string) error {
324324
isBin, err := isBinExtension(m.client, repo)
325325
if err != nil {
326326
return fmt.Errorf("could not check for binary extension: %w", err)
327327
}
328328
if isBin {
329-
return m.installBin(repo)
329+
return m.installBin(repo, targetCommitish)
330330
}
331331

332332
hs, err := hasScript(m.client, repo)
@@ -341,9 +341,14 @@ func (m *Manager) Install(repo ghrepo.Interface) error {
341341
return m.installGit(ghrepo.FormatRemoteURL(repo, protocol), m.io.Out, m.io.ErrOut)
342342
}
343343

344-
func (m *Manager) installBin(repo ghrepo.Interface) error {
344+
func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) error {
345345
var r *release
346-
r, err := fetchLatestRelease(m.client, repo)
346+
var err error
347+
if targetCommitish == "" {
348+
r, err = fetchLatestRelease(m.client, repo)
349+
} else {
350+
r, err = fetchReleaseFromTag(m.client, repo, targetCommitish)
351+
}
347352
if err != nil {
348353
return err
349354
}
@@ -498,7 +503,7 @@ func (m *Manager) upgradeExtension(ext Extension, force bool) error {
498503
if err != nil {
499504
return fmt.Errorf("failed to migrate to new precompiled extension format: %w", err)
500505
}
501-
return m.installBin(repo)
506+
return m.installBin(repo, "")
502507
}
503508
err = m.upgradeGitExtension(ext, force)
504509
}
@@ -525,7 +530,7 @@ func (m *Manager) upgradeBinExtension(ext Extension) error {
525530
if err != nil {
526531
return fmt.Errorf("failed to parse URL %s: %w", ext.url, err)
527532
}
528-
return m.installBin(repo)
533+
return m.installBin(repo, "")
529534
}
530535

531536
func (m *Manager) Remove(name string) error {

pkg/extensions/extension.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type Extension interface {
2727
//go:generate moq -rm -out manager_mock.go . ExtensionManager
2828
type ExtensionManager interface {
2929
List(includeMetadata bool) []Extension
30-
Install(ghrepo.Interface) error
30+
Install(ghrepo.Interface, string) error
3131
InstallLocal(dir string) error
3232
Upgrade(name string, force bool) error
3333
Remove(name string) error

pkg/extensions/manager_mock.go

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)