Skip to content

Commit 371615e

Browse files
authored
Merge pull request #232 from nao1215/nchika/v1.0.0
Release v1.0.0
2 parents 23128ad + 14fbe68 commit 371615e

40 files changed

Lines changed: 3367 additions & 360 deletions

.github/workflows/unit_test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,5 @@ jobs:
3232
go-version: ${{ matrix.go }}
3333

3434
- name: Run tests with race detector (Linux only)
35-
if: matrix.os == 'ubuntu-latest'
3635
run: go test -race ./...
3736

CHANGELOG.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,55 @@
1+
## [v1.0.0](https://github.com/nao1215/gup/compare/v0.28.3...v1.0.0) (2026-02-15)
2+
3+
### ⚠ BREAKING CHANGES
4+
5+
* Config format changed from plain-text `gup.conf` (`<name> = <import-path>`) to JSON `gup.json` with versioned schema
6+
* `gup import` now installs the exact version recorded in `gup.json`
7+
* `gup import` flag changed from `--input` to `--file`
8+
* `gup export` flag changed from `--output` to `--file`
9+
10+
### Features
11+
12+
* Store update channels (`latest` / `main` / `master`) in `gup.json`
13+
* Auto-adapt to module path changes (detect and follow import path renames)
14+
* Add config path resolution (`$XDG_CONFIG_HOME/gup/gup.json` first, then `./gup.json`)
15+
* Add `--file` option to both import and export
16+
* Always include version in bug-report template
17+
18+
### Fixes
19+
20+
* Persist dry-run temp path for proper cleanup and cancel workers on interrupt signals
21+
* Validate binary names in `removeOldBinaryIfRenamed` to prevent path traversal
22+
* Block path traversal in remove targets
23+
* Warn instead of failing when `gup.json` is corrupt during update
24+
* Surface `gup.json` parse errors during update
25+
* Normalize binary names in `--main`/`--master`/`--latest` flag resolution for Windows
26+
* Remove stale config entries when binary is renamed during update
27+
* Return original importPath when prefix does not match in `replaceImportPathPrefix`
28+
* Trim whitespace in `--exclude` package names
29+
* Require explicit `--install` for completion file writes
30+
* Validate go command availability in import
31+
* Exit with status 1 when command execution fails
32+
* Version coloring: yellow=outdated, green=up-to-date
33+
* Reject malformed `gup.conf` lines early (legacy format migration)
34+
* Normalize devel version to latest during import
35+
36+
### Performance
37+
38+
* Use a fixed worker-pool implementation for package processing
39+
* Deduplicate `GetLatestVer` calls and parallelize binary info collection
40+
* Filter binary completions by typed prefix
41+
42+
### Refactoring
43+
44+
* Replace `golang.org/x/exp/slices` with standard `slices` package
45+
* Share config file writing logic across commands
46+
* Remove unused update wrapper function and unused first argument from `shouldPersistChannels`
47+
48+
### Tests
49+
50+
* Add tests to increase coverage from 79.6% to 88.7%
51+
* Stub update operations in root command tests
52+
153
## [v0.28.3](https://github.com/nao1215/gup/compare/v0.28.2...v0.28.3) (2026-02-15)
254

