55 "errors"
66 "fmt"
77 "io"
8+ "io/fs"
89 "io/ioutil"
910 "net/http"
1011 "os"
@@ -116,36 +117,96 @@ func (m *Manager) list(includeMetadata bool) ([]extensions.Extension, error) {
116117 if ! strings .HasPrefix (f .Name (), "gh-" ) {
117118 continue
118119 }
119- var remoteUrl string
120- updateAvailable := false
121- isLocal := false
122- exePath := filepath .Join (dir , f .Name (), f .Name ())
123- if f .IsDir () {
124- if includeMetadata {
125- remoteUrl = m .getRemoteUrl (f .Name ())
126- updateAvailable = m .checkUpdateAvailable (f .Name ())
127- }
128- } else {
129- isLocal = true
130- if ! isSymlink (f .Mode ()) {
131- // if this is a regular file, its contents is the local directory of the extension
132- p , err := readPathFromFile (filepath .Join (dir , f .Name ()))
133- if err != nil {
134- return nil , err
135- }
136- exePath = filepath .Join (p , f .Name ())
137- }
120+ ext , err := m .parseExtensionDir (f , includeMetadata )
121+ if err != nil {
122+ return nil , err
138123 }
139- results = append (results , & Extension {
140- path : exePath ,
141- url : remoteUrl ,
142- isLocal : isLocal ,
143- updateAvailable : updateAvailable ,
144- })
124+ results = append (results , ext )
145125 }
126+
146127 return results , nil
147128}
148129
130+ func (m * Manager ) parseExtensionDir (fi fs.FileInfo , includeMetadata bool ) (* Extension , error ) {
131+ id := m .installDir ()
132+ if _ , err := os .Stat (filepath .Join (id , fi .Name (), manifestName )); err == nil {
133+ return m .parseBinaryExtensionDir (fi , includeMetadata )
134+ }
135+
136+ return m .parseGitExtensionDir (fi , includeMetadata )
137+ }
138+
139+ func (m * Manager ) parseBinaryExtensionDir (fi fs.FileInfo , includeMetadata bool ) (* Extension , error ) {
140+ id := m .installDir ()
141+ exePath := filepath .Join (id , fi .Name (), fi .Name ())
142+ manifestPath := filepath .Join (id , fi .Name (), manifestName )
143+ manifest , err := os .ReadFile (manifestPath )
144+ if err != nil {
145+ return nil , fmt .Errorf ("could not open %s for reading: %w" , manifestPath , err )
146+ }
147+
148+ var bm binManifest
149+ err = yaml .Unmarshal (manifest , & bm )
150+ if err != nil {
151+ return nil , fmt .Errorf ("could not parse %s: %w" , manifestPath , err )
152+ }
153+
154+ repo := ghrepo .NewWithHost (bm .Owner , bm .Name , bm .Host )
155+
156+ var remoteURL string
157+ var updateAvailable bool
158+
159+ if includeMetadata {
160+ remoteURL = ghrepo .GenerateRepoURL (repo , "" )
161+ var r * release
162+ r , err = fetchLatestRelease (m .client , repo )
163+ if err != nil {
164+ return nil , fmt .Errorf ("failed to get release info for %s: %w" , ghrepo .FullName (repo ), err )
165+ }
166+ if bm .Tag != r .Tag {
167+ updateAvailable = true
168+ }
169+ }
170+
171+ return & Extension {
172+ path : exePath ,
173+ url : remoteURL ,
174+ updateAvailable : updateAvailable ,
175+ }, nil
176+ }
177+
178+ func (m * Manager ) parseGitExtensionDir (fi fs.FileInfo , includeMetadata bool ) (* Extension , error ) {
179+ // TODO untangle local from this since local might be binary or git
180+ id := m .installDir ()
181+ var remoteUrl string
182+ updateAvailable := false
183+ isLocal := false
184+ exePath := filepath .Join (id , fi .Name (), fi .Name ())
185+ if fi .IsDir () {
186+ if includeMetadata {
187+ remoteUrl = m .getRemoteUrl (fi .Name ())
188+ updateAvailable = m .checkUpdateAvailable (fi .Name ())
189+ }
190+ } else {
191+ isLocal = true
192+ if ! isSymlink (fi .Mode ()) {
193+ // if this is a regular file, its contents is the local directory of the extension
194+ p , err := readPathFromFile (filepath .Join (id , fi .Name ()))
195+ if err != nil {
196+ return nil , err
197+ }
198+ exePath = filepath .Join (p , fi .Name ())
199+ }
200+ }
201+
202+ return & Extension {
203+ path : exePath ,
204+ url : remoteUrl ,
205+ isLocal : isLocal ,
206+ updateAvailable : updateAvailable ,
207+ }, nil
208+ }
209+
149210func (m * Manager ) getRemoteUrl (extension string ) string {
150211 gitExe , err := m .lookPath ("git" )
151212 if err != nil {
@@ -273,7 +334,7 @@ func (m *Manager) installBin(repo ghrepo.Interface) error {
273334 return fmt .Errorf ("failed to serialize manifest: %w" , err )
274335 }
275336
276- manifestPath := filepath .Join (targetDir , "manifest.yml" )
337+ manifestPath := filepath .Join (targetDir , manifestName )
277338
278339 f , err := os .OpenFile (manifestPath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , 0600 )
279340 if err != nil {
@@ -306,7 +367,7 @@ func (m *Manager) installGit(cloneURL string, stdout, stderr io.Writer) error {
306367
307368var localExtensionUpgradeError = errors .New ("local extensions can not be upgraded" )
308369
309- func (m * Manager ) Upgrade (name string , force bool , stdout , stderr io. Writer ) error {
370+ func (m * Manager ) Upgrade (name string , force bool ) error {
310371 exe , err := m .lookPath ("git" )
311372 if err != nil {
312373 return err
@@ -320,41 +381,82 @@ func (m *Manager) Upgrade(name string, force bool, stdout, stderr io.Writer) err
320381 someUpgraded := false
321382 for _ , f := range exts {
322383 if name == "" {
323- fmt .Fprintf (stdout , "[%s]: " , f .Name ())
384+ fmt .Fprintf (m . io . Out , "[%s]: " , f .Name ())
324385 } else if f .Name () != name {
325386 continue
326387 }
327388
328389 if f .IsLocal () {
329390 if name == "" {
330- fmt .Fprintf (stdout , "%s\n " , localExtensionUpgradeError )
391+ fmt .Fprintf (m . io . Out , "%s\n " , localExtensionUpgradeError )
331392 } else {
332393 err = localExtensionUpgradeError
333394 }
334395 continue
335396 }
336397
337- var cmds []* exec.Cmd
338- dir := filepath .Dir (f .Path ())
339- if force {
340- fetchCmd := m .newCommand (exe , "-C" , dir , "--git-dir=" + filepath .Join (dir , ".git" ), "fetch" , "origin" , "HEAD" )
341- resetCmd := m .newCommand (exe , "-C" , dir , "--git-dir=" + filepath .Join (dir , ".git" ), "reset" , "--hard" , "origin/HEAD" )
342- cmds = []* exec.Cmd {fetchCmd , resetCmd }
343- } else {
344- pullCmd := m .newCommand (exe , "-C" , dir , "--git-dir=" + filepath .Join (dir , ".git" ), "pull" , "--ff-only" )
345- cmds = []* exec.Cmd {pullCmd }
398+ binManifestPath := filepath .Join (filepath .Dir (f .Path ()), manifestName )
399+ if _ , e := os .Stat (binManifestPath ); e == nil {
400+ err = m .upgradeBin (f )
401+ someUpgraded = true
402+ continue
346403 }
347- if e := runCmds (cmds , stdout , stderr ); e != nil {
404+
405+ if e := m .upgradeGit (f , exe , force ); e != nil {
348406 err = e
349407 }
350408 someUpgraded = true
351409 }
410+
352411 if err == nil && ! someUpgraded {
353412 err = fmt .Errorf ("no extension matched %q" , name )
354413 }
414+
355415 return err
356416}
357417
418+ func (m * Manager ) upgradeGit (ext extensions.Extension , exe string , force bool ) error {
419+ var cmds []* exec.Cmd
420+ dir := filepath .Dir (ext .Path ())
421+ if force {
422+ fetchCmd := m .newCommand (exe , "-C" , dir , "--git-dir=" + filepath .Join (dir , ".git" ), "fetch" , "origin" , "HEAD" )
423+ resetCmd := m .newCommand (exe , "-C" , dir , "--git-dir=" + filepath .Join (dir , ".git" ), "reset" , "--hard" , "origin/HEAD" )
424+ cmds = []* exec.Cmd {fetchCmd , resetCmd }
425+ } else {
426+ pullCmd := m .newCommand (exe , "-C" , dir , "--git-dir=" + filepath .Join (dir , ".git" ), "pull" , "--ff-only" )
427+ cmds = []* exec.Cmd {pullCmd }
428+ }
429+
430+ return runCmds (cmds , m .io .Out , m .io .ErrOut )
431+ }
432+
433+ func (m * Manager ) upgradeBin (ext extensions.Extension ) error {
434+ manifestPath := filepath .Join (filepath .Dir (ext .Path ()), manifestName )
435+ manifest , err := os .ReadFile (manifestPath )
436+ if err != nil {
437+ return fmt .Errorf ("could not open %s for reading: %w" , manifestPath , err )
438+ }
439+
440+ var bm binManifest
441+ err = yaml .Unmarshal (manifest , & bm )
442+ if err != nil {
443+ return fmt .Errorf ("could not parse %s: %w" , manifestPath , err )
444+ }
445+ repo := ghrepo .NewWithHost (bm .Owner , bm .Name , bm .Host )
446+ var r * release
447+
448+ r , err = fetchLatestRelease (m .client , repo )
449+ if err != nil {
450+ return fmt .Errorf ("failed to get release info for %s: %w" , ghrepo .FullName (repo ), err )
451+ }
452+
453+ if bm .Tag == r .Tag {
454+ return nil
455+ }
456+
457+ return m .installBin (repo )
458+ }
459+
358460func (m * Manager ) Remove (name string ) error {
359461 targetDir := filepath .Join (m .installDir (), "gh-" + name )
360462 if _ , err := os .Lstat (targetDir ); os .IsNotExist (err ) {
0 commit comments