From ec9922742010818d85dcf3d23d3fcc7588a3b15a Mon Sep 17 00:00:00 2001 From: Grzegorz Dlugoszewski Date: Sun, 24 Aug 2025 11:30:22 +0200 Subject: [PATCH 1/5] Add missing git-list shim in scoop, ensure the shim is deleted when git-get is uninstalled --- .goreleaser.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index b0f87a7..d633872 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -130,5 +130,9 @@ scoops: description: "Better way to clone, organize and manage multiple git repositories" license: MIT post_install: [ - "New-Item -ItemType HardLink -Path \"$dir\\git-list.exe\" -Target \"$dir\\git-get.exe\" -Force | Out-Null" + "New-Item -ItemType HardLink -Path \"$dir\\git-list.exe\" -Target \"$dir\\git-get.exe\" -Force | Out-Null", + "scoop shim add git-list \"$dir\\git-list.exe\"" + ], + post_uninstall: [ + "scoop shim rm git-list" ] From 8a9002e6788f6fe90fd3ec6521cb996b5f523c4b Mon Sep 17 00:00:00 2001 From: Grzegorz Dlugoszewski Date: Sun, 24 Aug 2025 12:39:27 +0200 Subject: [PATCH 2/5] Replace the godirwalk package with a native implementation, replace pathpkg with filepath for better cross-platform support --- go.mod | 1 - go.sum | 2 - pkg/git/finder.go | 96 ++++++++++++++++++++--------------------------- pkg/run/run.go | 4 +- 4 files changed, 43 insertions(+), 60 deletions(-) diff --git a/go.mod b/go.mod index 0a8c577..38eda42 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module git-get go 1.24 require ( - github.com/karrick/godirwalk v1.17.0 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 9fc8d4a..69d6f83 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= -github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/pkg/git/finder.go b/pkg/git/finder.go index 61647e0..b9fd39f 100644 --- a/pkg/git/finder.go +++ b/pkg/git/finder.go @@ -1,26 +1,19 @@ package git import ( - "errors" "fmt" + "io/fs" "os" "path/filepath" "sort" "strings" - "syscall" - - "github.com/karrick/godirwalk" ) // Max number of concurrently running status loading workers. const maxWorkers = 100 -// errSkipNode is used as an error indicating that .git directory has been found. -// It's handled by ErrorsCallback to tell the WalkCallback to skip this dir. -var errSkipNode = errors.New(".git directory found, skipping this node") - -var errDirNoAccess = errors.New("directory can't be accessed") -var errDirNotExist = errors.New("directory doesn't exist") +var errDirNoAccess = fmt.Errorf("directory can't be accessed") +var errDirNotExist = fmt.Errorf("directory doesn't exist") // Exists returns true if a directory exists. If it doesn't or the directory can't be accessed it returns an error. func Exists(path string) (bool, error) { @@ -30,10 +23,8 @@ func Exists(path string) (bool, error) { return true, nil } - if err != nil { - if os.IsNotExist(err) { - return false, fmt.Errorf("can't access %s: %w", path, errDirNotExist) - } + if os.IsNotExist(err) { + return false, fmt.Errorf("can't access %s: %w", path, errDirNotExist) } // Directory exists but can't be accessed @@ -60,19 +51,43 @@ func NewRepoFinder(root string) *RepoFinder { // Returns error if root repo path can't be found or accessed. func (f *RepoFinder) Find() error { if _, err := Exists(f.root); err != nil { - return err + return fmt.Errorf("failed to access root path: %w", err) } - walkOpts := &godirwalk.Options{ - ErrorCallback: f.errorCb, - Callback: f.walkCb, - // Use Unsorted to improve speed because repos will be processed by goroutines in a random order anyway. - Unsorted: true, - } + err := filepath.WalkDir(f.root, func(path string, d fs.DirEntry, err error) error { + // Handle walk errors + if err != nil { + // Skip permission errors but continue walking + if os.IsPermission(err) { + return nil // Skip this path but continue + } + return fmt.Errorf("failed to walk %s: %w", path, err) + } + + // Only process directories + if !d.IsDir() { + return nil + } + + // Case 1: We're looking at a .git directory itself + if d.Name() == dotgit { + parentPath := filepath.Dir(path) + f.addIfOk(parentPath) + return fs.SkipDir // Skip the .git directory contents + } + + // Case 2: Check if this directory contains a .git subdirectory + gitPath := filepath.Join(path, dotgit) + if _, err := os.Stat(gitPath); err == nil { + f.addIfOk(path) + return fs.SkipDir // Skip this directory's contents since it's a repo + } + + return nil // Continue walking + }) - err := godirwalk.Walk(f.root, walkOpts) if err != nil { - return err + return fmt.Errorf("failed to walk directory tree: %w", err) } if len(f.repos) == 0 { @@ -131,41 +146,12 @@ func statusWorker(fetch bool, reposChan <-chan *Repo, statusChan chan<- *Status) } } -func (f *RepoFinder) walkCb(path string, ent *godirwalk.Dirent) error { - // Do not traverse .git directories - if ent.IsDir() && ent.Name() == dotgit { - f.addIfOk(path) - return errSkipNode - } - - // Do not traverse directories containing a .git directory - if ent.IsDir() { - _, err := os.Stat(filepath.Join(path, dotgit)) - if err == nil { - f.addIfOk(path) - return errSkipNode - } - } - return nil -} - // addIfOk adds the found repo to the repos slice if it can be opened. func (f *RepoFinder) addIfOk(path string) { - // TODO: is the case below really correct? What if there's a race condition and the dir becomes unaccessible between finding it and opening? - - // Open() should never return an error here. If a finder found a .git inside this dir, it means it could open and access it. - // If the dir was unaccessible, then it would have been skipped by the check in errorCb(). - repo, err := Open(strings.TrimSuffix(path, dotgit)) + // Open() should never return an error here since we already verified the .git directory exists. + // The path should already be the repository root (not the .git subdirectory). + repo, err := Open(path) if err == nil { f.repos = append(f.repos, repo) } } - -func (f *RepoFinder) errorCb(_ string, err error) godirwalk.ErrorAction { - // Skip .git directory and directories we don't have permissions to access - // TODO: Will syscall.EACCES work on windows? - if errors.Is(err, errSkipNode) || errors.Is(err, syscall.EACCES) { - return godirwalk.SkipNode - } - return godirwalk.Halt -} diff --git a/pkg/run/run.go b/pkg/run/run.go index aa571bc..4b8216b 100644 --- a/pkg/run/run.go +++ b/pkg/run/run.go @@ -6,7 +6,7 @@ import ( "fmt" "os" "os/exec" - pathpkg "path" + "path/filepath" "strings" ) @@ -45,7 +45,7 @@ func (c *Cmd) OnRepo(path string) *Cmd { return c } - insert := []string{"--work-tree", path, "--git-dir", pathpkg.Join(path, ".git")} + insert := []string{"--work-tree", path, "--git-dir", filepath.Join(path, ".git")} // Insert into the args slice after the 1st element (https://github.com/golang/go/wiki/SliceTricks#insert) c.cmd.Args = append(c.cmd.Args[:1], append(insert, c.cmd.Args[1:]...)...) From d7bc3b3a8f366919e8ad07b13e30fd2a4afbbf38 Mon Sep 17 00:00:00 2001 From: Grzegorz Dlugoszewski Date: Sun, 24 Aug 2025 13:16:14 +0200 Subject: [PATCH 3/5] Replace scripts with a simple symlink creation in nfpm packages goreleaser config --- .goreleaser.yml | 7 ++++--- release/scripts/postinstall.sh | 3 --- release/scripts/preremove.sh | 3 --- 3 files changed, 4 insertions(+), 9 deletions(-) delete mode 100755 release/scripts/postinstall.sh delete mode 100755 release/scripts/preremove.sh diff --git a/.goreleaser.yml b/.goreleaser.yml index d633872..24448be 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -110,9 +110,10 @@ nfpms: formats: - deb - rpm - scripts: - postinstall: "release/scripts/postinstall.sh" - preremove: "release/scripts/preremove.sh" + contents: + - src: /usr/local/bin/git-get + dst: /usr/local/bin/git-list + type: "symlink" scoops: - name: git-get diff --git a/release/scripts/postinstall.sh b/release/scripts/postinstall.sh deleted file mode 100755 index 5af3cdf..0000000 --- a/release/scripts/postinstall.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -# Create symlink for git-list command -ln -sf /usr/local/bin/git-get /usr/local/bin/git-list \ No newline at end of file diff --git a/release/scripts/preremove.sh b/release/scripts/preremove.sh deleted file mode 100755 index b013566..0000000 --- a/release/scripts/preremove.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -# Remove symlink for git-list command -rm -f /usr/local/bin/git-list \ No newline at end of file From b1e76b9071f3025cde3bf8a29c45add1a917675d Mon Sep 17 00:00:00 2001 From: Grzegorz Dlugoszewski Date: Sun, 24 Aug 2025 13:28:48 +0200 Subject: [PATCH 4/5] Move the scoop manifests to homebrew-tap repo --- .goreleaser.yml | 4 ++-- README.md | 2 +- release/bucket/git-get.json | 25 ------------------------- 3 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 release/bucket/git-get.json diff --git a/.goreleaser.yml b/.goreleaser.yml index 24448be..54e1bf7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -119,9 +119,9 @@ scoops: - name: git-get repository: owner: grdl - name: git-get + name: homebrew-tap branch: master - directory: release/bucket + directory: Scoop url_template: "https://github.com/grdl/git-get/releases/download/{{ .Tag }}/{{ .ArtifactName }}" commit_author: name: Grzegorz Dlugoszewski diff --git a/README.md b/README.md index ddfecb1..7f34ca0 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ brew install grdl/tap/git-get **Option 1: Scoop (Recommended)** ```powershell -scoop bucket add grdl https://github.com/grdl/git-get +scoop bucket add grdl https://github.com/grdl/homebrew-tap scoop install git-get ``` *This automatically creates both `git-get.exe` and `git-list.exe` commands.* diff --git a/release/bucket/git-get.json b/release/bucket/git-get.json deleted file mode 100644 index ae26578..0000000 --- a/release/bucket/git-get.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "version": "0.6.0", - "architecture": { - "64bit": { - "url": "https://github.com/grdl/git-get/releases/download/v0.6.0/git-get_0.6.0_windows_amd64vv1.zip", - "bin": [ - "git-get.exe" - ], - "hash": "77370170adbf2021047b0b9bc363b5f19b308a6d28382c5fc92d333509470926" - }, - "arm64": { - "url": "https://github.com/grdl/git-get/releases/download/v0.6.0/git-get_0.6.0_windows_arm64.zip", - "bin": [ - "git-get.exe" - ], - "hash": "3900843203ec27e4b7d4a859e1b0b5c474661bbfd98ee8586d8e3744cb8a8d55" - } - }, - "homepage": "https://github.com/grdl/git-get", - "license": "MIT", - "description": "Better way to clone, organize and manage multiple git repositories", - "post_install": [ - "New-Item -ItemType HardLink -Path \"$dir\\git-list.exe\" -Target \"$dir\\git-get.exe\" -Force | Out-Null" - ] -} \ No newline at end of file From 1962cfeddf2cc471f9b97ca62971cff970bf079d Mon Sep 17 00:00:00 2001 From: Grzegorz Dlugoszewski Date: Sun, 24 Aug 2025 13:34:25 +0200 Subject: [PATCH 5/5] Disable automatic changelog generation in goreleaser --- .goreleaser.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 54e1bf7..0064c1a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -61,14 +61,7 @@ checksum: name_template: 'checksums.txt' changelog: - sort: asc - use: github - filters: - exclude: - - '^docs:' - - '^test:' - - '^chore' - - typo + disable: true # Changelogs are crafted manually release: github: