Add update checking to extensions list
This commit is contained in:
parent
3ff94ae76b
commit
e70bdbf7a9
6 changed files with 114 additions and 25 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ type Extension interface {
|
|||
Path() string
|
||||
URL() string
|
||||
IsLocal() bool
|
||||
UpdateAvailable() bool
|
||||
}
|
||||
|
||||
//go:generate moq -out manager_mock.go . ExtensionManager
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue