list pinned exts

This commit is contained in:
meiji163 2022-03-02 21:06:23 -08:00
parent 44334bbec6
commit bdab7de1d2
5 changed files with 93 additions and 20 deletions

View file

@ -61,11 +61,14 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command {
t.AddField(fmt.Sprintf("gh %s", c.Name()), nil, nil) t.AddField(fmt.Sprintf("gh %s", c.Name()), nil, nil)
t.AddField(repo, nil, nil) t.AddField(repo, nil, nil)
var updateAvailable string
if c.UpdateAvailable() { if c.Pin() != "" {
updateAvailable = "Upgrade available" 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() t.EndRow()
} }
return t.Render() return t.Render()

View file

@ -18,6 +18,7 @@ type Extension struct {
path string path string
url string url string
isLocal bool isLocal bool
pin string
currentVersion string currentVersion string
latestVersion string latestVersion string
kind ExtensionKind kind ExtensionKind
@ -39,8 +40,13 @@ func (e *Extension) IsLocal() bool {
return e.isLocal return e.isLocal
} }
func (e *Extension) Pin() string {
return e.pin
}
func (e *Extension) UpdateAvailable() bool { func (e *Extension) UpdateAvailable() bool {
if e.isLocal || if e.pin != "" ||
e.isLocal ||
e.currentVersion == "" || e.currentVersion == "" ||
e.latestVersion == "" || e.latestVersion == "" ||
e.currentVersion == e.latestVersion { e.currentVersion == e.latestVersion {

View file

@ -8,7 +8,6 @@ import (
"io" "io"
"io/fs" "io/fs"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
@ -199,6 +198,9 @@ func (m *Manager) parseBinaryExtensionDir(fi fs.FileInfo) (Extension, error) {
remoteURL := ghrepo.GenerateRepoURL(repo, "") remoteURL := ghrepo.GenerateRepoURL(repo, "")
ext.url = remoteURL ext.url = remoteURL
ext.currentVersion = bm.Tag ext.currentVersion = bm.Tag
if bm.Pinned {
ext.pin = bm.Tag
}
return ext, nil return ext, nil
} }
@ -207,12 +209,20 @@ func (m *Manager) parseGitExtensionDir(fi fs.FileInfo) (Extension, error) {
exePath := filepath.Join(id, fi.Name(), fi.Name()) exePath := filepath.Join(id, fi.Name(), fi.Name())
remoteUrl := m.getRemoteUrl(fi.Name()) remoteUrl := m.getRemoteUrl(fi.Name())
currentVersion := m.getCurrentVersion(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{ return Extension{
path: exePath, path: exePath,
url: remoteUrl, url: remoteUrl,
isLocal: false, isLocal: false,
currentVersion: currentVersion, currentVersion: currentVersion,
kind: GitKind, kind: GitKind,
pin: pinnedVersion,
}, nil }, nil
} }
@ -313,15 +323,16 @@ func (m *Manager) InstallLocal(dir string) error {
} }
type binManifest struct { type binManifest struct {
Owner string Owner string
Name string Name string
Host string Host string
Tag string Tag string
Pinned bool
// TODO I may end up not using this; just thinking ahead to local installs // TODO I may end up not using this; just thinking ahead to local installs
Path string 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 { func (m *Manager) Install(repo ghrepo.Interface, targetCommitish string) error {
isBin, err := isBinExtension(m.client, repo) isBin, err := isBinExtension(m.client, repo)
if err != nil { 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 { func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) error {
log.Println("Installing binary extension")
var r *release var r *release
var err error var err error
if targetCommitish == "" { isPinned := targetCommitish != ""
if isPinned {
r, err = fetchLatestRelease(m.client, repo) r, err = fetchLatestRelease(m.client, repo)
} else { } else {
r, err = fetchReleaseFromTag(m.client, repo, targetCommitish) r, err = fetchReleaseFromTag(m.client, repo, targetCommitish)
@ -372,6 +383,7 @@ func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) erro
name := repo.RepoName() name := repo.RepoName()
targetDir := filepath.Join(m.installDir(), name) targetDir := filepath.Join(m.installDir(), name)
// TODO clean this up if function errs? // TODO clean this up if function errs?
err = os.MkdirAll(targetDir, 0755) err = os.MkdirAll(targetDir, 0755)
if err != nil { if err != nil {
@ -387,11 +399,12 @@ func (m *Manager) installBin(repo ghrepo.Interface, targetCommitish string) erro
} }
manifest := binManifest{ manifest := binManifest{
Name: name, Name: name,
Owner: repo.RepoOwner(), Owner: repo.RepoOwner(),
Host: repo.RepoHost(), Host: repo.RepoHost(),
Path: binPath, Path: binPath,
Tag: r.Tag, Tag: r.Tag,
Pinned: isPinned,
} }
bs, err := yaml.Marshal(manifest) 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 := m.newCommand(exe, "-C", targetDir, "checkout", commitSHA)
checkoutCmd.Stdout = stdout checkoutCmd.Stdout = stdout
checkoutCmd.Stderr = stderr 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 localExtensionUpgradeError = errors.New("local extensions can not be upgraded")
var upToDateError = errors.New("already up to date") var upToDateError = errors.New("already up to date")
var noExtensionsInstalledError = errors.New("no extensions installed") var noExtensionsInstalledError = errors.New("no extensions installed")
@ -508,6 +532,9 @@ func (m *Manager) upgradeExtension(ext Extension, force bool) error {
if ext.isLocal { if ext.isLocal {
return localExtensionUpgradeError return localExtensionUpgradeError
} }
if ext.pin != "" {
return pinnedExtensionUpgradeError
}
if !ext.UpdateAvailable() { if !ext.UpdateAvailable() {
return upToDateError return upToDateError
} }

View file

@ -19,9 +19,10 @@ type Extension interface {
Name() string // Extension Name without gh- Name() string // Extension Name without gh-
Path() string // Path to executable Path() string // Path to executable
URL() string URL() string
IsLocal() bool Pin() string // Pinned version
UpdateAvailable() bool UpdateAvailable() bool
IsBinary() bool IsBinary() bool
IsLocal() bool
} }
//go:generate moq -rm -out manager_mock.go . ExtensionManager //go:generate moq -rm -out manager_mock.go . ExtensionManager

View file

@ -29,6 +29,9 @@ var _ Extension = &ExtensionMock{}
// PathFunc: func() string { // PathFunc: func() string {
// panic("mock out the Path method") // panic("mock out the Path method")
// }, // },
// PinFunc: func() string {
// panic("mock out the Pin method")
// },
// URLFunc: func() string { // URLFunc: func() string {
// panic("mock out the URL method") // panic("mock out the URL method")
// }, // },
@ -54,6 +57,9 @@ type ExtensionMock struct {
// PathFunc mocks the Path method. // PathFunc mocks the Path method.
PathFunc func() string PathFunc func() string
// PinFunc mocks the Pin method.
PinFunc func() string
// URLFunc mocks the URL method. // URLFunc mocks the URL method.
URLFunc func() string URLFunc func() string
@ -74,6 +80,9 @@ type ExtensionMock struct {
// Path holds details about calls to the Path method. // Path holds details about calls to the Path method.
Path []struct { Path []struct {
} }
// Pin holds details about calls to the Pin method.
Pin []struct {
}
// URL holds details about calls to the URL method. // URL holds details about calls to the URL method.
URL []struct { URL []struct {
} }
@ -85,6 +94,7 @@ type ExtensionMock struct {
lockIsLocal sync.RWMutex lockIsLocal sync.RWMutex
lockName sync.RWMutex lockName sync.RWMutex
lockPath sync.RWMutex lockPath sync.RWMutex
lockPin sync.RWMutex
lockURL sync.RWMutex lockURL sync.RWMutex
lockUpdateAvailable sync.RWMutex lockUpdateAvailable sync.RWMutex
} }
@ -193,6 +203,32 @@ func (mock *ExtensionMock) PathCalls() []struct {
return calls 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. // URL calls URLFunc.
func (mock *ExtensionMock) URL() string { func (mock *ExtensionMock) URL() string {
if mock.URLFunc == nil { if mock.URLFunc == nil {