├── .devcontainer └── devcontainer.json ├── main.go ├── .github └── workflows │ ├── ci-docker.yml │ ├── ci.yml │ └── cd.yml ├── Dockerfile ├── cmd ├── gibo.go ├── root.go ├── update.go ├── version.go ├── dump.go ├── list.go └── search.go ├── tools └── delete-windows-release.sh ├── .gitignore ├── .runny.yaml ├── CONTRIBUTING.md ├── utils ├── fmt_test.go ├── fmt.go └── utils.go ├── LICENSE ├── go.mod ├── .goreleaser.yaml ├── README.md ├── .goreleaser.windows.yaml ├── CODE_OF_CONDUCT.md └── go.sum /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { "image": "mcr.microsoft.com/devcontainers/go" } 2 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/simonwhitaker/gibo/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/ci-docker.yml: -------------------------------------------------------------------------------- 1 | name: "CI: Test docker build" 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Docker build 16 | run: docker build -t gibo:latest . 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM golang:1.24.4-alpine AS build 4 | 5 | WORKDIR /app 6 | 7 | COPY go.mod go.sum ./ 8 | RUN go mod download 9 | 10 | COPY . ./ 11 | RUN CGO_ENABLED=0 GOOS=linux go build -o /gibo 12 | 13 | FROM alpine:3.18.2 14 | 15 | WORKDIR / 16 | 17 | COPY --from=build /gibo /gibo 18 | RUN /gibo update 19 | 20 | ENTRYPOINT [ "/gibo" ] 21 | -------------------------------------------------------------------------------- /cmd/gibo.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var giboCmd = &cobra.Command{ 11 | Use: "gibo", 12 | Short: "gibo is a command-line tool for easily accessing gitignore boilerplates", 13 | } 14 | 15 | func Execute() { 16 | if err := giboCmd.Execute(); err != nil { 17 | fmt.Println(err) 18 | os.Exit(1) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/simonwhitaker/gibo/utils" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | giboCmd.AddCommand(rootCmd) 12 | } 13 | 14 | var rootCmd = &cobra.Command{ 15 | Use: "root", 16 | Short: "Show the directory where gibo stores its boilerplates", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | fmt.Println(utils.RepoDir()) 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /tools/delete-windows-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | default_tag=$(git describe) 4 | tag=${1:-$default_tag} 5 | 6 | if [ -z "$1" ]; then 7 | echo "Defaulted to version $tag" 8 | fi 9 | 10 | # Quit early if the release doesn't exist 11 | gh release view "$tag" >/dev/null || exit 1 12 | 13 | gh release delete-asset -y "$tag" checksums.windows.txt 14 | gh release delete-asset -y "$tag" gibo_Windows_x86_64.zip 15 | gh release delete-asset -y "$tag" gibo_Windows_i386.zip 16 | gh release delete-asset -y "$tag" gibo_Windows_arm64.zip 17 | -------------------------------------------------------------------------------- /cmd/update.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/simonwhitaker/gibo/utils" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | giboCmd.AddCommand(updateCmd) 13 | } 14 | 15 | var updateCmd = &cobra.Command{ 16 | Use: "update", 17 | Short: "Update the gitignore boilerplate repository", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | msg, err := utils.Update() 20 | if err != nil { 21 | log.Fatal(err) 22 | } else { 23 | fmt.Println(msg) 24 | } 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // These vars are populated at build time via the `-ldflags` configured in 10 | // `.gorelease.yml`. More info at https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications 11 | var ( 12 | version = "dev" 13 | ) 14 | 15 | func init() { 16 | giboCmd.AddCommand(versionCmd) 17 | } 18 | 19 | var versionCmd = &cobra.Command{ 20 | Use: "version", 21 | Short: "Show the current version number of gibo", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | fmt.Println(version) 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /cmd/dump.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/simonwhitaker/gibo/utils" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | giboCmd.AddCommand(dumpCmd) 12 | } 13 | 14 | var dumpCmd = &cobra.Command{ 15 | Use: "dump", 16 | Short: "Dump one or more named boilerplates", 17 | Args: cobra.MinimumNArgs(1), 18 | ValidArgs: utils.ListBoilerplatesNoError(), 19 | Run: func(cmd *cobra.Command, args []string) { 20 | for _, boilerplate := range args { 21 | if err := utils.PrintBoilerplate(boilerplate); err != nil { 22 | log.Fatalf("On dumping %v: %v", boilerplate, err) 23 | } 24 | } 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: "CI: Go build and test" 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | pull_request: 10 | branches: ["main"] 11 | 12 | jobs: 13 | build-and-test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version-file: go.mod 22 | 23 | - name: Build 24 | run: go build -v ./... 25 | 26 | - name: Test 27 | run: go test -v ./... 28 | 29 | - name: Vet 30 | run: go vet ./... 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gibo 2 | 3 | dist/ 4 | ### Generated by gibo (https://github.com/simonwhitaker/gibo) 5 | ### https://raw.github.com/github/gitignore/4488915eec0b3a45b5c63ead28f286819c0917de/Go.gitignore 6 | 7 | # If you prefer the allow list template instead of the deny list, see community template: 8 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 9 | # 10 | # Binaries for programs and plugins 11 | *.exe 12 | *.exe~ 13 | *.dll 14 | *.so 15 | *.dylib 16 | 17 | # Test binary, built with `go test -c` 18 | *.test 19 | 20 | # Output of the go coverage tool, specifically when used with LiteIDE 21 | *.out 22 | 23 | # Dependency directories (remove the comment below to include it) 24 | # vendor/ 25 | 26 | # Go workspace file 27 | go.work 28 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/simonwhitaker/gibo/utils" 9 | "github.com/spf13/cobra" 10 | "golang.org/x/term" 11 | ) 12 | 13 | func init() { 14 | giboCmd.AddCommand(listCmd) 15 | } 16 | 17 | var listCmd = &cobra.Command{ 18 | Use: "list", 19 | Short: "List available boilerplates", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | list, err := utils.ListBoilerplates() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | if term.IsTerminal(int(os.Stdout.Fd())) { 26 | w, _, err := term.GetSize(int(os.Stdout.Fd())) 27 | if err == nil { 28 | utils.PrintInColumns(list, w) 29 | os.Exit(0) 30 | } 31 | } 32 | for _, el := range list { 33 | fmt.Println(el) 34 | } 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /.runny.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | delete-windows-release: 3 | run: ./tools/delete-windows-release.sh 4 | release: 5 | argnames: 6 | - tag 7 | run: git tag -am $tag $tag && git push origin refs/tags/$tag 8 | release-next: 9 | run: | 10 | latest_version=$(git tag --list "v*" --sort "-v:refname" | head -1) 11 | next_version=$(echo ${latest_version} | awk -F. -v OFS=. '{$NF += 1 ; print}') 12 | printf "Release $next_version? (Current release is $latest_version) [yN] " 13 | read answer 14 | if [[ $answer =~ ^[Yy]$ ]]; then 15 | git tag -am $next_version $next_version && git push origin refs/tags/$next_version 16 | fi 17 | test: 18 | run: go test ./... 19 | 20 | update-deps: 21 | run: go get -u . && go mod tidy 22 | 23 | check-goreleaser: 24 | run: goreleaser check .goreleaser.yaml .goreleaser.windows.yaml 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # To release a new version automatically 2 | 3 | Just push a version tag (e.g. `v3.0.1`) to Github. 4 | 5 | ## To release a new version manually 6 | 7 | 1. Install [GoReleaser](https://goreleaser.com/): 8 | 9 | ```sh 10 | brew install goreleaser/tap/goreleaser 11 | ``` 12 | 13 | 2. Create a new release tag: 14 | 15 | ```sh 16 | git tag -am 'vX.X.X' vX.X.X 17 | git push --tags 18 | ``` 19 | 20 | NB! Tags of the form `vX.X.X-alpha.X` will fail to upload to Chocolatey with a 504 GatewayTimeout error. The final dot is the problem; use `vX.X.X-alphaX` instead. 21 | 22 | 3. Run GoReleaser: 23 | 24 | GoReleaser requires `GITHUB_TOKEN` to be set. If you have the `gh` tool installed you can probably re-use the token it uses, by calling `gh auth token`: 25 | 26 | ```sh 27 | GITHUB_TOKEN=$(gh auth token) goreleaser release --clean 28 | ``` 29 | -------------------------------------------------------------------------------- /cmd/search.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "log" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/simonwhitaker/gibo/utils" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func init() { 15 | giboCmd.AddCommand(searchCmd) 16 | } 17 | 18 | var searchCmd = &cobra.Command{ 19 | Use: "search", 20 | Short: "Search for boilerplates", 21 | Args: cobra.ExactArgs(1), 22 | Run: func(cmd *cobra.Command, args []string) { 23 | searchTerm := args[0] 24 | err := filepath.WalkDir(utils.RepoDir(), func(path string, d fs.DirEntry, err error) error { 25 | base := filepath.Base(path) 26 | ext := filepath.Ext(base) 27 | if ext == ".gitignore" { 28 | filename := strings.TrimSuffix(base, ext) 29 | if strings.Contains(strings.ToLower(filename), strings.ToLower(searchTerm)) { 30 | fmt.Println(filename) 31 | } 32 | } 33 | return nil 34 | }) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /utils/fmt_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetMaxLengthAndNumColumns(t *testing.T) { 9 | var tests = []struct { 10 | list []string 11 | terminalWidth, 12 | expectedMaxLength, expectedNumColumns int 13 | }{ 14 | // Always get at least one column 15 | {[]string{"foo", "bar"}, 1, 3, 1}, 16 | 17 | // Get multiple columns as soon as there is space 18 | {[]string{"foo", "bar"}, 6, 3, 1}, 19 | {[]string{"foo", "bar"}, 7, 3, 2}, 20 | 21 | // Rudimentary max width test 22 | {[]string{"a", "bbb", "cc"}, 1, 3, 1}, 23 | } 24 | for _, tt := range tests { 25 | testName := fmt.Sprintf("%v,%v", tt.list, tt.terminalWidth) 26 | t.Run(testName, func(t *testing.T) { 27 | maxLength, numColumns := getMaxLengthAndNumColumns(tt.list, tt.terminalWidth) 28 | if maxLength != tt.expectedMaxLength { 29 | t.Errorf("Expected maxLength %v, got %v", tt.expectedMaxLength, maxLength) 30 | } 31 | 32 | if numColumns != tt.expectedNumColumns { 33 | t.Errorf("Expected numColumns %v, got %v", tt.expectedNumColumns, numColumns) 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /utils/fmt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "fmt" 4 | 5 | // calculate returns 6 | func getMaxLengthAndNumColumns(list []string, terminalWidth int) (int, int) { 7 | maxLength := 0 8 | for _, s := range list { 9 | if len(s) > maxLength { 10 | maxLength = len(s) 11 | } 12 | } 13 | 14 | // For n columns, we need enough room for n-1 spaces between the columns. 15 | // 16 | // For a list where the longest string is of length maxLength, the maximum 17 | // number of columns we can print is the largest number, n, where: 18 | // 19 | // n * maxLength + n - 1 <= terminalWidth 20 | // 21 | // Rearranging: 22 | // 23 | // n * maxLength + n <= terminalWidth + 1 24 | // n * (maxLength - 1) <= terminalWidth + 1 25 | // n <= (terminalWidth + 1) / (maxLength + 1) 26 | numColumns := (terminalWidth + 1) / (maxLength + 1) 27 | if numColumns == 0 { 28 | numColumns = 1 29 | } 30 | return maxLength, numColumns 31 | } 32 | 33 | func PrintInColumns(list []string, terminalWidth int) { 34 | maxLength, numColumns := getMaxLengthAndNumColumns(list, terminalWidth) 35 | fmtString := fmt.Sprintf("%%-%ds", maxLength) 36 | for i, s := range list { 37 | fmt.Printf(fmtString, s) 38 | if (i%numColumns == numColumns-1) || i == len(list)-1 { 39 | fmt.Print("\n") 40 | } else { 41 | fmt.Print(" ") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/simonwhitaker/gibo 2 | 3 | go 1.24.4 4 | 5 | require ( 6 | github.com/go-git/go-git/v5 v5.16.4 7 | github.com/spf13/cobra v1.10.2 8 | golang.org/x/term v0.38.0 9 | ) 10 | 11 | require ( 12 | dario.cat/mergo v1.0.2 // indirect 13 | github.com/Microsoft/go-winio v0.6.2 // indirect 14 | github.com/ProtonMail/go-crypto v1.3.0 // indirect 15 | github.com/cloudflare/circl v1.6.1 // indirect 16 | github.com/cyphar/filepath-securejoin v0.6.1 // indirect 17 | github.com/emirpasic/gods v1.18.1 // indirect 18 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 19 | github.com/go-git/go-billy/v5 v5.7.0 // indirect 20 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 21 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 22 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 23 | github.com/kevinburke/ssh_config v1.4.0 // indirect 24 | github.com/klauspost/cpuid/v2 v2.3.0 // indirect 25 | github.com/pjbgf/sha1cd v0.5.0 // indirect 26 | github.com/sergi/go-diff v1.4.0 // indirect 27 | github.com/skeema/knownhosts v1.3.2 // indirect 28 | github.com/spf13/pflag v1.0.10 // indirect 29 | github.com/xanzy/ssh-agent v0.3.3 // indirect 30 | golang.org/x/crypto v0.46.0 // indirect 31 | golang.org/x/net v0.48.0 // indirect 32 | golang.org/x/sys v0.39.0 // indirect 33 | gopkg.in/warnings.v0 v0.1.2 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 2 | 3 | # This is an example .goreleaser.yml file with some sensible defaults. 4 | # Make sure to check the documentation at https://goreleaser.com 5 | version: 2 6 | before: 7 | hooks: 8 | # You may remove this if you don't use go modules. 9 | - go mod tidy 10 | # you may remove this if you don't need go generate 11 | # - go generate ./... 12 | builds: 13 | - env: 14 | - CGO_ENABLED=0 15 | goos: 16 | - linux 17 | - darwin 18 | ldflags: 19 | # Default: '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser' 20 | "-s -w -X github.com/simonwhitaker/gibo/cmd.version={{.Version}}" 21 | 22 | archives: 23 | - formats: 24 | - tar.gz 25 | # this name template makes the OS and Arch compatible with the results of uname. 26 | name_template: >- 27 | {{ .ProjectName }}_ 28 | {{- title .Os }}_ 29 | {{- if eq .Arch "amd64" }}x86_64 30 | {{- else if eq .Arch "386" }}i386 31 | {{- else }}{{ .Arch }}{{ end }} 32 | {{- if .Arm }}v{{ .Arm }}{{ end }} 33 | # use zip for windows archives 34 | format_overrides: 35 | - goos: windows 36 | formats: 37 | - zip 38 | 39 | homebrew_casks: 40 | - name: gibo 41 | directory: Casks 42 | homepage: https://github.com/simonwhitaker/gibo 43 | repository: 44 | owner: simonwhitaker 45 | name: homebrew-tap 46 | commit_author: 47 | name: Simon Whitaker 48 | email: sw@netcetera.org 49 | hooks: 50 | post: 51 | install: | 52 | if OS.mac? 53 | system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/gibo"] 54 | end 55 | 56 | checksum: 57 | name_template: "checksums.txt" 58 | snapshot: 59 | version_template: "{{ incpatch .Version }}-next" 60 | changelog: 61 | sort: asc 62 | filters: 63 | exclude: 64 | - "^docs:" 65 | - "^test:" 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gibo: fast access to .gitignore boilerplates 2 | 3 | **gibo** (short for .gitignore boilerplates) is a command-line tool to help you easily access .gitignore boilerplates from [github.com/github/gitignore](https://github.com/github/gitignore). 4 | 5 | ## Typical usage 6 | 7 | ```command 8 | gibo dump Swift Xcode >> .gitignore 9 | ``` 10 | 11 | For additional usage instructions, run `gibo help`. 12 | 13 | ## Installation 14 | 15 | ### Using Homebrew 16 | 17 | ```command 18 | brew install gibo 19 | ``` 20 | 21 | Or use the tap, which may be slightly more up-to-date: 22 | 23 | ```command 24 | brew install simonwhitaker/tap/gibo 25 | ``` 26 | 27 | ### Using Scoop 28 | 29 | On Windows, you can install gibo using [Scoop](https://scoop.sh/#/apps?q=gibo): 30 | 31 | ```command 32 | scoop bucket add main 33 | scoop install main/gibo 34 | ``` 35 | 36 | ### Using Chocolatey 37 | 38 | ```command 39 | choco install gibo 40 | ``` 41 | 42 | ### Using the Go toolchain 43 | 44 | ```command 45 | go install github.com/simonwhitaker/gibo@latest 46 | ``` 47 | 48 | ### Building from source 49 | 50 | Clone the repo, then: 51 | 52 | ```command 53 | cd gibo 54 | go install . 55 | ``` 56 | 57 | ### Downloading a binary 58 | 59 | Download the latest [release](https://github.com/simonwhitaker/gibo/releases) for your platform, then put `gibo` (or `gibo.exe`) somewhere on your path. 60 | 61 | ### Installation on Docker 62 | 63 | ```command 64 | docker run --rm simonwhitaker/gibo 65 | ``` 66 | 67 | ## Tab completion in bash, zsh, fish and Powershell 68 | 69 | See the instructions at: 70 | 71 | ```command 72 | gibo completion 73 | ``` 74 | 75 | ## Use gibo to generate .hgignore files 76 | 77 | The `glob` .hgignore syntax for Mercurial is compatible with .gitignore syntax. This means that you can use gibo to generate .hgignore files, as long as the .hgignore files use the `glob` syntax: 78 | 79 | ```command 80 | echo 'syntax: glob' > .hgignore 81 | gibo dump Python >> .hgignore 82 | ``` 83 | 84 | ## Credits 85 | 86 | gibo was written by Simon Whitaker ([@s1mn](http://twitter.com/s1mn)) 87 | -------------------------------------------------------------------------------- /.goreleaser.windows.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 2 | # This is an example .goreleaser.yml file with some sensible defaults. 3 | # Make sure to check the documentation at https://goreleaser.com 4 | version: 2 5 | before: 6 | hooks: 7 | # You may remove this if you don't use go modules. 8 | - go mod tidy 9 | # you may remove this if you don't need go generate 10 | # - go generate ./... 11 | builds: 12 | - env: 13 | - CGO_ENABLED=0 14 | goos: 15 | - windows 16 | ldflags: 17 | # Default: '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser' 18 | "-s -w -X github.com/simonwhitaker/gibo/cmd.version={{.Version}}" 19 | 20 | archives: 21 | - formats: 22 | - zip 23 | # this name template makes the OS and Arch compatible with the results of uname. 24 | name_template: >- 25 | {{ .ProjectName }}_ 26 | {{- title .Os }}_ 27 | {{- if eq .Arch "amd64" }}x86_64 28 | {{- else if eq .Arch "386" }}i386 29 | {{- else }}{{ .Arch }}{{ end }} 30 | {{- if .Arm }}v{{ .Arm }}{{ end }} 31 | 32 | chocolateys: 33 | - name: gibo 34 | title: gibo 35 | summary: "Fast access to .gitignore boilerplates" 36 | project_source_url: https://github.com/simonwhitaker/gibo 37 | package_source_url: https://github.com/simonwhitaker/gibo 38 | project_url: https://github.com/simonwhitaker/gibo 39 | release_notes: "https://github.com/simonwhitaker/gibo/releases/tag/v{{ .Version }}" 40 | api_key: "{{ .Env.CHOCOLATEY_API_KEY }}" 41 | docs_url: https://github.com/simonwhitaker/gibo/blob/main/README.md 42 | license_url: https://github.com/simonwhitaker/gibo/blob/main/LICENSE 43 | require_license_acceptance: false 44 | url_template: "https://github.com/simonwhitaker/gibo/releases/download/{{ .Tag }}/{{ .ArtifactName }}" 45 | authors: Simon Whitaker 46 | description: |- 47 | **gibo** (short for .gitignore boilerplates) is a command-line tool to help you easily access .gitignore boilerplates from [github.com/github/gitignore](https://github.com/github/gitignore). 48 | tags: git gitignore 49 | 50 | checksum: 51 | name_template: "checksums.windows.txt" 52 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Deployment 2 | 3 | on: 4 | push: 5 | # run only against version tags 6 | tags: 7 | - "v*" 8 | 9 | permissions: 10 | contents: write 11 | # packages: write 12 | # issues: write 13 | 14 | jobs: 15 | goreleaser: 16 | # Generates the Github release, and updates the simonwhitaker/tap/gibo 17 | # Homebrew formula 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - run: git fetch --force --tags 24 | - uses: actions/setup-go@v5 25 | with: 26 | go-version-file: go.mod 27 | # More assembly might be required: Docker logins, GPG, etc. It all depends 28 | # on your needs. 29 | - uses: goreleaser/goreleaser-action@v6 30 | with: 31 | # either 'goreleaser' (default) or 'goreleaser-pro': 32 | distribution: goreleaser 33 | version: latest 34 | args: release --clean 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} 37 | goreleaser-windows: 38 | # Generates the Windows artefacts for the release, updates the gibo 39 | # chocolatey package 40 | runs-on: windows-latest 41 | steps: 42 | - uses: actions/checkout@v4 43 | with: 44 | fetch-depth: 0 45 | - run: git fetch --force --tags 46 | - uses: actions/setup-go@v5 47 | with: 48 | go-version: stable 49 | # More assembly might be required: Docker logins, GPG, etc. It all depends 50 | # on your needs. 51 | - uses: goreleaser/goreleaser-action@v6 52 | with: 53 | # either 'goreleaser' (default) or 'goreleaser-pro': 54 | distribution: goreleaser 55 | version: latest 56 | args: release -f .goreleaser.windows.yaml --clean 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.PUBLISHER_TOKEN }} 59 | CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} 60 | homebrew_core: 61 | runs-on: ubuntu-latest 62 | steps: 63 | - name: Bump Homebrew formula 64 | uses: dawidd6/action-homebrew-bump-formula@v3 65 | with: 66 | token: ${{ secrets.HOMEBREW_CORE_GITHUB_API_TOKEN }} 67 | formula: gibo 68 | docker_hub: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Check out the repo 72 | uses: actions/checkout@v4 73 | 74 | - name: Set up QEMU 75 | uses: docker/setup-qemu-action@v3 76 | 77 | - name: Set up Docker Buildx 78 | uses: docker/setup-buildx-action@v3 79 | 80 | - name: Log in to Docker Hub 81 | uses: docker/login-action@v3 82 | with: 83 | username: ${{ secrets.DOCKERHUB_USERNAME }} 84 | password: ${{ secrets.DOCKERHUB_TOKEN }} 85 | 86 | - name: Extract metadata (tags, labels) for Docker 87 | id: meta 88 | uses: docker/metadata-action@v5 89 | with: 90 | images: simonwhitaker/gibo 91 | 92 | - name: Build and push Docker image 93 | uses: docker/build-push-action@v5 94 | with: 95 | context: . 96 | platforms: linux/amd64,linux/arm64 97 | file: ./Dockerfile 98 | push: true 99 | tags: ${{ steps.meta.outputs.tags }} 100 | labels: ${{ steps.meta.outputs.labels }} 101 | 102 | - name: Update Docker Hub description 103 | uses: peter-evans/dockerhub-description@v4 104 | with: 105 | username: ${{ secrets.DOCKERHUB_USERNAME }} 106 | password: ${{ secrets.DOCKERHUB_TOKEN }} 107 | repository: simonwhitaker/gibo 108 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/fs" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "sort" 11 | "strings" 12 | 13 | "github.com/go-git/go-git/v5" 14 | "github.com/go-git/go-git/v5/plumbing" 15 | ) 16 | 17 | func RepoDir() string { 18 | cacheDir, err := os.UserCacheDir() 19 | if err != nil { 20 | log.Fatalln("gibo can't determine your user cache directory. Please file an issue at https://github.com/simonwhitaker/gibo/issues") 21 | } 22 | return filepath.Join(cacheDir, "gibo", "gitignore-boilerplates") 23 | } 24 | 25 | func cloneRepo(repo string) error { 26 | err := os.MkdirAll(repo, 0755) 27 | if err != nil && !os.IsExist(err) { 28 | return err 29 | } 30 | _, err = git.PlainClone(repo, false, &git.CloneOptions{ 31 | URL: "https://github.com/github/gitignore.git", 32 | Depth: 1, 33 | }) 34 | if err != nil && err != git.ErrRepositoryAlreadyExists { 35 | return err 36 | } 37 | return nil 38 | } 39 | 40 | func cloneIfNeeded() error { 41 | fileInfo, err := os.Stat(RepoDir()) 42 | if os.IsNotExist(err) { 43 | err = cloneRepo(RepoDir()) 44 | if err != nil { 45 | return err 46 | } 47 | } else if !fileInfo.IsDir() { 48 | return fmt.Errorf("%v exists but is not a directory", RepoDir()) 49 | } 50 | return nil 51 | } 52 | 53 | // pathForBoilerplate returns the filepath for a given boilerplate name. The 54 | // search is case-insensitive; passing `python` to `name` will find the path to 55 | // `Python.gitignore`, for example. 56 | func pathForBoilerplate(name string) (string, error) { 57 | filename := name + ".gitignore" 58 | var result string = "" 59 | filepath.WalkDir(RepoDir(), func(path string, d fs.DirEntry, err error) error { 60 | if strings.EqualFold(filepath.Base(path), filename) { 61 | result = path 62 | // Exit WalkDir early, we've found our match 63 | return filepath.SkipAll 64 | } 65 | return nil 66 | }) 67 | if len(result) > 0 { 68 | return result, nil 69 | } 70 | return "", fmt.Errorf("%v: boilerplate not found", name) 71 | } 72 | 73 | func PrintBoilerplate(name string) error { 74 | if err := cloneIfNeeded(); err != nil { 75 | return err 76 | } 77 | path, err := pathForBoilerplate(name) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | // Get the revision hash for the current head 83 | r, err := git.PlainOpen(RepoDir()) 84 | if err != nil { 85 | return err 86 | } 87 | revision, err := r.Head() 88 | if err != nil { 89 | return err 90 | } 91 | remoteWebRoot := "https://raw.github.com/github/gitignore/" + revision.Hash().String() 92 | relativePath := strings.TrimPrefix(path, RepoDir()) 93 | // On Windows, relativePath will use backslashes, but we need to append it to 94 | // a URL, which uses forward slashes. 95 | if os.PathSeparator == '\\' { 96 | relativePath = strings.ReplaceAll(relativePath, "\\", "/") 97 | } 98 | 99 | fmt.Println("### Generated by gibo (https://github.com/simonwhitaker/gibo)") 100 | fmt.Printf("### %v%v\n\n", remoteWebRoot, relativePath) 101 | 102 | f, err := os.Open(path) 103 | if err != nil { 104 | return err 105 | } 106 | io.Copy(os.Stdout, f) 107 | return nil 108 | } 109 | 110 | func ListBoilerplates() ([]string, error) { 111 | var result []string 112 | if err := cloneIfNeeded(); err != nil { 113 | return nil, err 114 | } 115 | err := filepath.WalkDir(RepoDir(), func(path string, d fs.DirEntry, err error) error { 116 | base := filepath.Base(path) 117 | ext := filepath.Ext(base) 118 | if ext == ".gitignore" { 119 | name := strings.TrimSuffix(base, ext) 120 | result = append(result, name) 121 | } 122 | return nil 123 | }) 124 | if err != nil { 125 | return nil, err 126 | } 127 | sort.Strings(result) 128 | return result, nil 129 | } 130 | 131 | func ListBoilerplatesNoError() []string { 132 | result, _ := ListBoilerplates() 133 | return result 134 | } 135 | 136 | func Update() (string, error) { 137 | cloneIfNeeded() 138 | r, err := git.PlainOpen(RepoDir()) 139 | if err != nil { 140 | return "", err 141 | } 142 | w, err := r.Worktree() 143 | if err != nil { 144 | return "", err 145 | } 146 | err = w.Pull(&git.PullOptions{}) 147 | if err != nil && err != git.NoErrAlreadyUpToDate { 148 | reference, err := r.Reference(plumbing.NewRemoteReferenceName("origin", "main"), false) 149 | if err != nil { 150 | return "", err 151 | } 152 | err = w.Reset(&git.ResetOptions{ 153 | Commit: reference.Hash(), 154 | Mode: git.HardReset, 155 | }) 156 | if err != nil { 157 | return "", err 158 | } 159 | } else if err != nil { 160 | return "Already up to date", nil 161 | } 162 | return "Updated", nil 163 | } 164 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | sw@netcetera.org. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= 2 | dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= 3 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 4 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 5 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 6 | github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= 7 | github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 8 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 9 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 10 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 11 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 12 | github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= 13 | github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 15 | github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= 16 | github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= 21 | github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= 22 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 23 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 24 | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 25 | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 26 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 27 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 28 | github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= 29 | github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= 30 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= 31 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 32 | github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= 33 | github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= 34 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 35 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 36 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 37 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 38 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 39 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 40 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 41 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 42 | github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= 43 | github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= 44 | github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= 45 | github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 46 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 47 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 48 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 49 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 50 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 51 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 52 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 53 | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= 54 | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 55 | github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= 56 | github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= 57 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 58 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 59 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 62 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 63 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 64 | github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= 65 | github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 66 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 67 | github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= 68 | github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= 69 | github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= 70 | github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= 71 | github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 72 | github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= 73 | github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 74 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 75 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 76 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 77 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 78 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 79 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 80 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 81 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 82 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 83 | golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= 84 | golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 85 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 86 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 87 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 88 | golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= 89 | golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 90 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 91 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 92 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 93 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 | golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= 97 | golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 98 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 99 | golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= 100 | golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= 101 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 102 | golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= 103 | golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 104 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 105 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 106 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 107 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 108 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 109 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 110 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 111 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 112 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 113 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 114 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 115 | --------------------------------------------------------------------------------