From bed630452bb2c29fbef3134479070f2e6d724b7b 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 | 8 ++++- pkg/cmd/extension/extension.go | 8 ++++- pkg/cmd/extension/manager.go | 57 +++++++++++++++++++++++--------- pkg/extensions/extension.go | 3 +- pkg/extensions/extension_mock.go | 36 ++++++++++++++++++++ 5 files changed, 93 insertions(+), 19 deletions(-) diff --git a/pkg/cmd/extension/command.go b/pkg/cmd/extension/command.go index ee0dbd512..ec756edfa 100644 --- a/pkg/cmd/extension/command.go +++ b/pkg/cmd/extension/command.go @@ -65,7 +65,13 @@ func NewCmdExtension(f *cmdutil.Factory) *cobra.Command { if !c.IsBinary() && len(version) > 8 { version = version[:8] } - t.AddField(version, nil, nil) + + if c.IsPinned() { + t.AddField(version, nil, cs.Cyan) + } else { + t.AddField(version, nil, nil) + } + var updateAvailable string if c.UpdateAvailable() { updateAvailable = "Upgrade available" diff --git a/pkg/cmd/extension/extension.go b/pkg/cmd/extension/extension.go index d1b814c57..e4f109d83 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 + isPinned bool currentVersion string latestVersion string kind ExtensionKind @@ -43,8 +44,13 @@ func (e *Extension) CurrentVersion() string { return e.currentVersion } +func (e *Extension) IsPinned() bool { + return e.isPinned +} + func (e *Extension) UpdateAvailable() bool { - if e.isLocal || + if e.isPinned || + 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..031e6d3a7 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,7 @@ func (m *Manager) parseBinaryExtensionDir(fi fs.FileInfo) (Extension, error) { remoteURL := ghrepo.GenerateRepoURL(repo, "") ext.url = remoteURL ext.currentVersion = bm.Tag + ext.isPinned = bm.IsPinned return ext, nil } @@ -207,12 +207,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 isPinned bool + pinPath := filepath.Join(id, fi.Name(), fmt.Sprintf(".pin-%s", currentVersion)) + if _, err := os.Stat(pinPath); err == nil { + isPinned = true + } + return Extension{ path: exePath, url: remoteUrl, isLocal: false, currentVersion: currentVersion, kind: GitKind, + isPinned: isPinned, }, nil } @@ -313,15 +321,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 + IsPinned 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,13 +352,13 @@ 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 == "" { - r, err = fetchLatestRelease(m.client, repo) - } else { + isPinned := targetCommitish != "" + if isPinned { r, err = fetchReleaseFromTag(m.client, repo, targetCommitish) + } else { + r, err = fetchLatestRelease(m.client, repo) } if err != nil { return err @@ -372,6 +381,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 +397,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, + IsPinned: isPinned, } bs, err := yaml.Marshal(manifest) @@ -448,9 +459,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 file 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 +530,9 @@ func (m *Manager) upgradeExtension(ext Extension, force bool) error { if ext.isLocal { return localExtensionUpgradeError } + if ext.IsPinned() { + return pinnedExtensionUpgradeError + } if !ext.UpdateAvailable() { return upToDateError } diff --git a/pkg/extensions/extension.go b/pkg/extensions/extension.go index fa1959ab7..aa1019a70 100644 --- a/pkg/extensions/extension.go +++ b/pkg/extensions/extension.go @@ -19,10 +19,11 @@ type Extension interface { Name() string // Extension Name without gh- Path() string // Path to executable URL() string - IsLocal() bool CurrentVersion() string + IsPinned() bool 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 5b418c3f1..e68c376f2 100644 --- a/pkg/extensions/extension_mock.go +++ b/pkg/extensions/extension_mock.go @@ -26,6 +26,9 @@ var _ Extension = &ExtensionMock{} // IsLocalFunc: func() bool { // panic("mock out the IsLocal method") // }, +// IsPinnedFunc: func() bool { +// panic("mock out the IsPinned method") +// }, // NameFunc: func() string { // panic("mock out the Name method") // }, @@ -54,6 +57,9 @@ type ExtensionMock struct { // IsLocalFunc mocks the IsLocal method. IsLocalFunc func() bool + // IsPinnedFunc mocks the IsPinned method. + IsPinnedFunc func() bool + // NameFunc mocks the Name method. NameFunc func() string @@ -77,6 +83,9 @@ type ExtensionMock struct { // IsLocal holds details about calls to the IsLocal method. IsLocal []struct { } + // IsPinned holds details about calls to the IsPinned method. + IsPinned []struct { + } // Name holds details about calls to the Name method. Name []struct { } @@ -93,6 +102,7 @@ type ExtensionMock struct { lockCurrentVersion sync.RWMutex lockIsBinary sync.RWMutex lockIsLocal sync.RWMutex + lockIsPinned sync.RWMutex lockName sync.RWMutex lockPath sync.RWMutex lockURL sync.RWMutex @@ -177,6 +187,32 @@ func (mock *ExtensionMock) IsLocalCalls() []struct { return calls } +// IsPinned calls IsPinnedFunc. +func (mock *ExtensionMock) IsPinned() bool { + if mock.IsPinnedFunc == nil { + panic("ExtensionMock.IsPinnedFunc: method is nil but Extension.IsPinned was just called") + } + callInfo := struct { + }{} + mock.lockIsPinned.Lock() + mock.calls.IsPinned = append(mock.calls.IsPinned, callInfo) + mock.lockIsPinned.Unlock() + return mock.IsPinnedFunc() +} + +// IsPinnedCalls gets all the calls that were made to IsPinned. +// Check the length with: +// len(mockedExtension.IsPinnedCalls()) +func (mock *ExtensionMock) IsPinnedCalls() []struct { +} { + var calls []struct { + } + mock.lockIsPinned.RLock() + calls = mock.calls.IsPinned + mock.lockIsPinned.RUnlock() + return calls +} + // Name calls NameFunc. func (mock *ExtensionMock) Name() string { if mock.NameFunc == nil {