[![All Contributors](https://img.shields.io/badge/all_contributors-32-orange.svg?style=flat-square)](#contributors-) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![reviewdog](https://github.com/nao1215/gup/actions/workflows/reviewdog.yml/badge.svg)](https://github.com/nao1215/gup/actions/workflows/reviewdog.yml) ![Coverage](https://raw.githubusercontent.com/nao1215/octocovs-central-repo/main/badges/nao1215/gup/coverage.svg) [![Go Reference](https://pkg.go.dev/badge/github.com/nao1215/gup.svg)](https://pkg.go.dev/github.com/nao1215/gup) [![Go Report Card](https://goreportcard.com/badge/github.com/nao1215/gup)](https://goreportcard.com/report/github.com/nao1215/gup) ![GitHub](https://img.shields.io/github/license/nao1215/gup) [日本語](./doc/ja/README.md) | [Русский](./doc/ru/README.md) | [中文](./doc/zh-cn/README.md) | [한국어](./doc/ko/README.md) | [Español](./doc/es/README.md) | [Français](./doc/fr/README.md) ![sample](./doc/img/sample.gif) gup updates and manages the global Go command-line tools in your `$GOBIN`. `go install` places each program in `$GOBIN` (`$GOPATH/bin`) but never updates it again, keeps no manifest of what it installed, and offers no way to hold a tool at a version you depend on. gup manages that tool set: it brings the whole set up to date in parallel, can `pin` selected tools to exact versions, and adds the management commands `go install` lacks: `list`/`check` what is installed, `remove` binaries, `export`/`import` the set to reproduce it on another machine, and `migrate` it to a new `$GOBIN`. Runs on Windows, macOS, and Linux. ## Supported OS (unit testing with GitHub Actions) - Linux - Mac - Windows ## How to install gup is already available via `winget`, `mise`, `nix`, and `aqua` in addition to `go install` and Homebrew. ### Use "go install" If you do not have the Go development environment installed on your system, please install it from the [official website](https://go.dev/doc/install). ``` go install github.com/nao1215/gup@latest ``` Building from source needs Go 1.25 or newer. On an older Go, install a prebuilt release binary or a package (see below) instead. ### Use homebrew ```shell brew install nao1215/tap/gup ``` ### Use winget (Windows) ```shell winget install --id nao1215.gup ``` ### Use mise-en-place ```shell mise use -g gup@latest ``` ### Use nix (Nix profile) ```shell nix profile install nixpkgs#gogup ``` ### Use aqua gup is registered in the [aqua](https://aquaproj.github.io/) standard registry. Add it to your `aqua.yaml`: ```shell aqua g -i nao1215/gup ``` ### Install from Package or Binary [The release page](https://github.com/nao1215/gup/releases) contains packages in .deb, .rpm, and .apk formats. gup command uses the go command internally, so the golang installation is required. ## Verifying release integrity Every release ships supply-chain metadata so you can verify what you download: - Signed checksums: `checksums.txt` is signed with [cosign](https://github.com/sigstore/cosign) (keyless), producing `checksums.txt.sigstore.json`. - SBOM: an SPDX Software Bill of Materials is attached to each release archive. - Build provenance: SLSA build provenance is attested via GitHub OIDC. Verify the signed checksums (then check your archive against `checksums.txt`): ```shell cosign verify-blob \ --bundle checksums.txt.sigstore.json \ --certificate-identity-regexp 'https://github.com/nao1215/gup/\.github/workflows/release\.yml@refs/tags/.*' \ --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ checksums.txt sha256sum --check --ignore-missing checksums.txt ``` Verify the build provenance of a downloaded artifact with the GitHub CLI: ```shell gh attestation verify gup___.tar.gz --repo nao1215/gup ``` ## How to use ### Check the gup version Print the version with either the `version` subcommand or the top-level `--version`/`-V` flag. ```shell $ gup --version $ gup version ``` ### Update all binaries `gup update` updates every binary under `$GOBIN`, in parallel. ![update](./doc/img/update.gif) ### Update the specified binary If you want to update only the specified binaries, you specify multiple command names separated by space. ```shell $ gup update subaru gup ubume update binary under $GOPATH/bin or $GOBIN [1/3] github.com/nao1215/gup (v0.7.0 to v0.7.1, go1.20.1 to go1.22.4) [2/3] github.com/nao1215/subaru (Already up-to-date: v1.0.2 / go1.22.4) [3/3] github.com/nao1215/ubume/cmd/ubume (Already up-to-date: v1.4.1 / go1.22.4) ``` ### Exclude binaries during gup update If you don't want to update some binaries simply specify binaries which should not be updated separated using ',' without spaces as a delimiter. Also works in combination with --dry-run ```shell $ gup update --exclude=gopls,golangci-lint //--exclude or -e, this example will exclude 'gopls' and 'golangci-lint' ``` ### Update binaries with @main, @master, or @latest If you want to control update source per binary, use the following options: - `--main` (`-m`): update by `@main` (falls back to `@master` only when the repository has no `main` branch) - `--master`: update by `@master` - `--latest`: update by `@latest` The `@main` → `@master` fallback applies only to a missing `main` branch. Build, network, authentication, timeout, and cancellation failures are reported as-is and never silently install `@master`. The selected channel is saved to `gup.json` and reused by future `gup update` runs. ```shell $ gup update --main=gup,lazygit --master=sqly --latest=air ``` ### Pin a tool to a specific version Use `pin` when a global tool must stay on a specific version, for example when it needs to match CI or a team-wide development environment. ```shell $ gup pin golangci-lint v1.62.0 $ gup update ``` A pinned tool is installed with the recorded version (`go install @`), never `@latest`. `gup update` keeps it at that version and reinstalls it there if the installed version differs; the rest of the tool set still updates as usual. The pin locks the module version, not the Go build, so a pinned tool is still rebuilt at the pinned version when the Go toolchain changes (use `--ignore-go-update` to suppress that, exactly as for unpinned tools). The pin is stored in `gup.json` with `channel: "pinned"`: ```json { "schema_version": 2, "packages": [ { "name": "golangci-lint", "import_path": "github.com/golangci/golangci-lint/cmd/golangci-lint", "version": "v1.62.0", "channel": "pinned" } ] } ``` `gup pin` also accepts the `tool@version` form (`gup pin golangci-lint@v1.62.0`). The tool must already be installed under `$GOBIN`. To allow the tool to update again: ```shell $ gup unpin golangci-lint ``` `gup check` reports a pinned tool as `pinned` when it is at the pinned version and built with the current Go toolchain, or `pin-mismatch` (with a `gup update ` suggestion) when the installed version differs or a Go-toolchain rebuild is pending; it never compares a pinned tool against `@latest`. ### List up command name with package path and version under $GOPATH/bin list subcommand print command information under $GOPATH/bin or $GOBIN. The output information is the command name, package path, and command version. ![list](./doc/img/list.gif) ### Remove the specified binary If you want to remove a command under $GOPATH/bin or $GOBIN, use the remove subcommand. The remove subcommand asks if you want to remove it before removing it. ```shell $ gup remove subaru gal ubume gup:CHECK: remove /home/nao/.go/bin/subaru? [Y/n] Y removed /home/nao/.go/bin/subaru gup:CHECK: remove /home/nao/.go/bin/gal? [Y/n] n cancel removal /home/nao/.go/bin/gal gup:CHECK: remove /home/nao/.go/bin/ubume? [Y/n] Y removed /home/nao/.go/bin/ubume ``` If you want to force the removal, use the --force option. ```shell $ gup remove --force gal removed /home/nao/.go/bin/gal ``` In non-interactive execution (when stdin is not a TTY, e.g. CI or a pipe), `gup remove` no longer blocks waiting for confirmation. It fails fast with a clear message; pass `--force` to remove without confirmation. ### Check if the binary is the latest version If you want to know if the binary is the latest version, use the check subcommand. check subcommand checks if the binary is the latest version and displays the name of the binary that needs to be updated. ```shell $ gup check check binary under $GOPATH/bin or $GOBIN [ 1/33] github.com/cheat/cheat (Already up-to-date: v0.0.0-20211009161301-12ffa4cb5c87 / go1.22.4) [ 2/33] fyne.io/fyne/v2 (current: v2.1.3, latest: v2.1.4 / current: go1.20.2, installed: go1.22.4) : [33/33] github.com/nao1215/ubume (Already up-to-date: v1.5.0 / go1.22.4) If you want to update binaries, the following command. $ gup update fyne_demo gup mimixbox ``` Like other subcommands, you can only check the specified binaries. ```shell $ gup check lazygit mimixbox check binary under $GOPATH/bin or $GOBIN [1/2] github.com/jesseduffield/lazygit (Already up-to-date: v0.32.2 / go1.22.4) [2/2] github.com/nao1215/mimixbox (current: v0.32.1, latest: v0.33.2 / go1.22.4) If you want to update binaries, the following command. $ gup update mimixbox ``` ### Quiet output for large tool sets `check` and `update` print every binary by default, which is noisy when you have many tools installed. Pass `--quiet` (`-q`) to suppress the up-to-date lines and show only the binaries that were updated (or have an update available) plus failures, followed by a one-line summary. Errors are always written to STDERR, so they stay visible. When `--json` is also given, `--quiet` is ignored and the full JSON array is printed. ```shell $ gup update --quiet github.com/nao1215/gup (v0.7.0 to v0.7.1) gup: 1 updated, 8 up-to-date, 0 failed $ gup check -q github.com/nao1215/gup (current: v0.7.0, latest: v0.7.1 / go1.22.4) If you want to update binaries, run the following command. $ gup update gup gup: 1 update available, 8 up-to-date, 0 failed ``` ### Machine-readable JSON output (for scripting / CI) `list`, `check`, and `update` accept `--json`, printing a JSON array instead of the human-readable output (which stays the default). ```shell $ gup check --json [ { "name": "gup", "import_path": "github.com/nao1215/gup", "module_path": "github.com/nao1215/gup", "channel": "latest", "current_version": "v1.0.0", "latest_version": "v1.1.0", "current_go_version": "go1.22.4", "installed_go_version": "go1.22.4", "status": "update-available" } ] ``` Each element has these fields: `name`, `import_path`, `module_path`, `channel` (`latest`/`main`/`master`/`pinned`), `current_version`, `latest_version` (empty for `list` and for pinned packages), `pinned_version` (present only for `channel: "pinned"`), `current_go_version`, `installed_go_version`, `status`, `error` (omitted when absent), and `hint` (a next-step suggestion, present only when one applies to the error). `status` is `installed` (list), `up-to-date`, `update-available` (check), `updated` (update), `pinned`/`pin-mismatch` (a pinned package at / away from its pinned version), or `error`. The array is always valid JSON, including partial failures (those packages get `"status": "error"`; error detail also goes to STDERR so STDOUT stays pure JSON). Exit codes are unchanged—`check` reporting `update-available` still exits `0`. ### Failure diagnostics / next-step hints When `update` or `check` fails, gup turns the Go toolchain's cryptic output into a short, actionable next step printed on STDERR right after the error (and exposed as the `hint` field with `--json`): ```shell $ gup update gup:ERROR: [1/1] tool: can't install gup.test/moved/cmd/tool: go: gup.test/moved/cmd/tool@latest: module gup.test/moved@latest found (v1.1.0), but does not contain package gup.test/moved/cmd/tool gup:HINT : The module no longer provides this command at its import path. The project likely moved to a new major version (e.g. a `/v2` module path) or relocated the command; check its current install instructions and reinstall with the new path. ``` Hints cover module renames/major-version moves, relocated commands, `go.mod` `replace` directives, binaries not installed via `go install`, missing branch/tag, unresolvable/private/deleted repositories, permission and network errors, and an out-of-date Go toolchain. gup stays silent when it has nothing reliable to add (e.g. a timeout, whose message already names the remedy). ### Behavior on an empty environment An empty global environment (no binaries installed by `go install` yet) is treated as a normal first-run condition, not an error: - `list`, `check`, and `update` exit `0`, printing a short informational note (or a valid empty `[]` with `--json`). - `export` exits `0` and writes an empty `gup.json`. Naming a binary that is not installed, or excluding every binary, is still a usage error and exits `1`. A config problem is also still reported even on an empty environment: if the `gup.json` that would be read (an explicit `--file`, or an auto-detected one) is malformed, has an unsupported schema/channel/pin, or is ambiguous (both the user-level config and `./gup.json` exist with no `--file`), `check`, `update`, and `list --json` fail fast and exit `1` instead of silently ignoring it. ### Export/Import subcommand Use export/import when you want to install the same Go binaries across multiple systems. `gup.json` stores each tool's import path, the recorded binary `version`, and its update `channel` (`latest` / `main` / `master` / `pinned`). For `channel: "pinned"`, `version` is the exact target version the tool is held at; for the other channels it is the version that was recorded at export time. `import` installs the exact version written in the file, and a pinned package stays pinned after import. ```json { "schema_version": 1, "packages": [ { "name": "gal", "import_path": "github.com/nao1215/gal/cmd/gal", "version": "v1.1.1", "channel": "latest" }, { "name": "posixer", "import_path": "github.com/nao1215/posixer", "version": "v0.1.0", "channel": "main" } ] } ``` By default: - `gup export` writes to `$XDG_CONFIG_HOME/gup/gup.json` - `gup import`, `gup check`, and `gup update` auto-detect the config path in this order: 1) `$XDG_CONFIG_HOME/gup/gup.json` (if exists) 2) `./gup.json` (if exists) If both the user-level `gup.json` and `./gup.json` exist, `import`, `check`, `update`, and `list --json` fail fast and ask you to disambiguate with `--file`, instead of silently picking one. You can always override the path with `--file` (`-f`); `list` accepts `--file` together with `--json` to choose the config that supplies the reported `channel`. `schema_version` is `1` for configs with no pinned packages and `2` once any package is pinned, so an environment that uses no pins keeps producing the `1` format that older gup releases can read. gup reads both `1` and `2`. The `pinned` channel is only valid under `schema_version: 2`; a `pinned` entry under `schema_version: 1`, a pinned package without a concrete version, an unknown channel value, or an unsupported `schema_version` is rejected. A malformed or invalid `gup.json` (invalid JSON, an unknown channel, an unsupported `schema_version`, or an unsafe pin) is treated as an error rather than silently ignored: `check`, `update`, and `export` fail fast and name the offending file, so saved per-package channels are never quietly downgraded to `latest` because the config could not be parsed. An unknown channel is never normalized to `latest`. When exporting to a file, `gup export` reads saved update channels from the same `gup.json` it writes to: a default export (no `--file`) reads from and writes to the canonical user-level `gup.json`, while `gup export --file ` reads from and writes to ``. Exporting back to the same alternate config file therefore preserves its saved channels (round-trip safe) instead of resetting them to `latest` from another source. A first export to a brand-new file has no saved channels to read, so its packages are recorded as `latest`. With `--output`, `--file` still selects the channel source, but the exported config is printed to STDOUT instead of being written back to that path. ```shell ※ Environments A (e.g. ubuntu) $ gup export Export /home/nao/.config/gup/gup.json ※ Environments B (e.g. debian) $ gup import ``` `export` can print config content to STDOUT by `--output`. `import` can read a specific file by `--file`. ```shell ※ Environments A (e.g. ubuntu) $ gup export --output > gup.json ※ Environments B (e.g. debian) $ gup import --file=gup.json ``` ### Migrate binaries to a new $GOBIN ```shell gup migrate BEFORE_PATH AFTER_PATH [BINARY...] ``` `gup migrate` reinstalls the Go binaries under `BEFORE_PATH` into `AFTER_PATH`, using the exact `import path@version` recorded in each binary's build info (it never silently upgrades to `@latest`). Internally it just sets `GOBIN` to `AFTER_PATH` and runs the normal `go install` path, so the binaries are rebuilt with the Go toolchain currently in use. #### Why this is useful (e.g. with `mise`) When you manage Go with [`mise`](https://mise.jdx.dev/), updating Go can change the real path of `$GOBIN` per Go version. As a result, tools you installed under the previous `$GOBIN` are no longer visible to the new Go. `gup migrate` lets you reinstall the same Go tool set from the old `$GOBIN` into the new one: ```shell # Reinstall every go-install tool from the old GOBIN into the new GOBIN $ gup migrate ~/.local/share/mise/installs/go/1.24.0/bin ~/.local/share/mise/installs/go/1.25.0/bin # Migrate only specific binaries $ gup migrate /old/gobin /new/gobin gopls air ``` `migrate` is add-only: - It never deletes or cleans up files in `AFTER_PATH`. - Binaries that already exist in `AFTER_PATH` are skipped by default. Use `--force` to reinstall over them. - `AFTER_PATH` is created automatically when it does not exist. - `BEFORE_PATH` and `AFTER_PATH` must be different directories. Binaries whose import path or version cannot be resolved, and development builds (`devel` / `(devel)`), are skipped instead of being upgraded, so local or non-reproducible builds are never broken. Supported flags: `--dry-run` (`-n`), `--notify` (`-N`), `--jobs` (`-j`), `--force`. ### Generate man-pages (for linux, mac) man subcommand generates man-pages under /usr/share/man/man1 by default. If `MANPATH` is set, gup writes to the `man1` directory under each entry instead, creating it when it does not exist yet. An unwritable target exits with a clear error. ```shell $ sudo gup man Generate /usr/share/man/man1/gup-bug-report.1.gz Generate /usr/share/man/man1/gup-check.1.gz Generate /usr/share/man/man1/gup-completion.1.gz Generate /usr/share/man/man1/gup-export.1.gz Generate /usr/share/man/man1/gup-import.1.gz Generate /usr/share/man/man1/gup-list.1.gz Generate /usr/share/man/man1/gup-man.1.gz Generate /usr/share/man/man1/gup-migrate.1.gz Generate /usr/share/man/man1/gup-remove.1.gz Generate /usr/share/man/man1/gup-update.1.gz Generate /usr/share/man/man1/gup-version.1.gz Generate /usr/share/man/man1/gup.1.gz ``` ### Generate shell completion file (for bash, zsh, fish, PowerShell) `completion` prints completion scripts to STDOUT when you pass a shell name. To install completion files into your user environment for bash/fish/zsh, use `--install`. For PowerShell, redirect the output to a `.ps1` file and source it from your profile. ```shell $ gup completion bash > gup.bash $ gup completion zsh > _gup $ gup completion fish > gup.fish $ gup completion powershell > gup.ps1 # Install files automatically to default user paths $ gup completion --install ``` `--install` writes to the paths that match your shell/config layout: bash honors `XDG_DATA_HOME` (falling back to `$HOME/.local/share`), fish honors `XDG_CONFIG_HOME` (falling back to `$HOME/.config`), and zsh resolves both the completion file and `.zshrc` via `ZDOTDIR` (falling back to `$HOME`). It still requires `HOME` to be set; it fails fast (without writing files into the current directory) when `HOME` is empty, and exits non-zero if any completion file cannot be written. Re-running `--install` is idempotent and does not duplicate the zsh init snippet in `.zshrc`. ### Desktop notification If you use gup with --notify option, gup command notify you on your desktop whether the update was successful or unsuccessful after the update was finished. ```shell $ gup update --notify ``` ![success](./doc/img/notify_success.png) ![warning](./doc/img/notify_warning.png) ### Disable colorized output gup colorizes its output by default. To turn colors off, pass `--no-color` or set the `NO_COLOR` environment variable to a non-empty value (following the [NO_COLOR](https://no-color.org/) convention). This is useful when piping output, in CI logs, or with `NO_COLOR` set globally. ```shell $ gup update --no-color $ NO_COLOR=1 gup update ``` ## gup vs. `go tool` Go 1.24's built-in [`go tool`](https://go.dev/doc/modules/managing-dependencies#tools) manages tools scoped to a single project and recorded in that project's `go.mod`, so those tools exist only inside that module. gup manages the binaries installed system-wide under `$GOBIN`, the commands you run from any directory and keep alongside your dotfiles, optionally pinned to versions you depend on. Use `go tool` for per-project tooling and gup for your global toolbox. ## Feature comparison | Feature | gup | [go-global-update](https://github.com/Gelio/go-global-update) | `go install` loop | | --- | :-: | :-: | :-: | | Parallel update | Yes | No | Manual | | Update time, 9 binaries | 0.7s | 2.9s | 2.9s | | Per-package update channels (`latest`/`main`/`master`) | Yes | No | Manual | | Version pinning / lock | Yes | No | Manual | | Export/import tool set | Yes | No | Manual | | Migrate binaries to a new `$GOBIN` | Yes | No | Manual | | Machine-readable JSON output (`--json`) | Yes | No | No | | Shell completion generation/install | Yes | No | No | | `update` reinstalls up-to-date binaries | No | Yes | Yes | | `migrate --force` reinstalls when the target already exists | Yes | No | Manual | | Failure diagnostics / next-step hints | Yes | Yes | No | | `NO_COLOR` support | Yes | Yes | — | *Update time: 9 binaries each with a newer version available; gup updates in parallel, the others sequentially. AMD Ryzen AI Max+ 395 / go 1.26.4, median of 5 runs with a warm module cache; times depend on build time and CPU.* ## FAQ ### `gup` fails with `fatal: not a git repository` You are probably on oh-my-zsh, which ships a `gup` alias for `git pull --rebase` that shadows this command ([#16](https://github.com/nao1215/gup/issues/16), [#204](https://github.com/nao1215/gup/issues/204)). Remove or rename that alias, or run gup with a leading backslash to bypass it: ```shell $ \gup update ``` ## Contributing First off, thanks for taking the time to contribute! ❤️ See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. Developer workflow, quality checklist, and tool management are documented in [CONTRIBUTING.md](./CONTRIBUTING.md). Contributions are not only related to development. For example, GitHub Star motivates me to develop! ### Star History [![Star History Chart](https://api.star-history.com/svg?repos=nao1215/gup&type=Date)](https://star-history.com/#nao1215/gup&Date) ## Contact If you would like to send comments such as "find a bug" or "request for additional features" to the developer, please use one of the following contacts. - [GitHub Issue](https://github.com/nao1215/gup/issues) You can use the bug-report subcommand to send a bug report. ``` $ gup bug-report ※ Open GitHub issue page by your default browser ``` ## LICENSE The gup project is licensed under the terms of [the Apache License 2.0](./LICENSE). ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
CHIKAMATSU Naohiro
CHIKAMATSU Naohiro

💻
KEINOS
KEINOS

💻
mattn
mattn

💻
Justin Lecher
Justin Lecher

💻
Lincoln Nogueira
Lincoln Nogueira

💻
Masaya Watanabe
Masaya Watanabe

💻
memreflect
memreflect

💻
Akimo
Akimo

💻
rkscv
rkscv

💻
Ville Skyttä
Ville Skyttä

💻
Zephyr Lykos
Zephyr Lykos

💻
iTrooz
iTrooz

💻
Tiago Peczenyj
Tiago Peczenyj

💻
ICHINOSE Shogo
ICHINOSE Shogo

📖 💻
Jean-Yves LENHOF
Jean-Yves LENHOF

📖
Clara Bennett
Clara Bennett

📖
Lucas Marchesan
Lucas Marchesan

📖
Radim Kolar
Radim Kolar

🐛
Mohannad Abdulaziz
Mohannad Abdulaziz

🐛
Yannick
Yannick

🐛
Diego Alcântara
Diego Alcântara

🐛
Crocmagnon
Crocmagnon

🐛
Luke Hamburg
Luke Hamburg

🐛
Frederick Zhang
Frederick Zhang

🤔
Tim Schwenke
Tim Schwenke

🤔
ybrhue
ybrhue

🤔
Samuel D. Leslie
Samuel D. Leslie

🤔
Giovanni Bassi
Giovanni Bassi

🤔
Nick Craig-Wood
Nick Craig-Wood

🤔
Rui Chen
Rui Chen

🐛
phanirithvij
phanirithvij

🐛
Darkcast
Darkcast

🐛
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!