Skip to content

Commit 4bdddd7

Browse files
committed
Allow installing local extensions via symlinks
This also quits searching for local extensions in PATH.
1 parent fce93d6 commit 4bdddd7

File tree

2 files changed

+21
-42
lines changed

2 files changed

+21
-42
lines changed

pkg/cmd/extensions/command.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package extensions
33
import (
44
"errors"
55
"fmt"
6+
"os"
67
"path/filepath"
78
"strings"
89

@@ -43,6 +44,13 @@ func NewCmdExtensions(io *iostreams.IOStreams) *cobra.Command {
4344
Short: "Install a gh extension from a repository",
4445
Args: cmdutil.MinimumArgs(1, "must specify a repository to install from"),
4546
RunE: func(cmd *cobra.Command, args []string) error {
47+
if args[0] == "." {
48+
wd, err := os.Getwd()
49+
if err != nil {
50+
return err
51+
}
52+
return m.InstallLocal(wd)
53+
}
4654
repo, err := ghrepo.FromFullName(args[0])
4755
if err != nil {
4856
return err

pkg/cmd/extensions/manager.go

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package extensions
22

33
import (
44
"errors"
5+
"fmt"
56
"io"
67
"io/ioutil"
78
"os"
@@ -17,14 +18,12 @@ import (
1718
type Manager struct {
1819
dataDir func() string
1920
lookPath func(string) (string, error)
20-
pathEnv string
2121
}
2222

2323
func NewManager() *Manager {
2424
return &Manager{
2525
dataDir: config.ConfigDir,
2626
lookPath: safeexec.LookPath,
27-
pathEnv: os.Getenv("PATH"),
2827
}
2928
}
3029

@@ -37,18 +36,14 @@ func (m *Manager) Dispatch(args []string, stdin io.Reader, stdout, stderr io.Wri
3736
extName := "gh-" + args[0]
3837
forwardArgs := args[1:]
3938

40-
for _, e := range m.listInstalled() {
39+
for _, e := range m.List() {
4140
if filepath.Base(e) == extName {
4241
exe = e
4342
break
4443
}
4544
}
4645
if exe == "" {
47-
var err error
48-
exe, err = m.lookPath(extName)
49-
if err != nil {
50-
return false, nil
51-
}
46+
return false, nil
5247
}
5348

5449
// TODO: parse the shebang on Windows and invoke the correct interpreter instead of invoking directly
@@ -59,7 +54,7 @@ func (m *Manager) Dispatch(args []string, stdin io.Reader, stdout, stderr io.Wri
5954
return true, externalCmd.Run()
6055
}
6156

62-
func (m *Manager) listInstalled() []string {
57+
func (m *Manager) List() []string {
6358
dir := m.installDir()
6459
entries, err := ioutil.ReadDir(dir)
6560
if err != nil {
@@ -68,39 +63,18 @@ func (m *Manager) listInstalled() []string {
6863

6964
var results []string
7065
for _, f := range entries {
71-
if !strings.HasPrefix(f.Name(), "gh-") || !f.IsDir() {
66+
if !strings.HasPrefix(f.Name(), "gh-") || !(f.IsDir() || f.Mode()&os.ModeSymlink != 0) {
7267
continue
7368
}
7469
results = append(results, filepath.Join(dir, f.Name(), f.Name()))
7570
}
7671
return results
7772
}
7873

79-
func (m *Manager) List() []string {
80-
results := m.listInstalled()
81-
seen := make(map[string]struct{})
82-
for _, f := range results {
83-
seen[filepath.Base(f)] = struct{}{}
84-
}
85-
86-
for _, p := range filepath.SplitList(m.pathEnv) {
87-
entries, err := ioutil.ReadDir(p)
88-
if err != nil {
89-
continue
90-
}
91-
for _, f := range entries {
92-
if _, ok := seen[f.Name()]; ok {
93-
continue
94-
}
95-
if !strings.HasPrefix(f.Name(), "gh-") || !isExecutable(f) {
96-
continue
97-
}
98-
results = append(results, filepath.Join(p, f.Name()))
99-
seen[f.Name()] = struct{}{}
100-
}
101-
}
102-
103-
return results
74+
func (m *Manager) InstallLocal(dir string) error {
75+
name := filepath.Base(dir)
76+
targetDir := filepath.Join(m.installDir(), name)
77+
return os.Symlink(dir, targetDir)
10478
}
10579

10680
func (m *Manager) Install(cloneURL string, stdout, stderr io.Writer) error {
@@ -124,13 +98,15 @@ func (m *Manager) Upgrade(stdout, stderr io.Writer) error {
12498
return err
12599
}
126100

127-
exts := m.listInstalled()
101+
exts := m.List()
128102
if len(exts) == 0 {
129103
return errors.New("no extensions installed")
130104
}
131105

132106
for _, f := range exts {
133-
externalCmd := exec.Command(exe, "-C", filepath.Dir(f), "pull", "--ff-only")
107+
fmt.Fprintf(stdout, "[%s]: ", filepath.Base(f))
108+
dir := filepath.Dir(f)
109+
externalCmd := exec.Command(exe, "-C", dir, "--git-dir="+filepath.Join(dir, ".git"), "pull", "--ff-only")
134110
externalCmd.Stdout = stdout
135111
externalCmd.Stderr = stderr
136112
if e := externalCmd.Run(); e != nil {
@@ -143,8 +119,3 @@ func (m *Manager) Upgrade(stdout, stderr io.Writer) error {
143119
func (m *Manager) installDir() string {
144120
return filepath.Join(m.dataDir(), "extensions")
145121
}
146-
147-
// TODO: ignore file mode on Windows
148-
func isExecutable(f os.FileInfo) bool {
149-
return !f.IsDir() && f.Mode()&0111 != 0
150-
}

0 commit comments

Comments
 (0)