From bdab7de1d2b79641eb873cbb9b0b1b5c08017478 Mon Sep 17 00:00:00 2001 From: meiji163 Date: Wed, 2 Mar 2022 21:06:23 -0800 Subject: [PATCH] list pinned exts --- pkg/cmd/extension/command.go | 11 ++++--- pkg/cmd/extension/extension.go | 8 ++++- pkg/cmd/extension/manager.go | 55 ++++++++++++++++++++++++-------- pkg/extensions/extension.go | 3 +- pkg/extensions/extension_mock.go | 36 +++++++++++++++++++++ 5 files changed, 93 insertions(+), 20 deletions(-) diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index 63f560710..eb3a4306a 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -61,11 +61,14 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { t.AddField(fmt.Sprintf("gh %s", c.Name()), nil, nil) t.AddField(repo, nil, nil) - var updateAvailable string - if c.UpdateAvailable() { - updateAvailable = "Upgrade available" + + if c.Pin() != "" { + t.AddField(c.Pin(), nil, cs.Cyan) + } else if c.UpdateAvailable() { + t.AddField("Upgrade available", nil, cs.Green) + } else { + t.AddField("", nil, nil) } - t.AddField(updateAvailable, nil, cs.Green) t.EndRow() } return t.Render() diff --git a/pkg/cmd/extension/extension.go b/pkg/cmd/extension/extension.go index b4106228f..87cd20c78 100644 --- a/pkg/cmd/extension/extension.go +++ b/pkg/cmd/extension/extension.go @@ -18,6 +18,7 @@ type Extension struct { path string url string isLocal bool + pin string currentVersion string latestVersion string kind ExtensionKind @@ -39,8 +40,13 @@ func (e *Extension) IsLocal() bool { return e.isLocal } +func (e *Extension) Pin() string { + return e.pin +} + func (e *Extension) UpdateAvailable() bool { - if e.isLocal || + if e.pin != "" || + e.isLocal || e.currentVersion == "" || e.latestVersion == "" || e.currentVersion == e.latestVersion { diff --git a/pkg/cmd/extension/manager.go b/pkg/cmd/extension/manager.go index aa45ee48c..34db89a10 100644 --- a/pkg/cmd/extension/manager.go +++ b/pkg/cmd/extension/manager.go @@ -8,7 +8,6 @@ import ( "io" "io/fs" "io/ioutil" - "log" "net/http" "os" "os/exec" @@ -199,6 +198,9 @@ func (m *Manager) parseBinaryExtensionDir(fi fs.FileInfo) (Extension, error) { remoteURL := ghrepo.GenerateRepoURL(repo, "") ext.url = remoteURL ext.currentVersion = bm.Tag + if bm.Pinned { + ext.pin = bm.Tag + } return ext, nil } @@ -207,12 +209,20 @@ func (m *Manager) parseGitExtensionDir(fi fs.FileInfo) (Extension, error) { exePath := filepath.Join(id, fi.Name(), fi.Name()) remoteUrl := m.getRemoteUrl(fi.Name()) currentVersion := m.getCurrentVersion(fi.Name()) + + var pinnedVersion string + pinPath := filepath.Join(id, fi.Name(), fmt.Sprintf(".pin-%s", currentVersion)) + if _, err := os.Stat(pinPath); err == nil { + pinnedVersion = currentVersion[:7] + } + return Extension{ path: exePath, url: remoteUrl, isLocal: false, currentVersion: currentVersion, kind: GitKind, + pin: pinnedVersion, }, nil } @@ -313,15 +323,16 @@ func (m *Manager) InstallLocal(dir string) error { } type binManifest struct { - Owner string - Name string - Host string - Tag string + Owner string + Name string + Host string + Tag string + Pinned bool // TODO I may end up not using this; just thinking ahead to local installs Path string } -// Install an extension from repo, and pin to commitish if provided +// Install installs an extension from repo, and pins to commitish if provided func (m *Manager) Install(repo ghrepo.Interface, targetCommitish string) error { isBin, err := isBinExtension(m.client, repo) if err != nil { @@ -343,10 +354,10 @@ func (m *Manager) Install(repo ghrepo.Interface, targetCommitish string) error { } func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) error { - log.Println("Installing binary extension") var r *release var err error - if targetCommitish == "" { + isPinned := targetCommitish != "" + if isPinned { r, err = fetchLatestRelease(m.client, repo) } else { r, err = fetchReleaseFromTag(m.client, repo, targetCommitish) @@ -372,6 +383,7 @@ func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) erro name := repo.RepoName() targetDir := filepath.Join(m.installDir(), name) + // TODO clean this up if function errs? err = os.MkdirAll(targetDir, 0755) if err != nil { @@ -387,11 +399,12 @@ func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) erro } manifest := binManifest{ - Name: name, - Owner: repo.RepoOwner(), - Host: repo.RepoHost(), - Path: binPath, - Tag: r.Tag, + Name: name, + Owner: repo.RepoOwner(), + Host: repo.RepoHost(), + Path: binPath, + Tag: r.Tag, + Pinned: isPinned, } bs, err := yaml.Marshal(manifest) @@ -448,9 +461,20 @@ func (m *Manager) installGit(repo ghrepo.Interface, targetCommitish string, stdo checkoutCmd := m.newCommand(exe, "-C", targetDir, "checkout", commitSHA) checkoutCmd.Stdout = stdout checkoutCmd.Stderr = stderr - return checkoutCmd.Run() + if err := checkoutCmd.Run(); err != nil { + return err + } + + pinPath := filepath.Join(targetDir, fmt.Sprintf(".pin-%s", commitSHA)) + f, err := os.OpenFile(pinPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + defer f.Close() + if err != nil { + return fmt.Errorf("failed to create pin in directory: %w", err) + } + return nil } +var pinnedExtensionUpgradeError = errors.New("pinned extensions can not be upgraded") var localExtensionUpgradeError = errors.New("local extensions can not be upgraded") var upToDateError = errors.New("already up to date") var noExtensionsInstalledError = errors.New("no extensions installed") @@ -508,6 +532,9 @@ func (m *Manager) upgradeExtension(ext Extension, force bool) error { if ext.isLocal { return localExtensionUpgradeError } + if ext.pin != "" { + return pinnedExtensionUpgradeError + } if !ext.UpdateAvailable() { return upToDateError } diff --git a/pkg/extensions/extension.go b/pkg/extensions/extension.go index 6b57a1cc5..2db8ab0fb 100644 --- a/pkg/extensions/extension.go +++ b/pkg/extensions/extension.go @@ -19,9 +19,10 @@ type Extension interface { Name() string // Extension Name without gh- Path() string // Path to executable URL() string - IsLocal() bool + Pin() string // Pinned version UpdateAvailable() bool IsBinary() bool + IsLocal() bool } //go:generate moq -rm -out manager_mock.go . ExtensionManager diff --git a/pkg/extensions/extension_mock.go b/pkg/extensions/extension_mock.go index b999ab955..9d47d1c4f 100644 --- a/pkg/extensions/extension_mock.go +++ b/pkg/extensions/extension_mock.go @@ -29,6 +29,9 @@ var _ Extension = &ExtensionMock{} // PathFunc: func() string { // panic("mock out the Path method") // }, +// PinFunc: func() string { +// panic("mock out the Pin method") +// }, // URLFunc: func() string { // panic("mock out the URL method") // }, @@ -54,6 +57,9 @@ type ExtensionMock struct { // PathFunc mocks the Path method. PathFunc func() string + // PinFunc mocks the Pin method. + PinFunc func() string + // URLFunc mocks the URL method. URLFunc func() string @@ -74,6 +80,9 @@ type ExtensionMock struct { // Path holds details about calls to the Path method. Path []struct { } + // Pin holds details about calls to the Pin method. + Pin []struct { + } // URL holds details about calls to the URL method. URL []struct { } @@ -85,6 +94,7 @@ type ExtensionMock struct { lockIsLocal sync.RWMutex lockName sync.RWMutex lockPath sync.RWMutex + lockPin sync.RWMutex lockURL sync.RWMutex lockUpdateAvailable sync.RWMutex } @@ -193,6 +203,32 @@ func (mock *ExtensionMock) PathCalls() []struct { return calls } +// Pin calls PinFunc. +func (mock *ExtensionMock) Pin() string { + if mock.PinFunc == nil { + panic("ExtensionMock.PinFunc: method is nil but Extension.Pin was just called") + } + callInfo := struct { + }{} + mock.lockPin.Lock() + mock.calls.Pin = append(mock.calls.Pin, callInfo) + mock.lockPin.Unlock() + return mock.PinFunc() +} + +// PinCalls gets all the calls that were made to Pin. +// Check the length with: +// len(mockedExtension.PinCalls()) +func (mock *ExtensionMock) PinCalls() []struct { +} { + var calls []struct { + } + mock.lockPin.RLock() + calls = mock.calls.Pin + mock.lockPin.RUnlock() + return calls +} + // URL calls URLFunc. func (mock *ExtensionMock) URL() string { if mock.URLFunc == nil {