355
* Fix bug fixes and CI improvements [#230](https://github.com/nao1215/gup/pull/230) ([nao1215](https://github.com/nao1215))

README.md

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)
55
[![reviewdog](https://github.com/nao1215/gup/actions/workflows/reviewdog.yml/badge.svg)](https://github.com/nao1215/gup/actions/workflows/reviewdog.yml)
66
![Coverage](https://raw.githubusercontent.com/nao1215/octocovs-central-repo/main/badges/nao1215/gup/coverage.svg)
7-
[![gosec](https://github.com/nao1215/gup/actions/workflows/security.yml/badge.svg)](https://github.com/nao1215/gup/actions/workflows/security.yml)
87
[![Go Reference](https://pkg.go.dev/badge/github.com/nao1215/gup.svg)](https://pkg.go.dev/github.com/nao1215/gup)
98
[![Go Report Card](https://goreportcard.com/badge/github.com/nao1215/gup)](https://goreportcard.com/report/github.com/nao1215/gup)
109
![GitHub](https://img.shields.io/github/license/nao1215/gup)
@@ -19,6 +18,11 @@
1918

2019
If you are using oh-my-zsh, then gup has an alias set up. The alias is `gup - git pull --rebase`. Therefore, please make sure that the oh-my-zsh alias is disabled (e.g. $ \gup update).
2120

21+
## Breaking change (v1.0.0)
22+
- The config file format changed from `gup.conf` to `gup.json`.
23+
- `gup.conf` is no longer read by `gup import`.
24+
- The update channel (`latest` / `main` / `master`) is stored per package in `gup.json`.
25+
2226

2327
## Supported OS (unit testing with GitHub Actions)
2428
- Linux
@@ -80,10 +84,15 @@ Also works in combination with --dry-run
8084
$ gup update --exclude=gopls,golangci-lint //--exclude or -e, this example will exclude 'gopls' and 'golangci-lint'
8185
```
8286

83-
### Update binaries with @main or @master
84-
If you want to update binaries with @master or @main, you can specify the -m or --master option.
87+
### Update binaries with @main, @master, or @latest
88+
If you want to control update source per binary, use the following options:
89+
- `--main` (`-m`): update by `@main` (fallback to `@master`)
90+
- `--master`: update by `@master`
91+
- `--latest`: update by `@latest`
92+
93+
The selected channel is saved to `gup.json` and reused by future `gup update` runs.
8594
```shell
86-
$ gup update --main=gup,lazygit,sqly
95+
$ gup update --main=gup,lazygit --master=sqly --latest=air
8796
```
8897

8998
### List up command name with package path and version under $GOPATH/bin
@@ -133,27 +142,54 @@ If you want to update binaries, the following command.
133142
$ gup update mimixbox
134143
```
135144
### Export/Import subcommand
136-
You use the export/import subcommand if you want to install the same golang binaries across multiple systems. By default, export-subcommand exports the file to $XDG_CONFIG_HOME/gup/gup.conf. If you want to know [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), see this link. After you have placed gup.conf in the same path hierarchy on another system, you execute import-subcommand. gup start the installation
137-
according to the contents of gup.conf.
145+
Use export/import when you want to install the same Go binaries across multiple systems.
146+
`gup.json` stores import path, binary version, and update channel (`latest` / `main` / `master`).
147+
`import` installs the exact version written in the file.
148+
149+
```json
150+
{
151+
"schema_version": 1,
152+
"packages": [
153+
{
154+
"name": "gal",
155+
"import_path": "github.com/nao1215/gal/cmd/gal",
156+
"version": "v1.1.1",
157+
"channel": "latest"
158+
},
159+
{
160+
"name": "posixer",
161+
"import_path": "github.com/nao1215/posixer",
162+
"version": "v0.1.0",
163+
"channel": "main"
164+
}
165+
]
166+
}
167+
```
168+
169+
By default:
170+
- `gup export` writes to `$XDG_CONFIG_HOME/gup/gup.json`
171+
- `gup import` auto-detects config path in this order:
172+
1) `$XDG_CONFIG_HOME/gup/gup.json` (if exists)
173+
2) `./gup.json` (if exists)
174+
175+
You can always override the path with `--file`.
138176

139177
```shell
140178
※ Environments A (e.g. ubuntu)
141179
$ gup export
142-
Export /home/nao/.config/gup/gup.conf
180+
Export /home/nao/.config/gup/gup.json
143181

144182
※ Environments B (e.g. debian)
145-
$ ls /home/nao/.config/gup/gup.conf
146-
/home/nao/.config/gup/gup.conf
147183
$ gup import
148184
```
149185

150-
Alternatively, export subcommand print package information (it's same as gup.conf) that you want to export at STDOUT if you use --output option. import subcommand can also specify the gup.conf file path if you use --input option.
186+
`export` can print config content to STDOUT by `--output`. `import` can read a specific file by `--file`.
151187
```shell
152188
※ Environments A (e.g. ubuntu)
153-
$ gup export --output > gup.conf
189+
$ gup export --output > gup.json
154190

155191
※ Environments B (e.g. debian)
156-
$ gup import --input=gup.conf
192+
$ gup import --file=gup.json
157193
```
158194

159195
### Generate man-pages (for linux, mac)
@@ -174,13 +210,16 @@ Generate /usr/share/man/man1/gup.1.gz
174210
```
175211

176212
### Generate shell completion file (for bash, zsh, fish)
177-
completion subcommand generates shell completion files for bash, zsh, and fish. If the shell completion file does not exist in the system, the generation process will begin. To activate the completion feature, restart the shell.
213+
`completion` prints completion scripts to STDOUT when you pass a shell name.
214+
To install completion files into your user environment, use `--install`.
178215

179216
```shell
180-
$ gup completion
181-
create bash-completion file: /home/nao/.bash_completion
182-
create fish-completion file: /home/nao/.config/fish/completions/gup.fish
183-
create zsh-completion file: /home/nao/.zsh/completion/_gup
217+
$ gup completion bash > gup.bash
218+
$ gup completion zsh > _gup
219+
$ gup completion fish > gup.fish
220+
221+
# Install files automatically to default user paths
222+
$ gup completion --install
184223
```
185224

186225
### Desktop notification

cmd/bug_report.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"bytes"
55
"fmt"
66
"net/url"
7+
"strings"
78

9+
"github.com/nao1215/gup/internal/cmdinfo"
810
"github.com/spf13/cobra"
911
)
1012

@@ -28,6 +30,10 @@ type openBrowserFunc func(string) bool
2830
// bugReport opens the default browser to start a bug report which will include useful system information.
2931
func bugReport(cmd *cobra.Command, _ []string, openBrowser openBrowserFunc) int {
3032
var buf bytes.Buffer
33+
version := strings.TrimSpace(cmd.Version)
34+
if version == "" {
35+
version = cmdinfo.GetVersion()
36+
}
3137

3238
const (
3339
description = `## Description (About the problem)
@@ -46,7 +52,7 @@ Expected behavior.
4652
Any other useful data to share.
4753
`
4854
)
49-
buf.WriteString(fmt.Sprintf("## gup version\n%s\n\n", cmd.Version))
55+
buf.WriteString(fmt.Sprintf("## gup version\n%s\n\n", version))
5056
buf.WriteString(description)
5157
buf.WriteString(toReproduce)
5258
buf.WriteString(expectedBehavior)

cmd/bug_report_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,20 @@ func Test_bugReport(t *testing.T) {
6262
t.Errorf("bugReport() = %d; want %d", gotReturnVal, wantReturnVal)
6363
}
6464
}
65+
66+
func Test_bugReport_fallbackVersion(t *testing.T) {
67+
t.Parallel()
68+
69+
cmd := newBugReportCmd()
70+
cmd.Version = ""
71+
72+
gotReturnVal := bugReport(cmd, nil, func(s string) bool {
73+
if !strings.Contains(s, "gup+version") {
74+
t.Errorf("expected bug report URL to contain gup version section, but got: %s", s)
75+
}
76+
return true
77+
})
78+
if gotReturnVal != 0 {
79+
t.Errorf("bugReport() = %d; want %d", gotReturnVal, 0)
80+
}
81+
}

cmd/check.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func doCheck(pkgs []goutil.Package, cpus int, ignoreGoUpdate bool) int {
7777
countFmt := "[%" + pkgDigit(pkgs) + "d/%" + pkgDigit(pkgs) + "d]"
7878
var mu sync.Mutex
7979
needUpdatePkgs := []goutil.Package{}
80+
verCache := newLatestVerCache()
8081

8182
print.Info("check binary under $GOPATH/bin or $GOBIN")
8283

@@ -86,17 +87,30 @@ func doCheck(pkgs []goutil.Package, cpus int, ignoreGoUpdate bool) int {
8687
err = fmt.Errorf(" %s is not installed by 'go install' (or permission incorrect)", p.Name)
8788
} else {
8889
var latestVer string
89-
latestVer, err = goutil.GetLatestVer(p.ModulePath)
90+
modulePathChanged := false
91+
latestVer, err = verCache.get(p.ModulePath)
9092
if err != nil {
91-
err = fmt.Errorf(" %s %w", p.Name, err)
93+
newPkg, changed := resolveModulePathChange(p, err)
94+
if !changed {
95+
err = fmt.Errorf(" %s %w", p.Name, err)
96+
} else {
97+
modulePathChanged = true
98+
p = newPkg
99+
latestVer, err = verCache.get(p.ModulePath)
100+
if err != nil {
101+
err = fmt.Errorf(" %s %w", p.Name, err)
102+
}
103+
}
92104
}
93-
p.Version.Latest = latestVer
94-
95-
shouldUpdate := !p.IsPackageUpToDate() || (!ignoreGoUpdate && !p.IsGoUpToDate())
96-
if shouldUpdate {
97-
mu.Lock()
98-
needUpdatePkgs = append(needUpdatePkgs, p)
99-
mu.Unlock()
105+
if err == nil {
106+
p.Version.Latest = latestVer
107+
108+
shouldUpdate := modulePathChanged || !p.IsPackageUpToDate() || (!ignoreGoUpdate && !p.IsGoUpToDate())
109+
if shouldUpdate {
110+
mu.Lock()
111+
needUpdatePkgs = append(needUpdatePkgs, p)
112+
mu.Unlock()
113+
}
100114
}
101115
}
102116

cmd/check_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33

44
import (
55
"bytes"
6+
"errors"
67
"io"
78
"os"
89
"path/filepath"
@@ -393,3 +394,69 @@ func Test_check_jobsClamp(t *testing.T) {
393394
t.Errorf("check() = %v, want 0 or 1", got)
394395
}
395396
}
397+
398+
func Test_doCheck_modulePathChanged(t *testing.T) {
399+
const (
400+
oldModule = "github.com/cosmtrek/air"
401+
newModule = "github.com/air-verse/air"
402+
)
403+
404+
origGetLatest := getLatestVer
405+
defer func() {
406+
getLatestVer = origGetLatest
407+
}()
408+
409+
getLatestVer = func(modulePath string) (string, error) {
410+
if modulePath == oldModule {
411+
return "", errors.New("version constraints conflict:\n" +
412+
"module declares its path as: " + newModule + "\n" +
413+
"but was required as: " + oldModule)
414+
}
415+
if modulePath == newModule {
416+
return "v1.2.3", nil
417+
}
418+
return "", errors.New("unexpected module path")
419+
}
420+
421+
orgStdout := print.Stdout
422+
orgStderr := print.Stderr
423+
pr, pw, err := os.Pipe()
424+
if err != nil {
425+
t.Fatal(err)
426+
}
427+
print.Stdout = pw
428+
print.Stderr = pw
429+
430+
pkgs := []goutil.Package{
431+
{
432+
Name: "air",
433+
ImportPath: "github.com/cosmtrek/air/cmd/air",
434+
ModulePath: oldModule,
435+
Version: &goutil.Version{
436+
Current: "v1.2.3",
437+
},
438+
GoVersion: &goutil.Version{
439+
Current: "go1.22.4",
440+
Latest: "go1.22.4",
441+
},
442+
},
443+
}
444+
got := doCheck(pkgs, 1, true)
445+
446+
pw.Close()
447+
print.Stdout = orgStdout
448+
print.Stderr = orgStderr
449+
450+
buf := bytes.Buffer{}
451+
if _, err := io.Copy(&buf, pr); err != nil {
452+
t.Fatal(err)
453+
}
454+
_ = pr.Close()
455+
456+
if got != 0 {
457+
t.Fatalf("doCheck() = %v, want 0", got)
458+
}
459+
if !strings.Contains(buf.String(), "$ gup update air ") {
460+
t.Fatalf("expected update hint for migrated module path, got:\n%s", buf.String())
461+
}
462+
}

0 commit comments

Comments
 (0)