Add update checking to extensions list

This commit is contained in:
Sam Coe 2021-06-24 12:04:54 -07:00
parent 3ff94ae76b
commit e70bdbf7a9
No known key found for this signature in database
GPG key ID: 8E322C20F811D086
6 changed files with 114 additions and 25 deletions

View file

@ -43,7 +43,7 @@ func NewCmdExtensions(f *cmdutil.Factory) *cobra.Command {
if len(cmds) == 0 {
return errors.New("no extensions installed")
}
// cs := io.ColorScheme()
cs := io.ColorScheme()
t := utils.NewTablePrinter(io)
for _, c := range cmds {
var repo string
@ -55,8 +55,11 @@ func NewCmdExtensions(f *cmdutil.Factory) *cobra.Command {
t.AddField(fmt.Sprintf("gh %s", c.Name()), nil, nil)
t.AddField(repo, nil, nil)
// TODO: add notice about available update
//t.AddField("Update available", nil, cs.Green)
var updateAvailable string
if c.UpdateAvailable() {
updateAvailable = "Update available"
}
t.AddField(updateAvailable, nil, cs.Green)
t.EndRow()
}
return t.Render()

View file

@ -146,6 +146,21 @@ func TestNewCmdExtensions(t *testing.T) {
isTTY: false,
wantStdout: "",
},
{
name: "list extensions",
args: []string{"list"},
managerStubs: func(em *extensions.ExtensionManagerMock) func(*testing.T) {
em.ListFunc = func() []extensions.Extension {
ex1 := &Extension{path: "cli/gh-test", url: "https://github.com/cli/gh-test", updateAvailable: false}
ex2 := &Extension{path: "cli/gh-test2", url: "https://github.com/cli/gh-test2", updateAvailable: true}
return []extensions.Extension{ex1, ex2}
}
return func(t *testing.T) {
assert.Equal(t, 1, len(em.ListCalls()))
}
},
wantStdout: "gh test\tcli/gh-test\t\ngh test2\tcli/gh-test2\tUpdate available\n",
},
}
for _, tt := range tests {

View file

@ -8,8 +8,9 @@ import (
)
type Extension struct {
path string
url string
path string
url string
updateAvailable bool
}
func (e *Extension) Name() string {
@ -40,3 +41,7 @@ func (e *Extension) IsLocal() bool {
}
return false
}
func (e *Extension) UpdateAvailable() bool {
return e.updateAvailable
}

View file

@ -87,34 +87,63 @@ func (m *Manager) list(includeMetadata bool) []extensions.Extension {
if err != nil {
return nil
}
var gitExe string
if includeMetadata {
gitExe, _ = m.lookPath("git")
}
var results []extensions.Extension
for _, f := range entries {
if !strings.HasPrefix(f.Name(), "gh-") || !(f.IsDir() || f.Mode()&os.ModeSymlink != 0) {
continue
}
var remoteURL string
if gitExe != "" {
stdout := bytes.Buffer{}
cmd := m.newCommand(gitExe, "--git-dir="+filepath.Join(dir, f.Name(), ".git"), "config", "remote.origin.url")
cmd.Stdout = &stdout
if err := cmd.Run(); err == nil {
remoteURL = strings.TrimSpace(stdout.String())
}
var remoteUrl string
var updateAvailable bool
if includeMetadata {
remoteUrl = m.getRemoteUrl(f.Name())
updateAvailable = m.checkUpdateAvailable(f.Name())
}
results = append(results, &Extension{
path: filepath.Join(dir, f.Name(), f.Name()),
url: remoteURL,
path: filepath.Join(dir, f.Name(), f.Name()),
url: remoteUrl,
updateAvailable: updateAvailable,
})
}
return results
}
func (m *Manager) getRemoteUrl(extension string) string {
gitExe, err := m.lookPath("git")
if err != nil {
return ""
}
dir := m.installDir()
gitDir := "--git-dir=" + filepath.Join(dir, extension, ".git")
cmd := m.newCommand(gitExe, gitDir, "config", "remote.origin.url")
url, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(url))
}
func (m *Manager) checkUpdateAvailable(extension string) bool {
gitExe, err := m.lookPath("git")
if err != nil {
return false
}
dir := m.installDir()
gitDir := "--git-dir=" + filepath.Join(dir, extension, ".git")
cmd := m.newCommand(gitExe, gitDir, "ls-remote", "origin", "HEAD")
lsRemote, err := cmd.Output()
if err != nil {
return false
}
remoteSha := bytes.SplitN(lsRemote, []byte("\t"), 2)[0]
cmd = m.newCommand(gitExe, gitDir, "rev-parse", "HEAD")
localSha, err := cmd.Output()
if err != nil {
return false
}
localSha = bytes.TrimSpace(localSha)
return !bytes.Equal(remoteSha, localSha)
}
func (m *Manager) InstallLocal(dir string) error {
name := filepath.Base(dir)
targetDir := filepath.Join(m.installDir(), name)

View file

@ -10,6 +10,7 @@ type Extension interface {
Path() string
URL() string
IsLocal() bool
UpdateAvailable() bool
}
//go:generate moq -out manager_mock.go . ExtensionManager

View file

@ -29,6 +29,9 @@ var _ Extension = &ExtensionMock{}
// URLFunc: func() string {
// panic("mock out the URL method")
// },
// UpdateAvailableFunc: func() bool {
// panic("mock out the UpdateAvailable method")
// },
// }
//
// // use mockedExtension in code that requires Extension
@ -48,6 +51,9 @@ type ExtensionMock struct {
// URLFunc mocks the URL method.
URLFunc func() string
// UpdateAvailableFunc mocks the UpdateAvailable method.
UpdateAvailableFunc func() bool
// calls tracks calls to the methods.
calls struct {
// IsLocal holds details about calls to the IsLocal method.
@ -62,11 +68,15 @@ type ExtensionMock struct {
// URL holds details about calls to the URL method.
URL []struct {
}
// UpdateAvailable holds details about calls to the UpdateAvailable method.
UpdateAvailable []struct {
}
}
lockIsLocal sync.RWMutex
lockName sync.RWMutex
lockPath sync.RWMutex
lockURL sync.RWMutex
lockIsLocal sync.RWMutex
lockName sync.RWMutex
lockPath sync.RWMutex
lockURL sync.RWMutex
lockUpdateAvailable sync.RWMutex
}
// IsLocal calls IsLocalFunc.
@ -172,3 +182,29 @@ func (mock *ExtensionMock) URLCalls() []struct {
mock.lockURL.RUnlock()
return calls
}
// UpdateAvailable calls UpdateAvailableFunc.
func (mock *ExtensionMock) UpdateAvailable() bool {
if mock.UpdateAvailableFunc == nil {
panic("ExtensionMock.UpdateAvailableFunc: method is nil but Extension.UpdateAvailable was just called")
}
callInfo := struct {
}{}
mock.lockUpdateAvailable.Lock()
mock.calls.UpdateAvailable = append(mock.calls.UpdateAvailable, callInfo)
mock.lockUpdateAvailable.Unlock()
return mock.UpdateAvailableFunc()
}
// UpdateAvailableCalls gets all the calls that were made to UpdateAvailable.
// Check the length with:
// len(mockedExtension.UpdateAvailableCalls())
func (mock *ExtensionMock) UpdateAvailableCalls() []struct {
} {
var calls []struct {
}
mock.lockUpdateAvailable.RLock()
calls = mock.calls.UpdateAvailable
mock.lockUpdateAvailable.RUnlock()
return calls
}