Install from @latest, retract v0.0.1, print a go-installed binary's version (closes #116)
check / check (push) Waiting to run
check / check (push) Waiting to run
README "Install" now leads with go install ...@latest: until 1.0 each release is a v0.0.N tag on main's tip, so @latest gives that tip once it has passed the build. It also says what @main and v0.0.1 give and how to check an installed binary. go.mod retracts v0.0.1, the early build without a database. sfdupes --version printed "dev" for every go-installed binary, since only the linker set Version. When the linker sets none, it now prints the main module's version from runtime/debug.ReadBuildInfo; "dev" is left only when neither is known. Version is now empty by default. The retraction takes effect only once a release carrying it is the newest. Model: opus-5-5
This commit was merged in pull request #130.
This commit is contained in:
@@ -49,15 +49,40 @@ results are as fresh as the last completed scan.
|
||||
|
||||
### Install
|
||||
|
||||
With Go installed, this builds and installs the current `main` branch:
|
||||
With Go installed, this builds and installs the newest release:
|
||||
|
||||
```sh
|
||||
go install sneak.berlin/go/sfdupes@latest
|
||||
```
|
||||
|
||||
Until 1.0, releases are `v0.0.N` tags on the tip of the `main` branch: each time
|
||||
`main` moves and its tip passes `script/cibuild`, that tip is tagged with the
|
||||
next `v0.0.N`. So `@latest` installs `main`'s tip once it has passed the build.
|
||||
`@main` installs the tip before it is tagged:
|
||||
|
||||
```sh
|
||||
go install sneak.berlin/go/sfdupes@main
|
||||
```
|
||||
|
||||
The binary goes to `$(go env GOPATH)/bin`, or to `$GOBIN` when that is set. A
|
||||
binary installed this way reports its version as `dev`; one built from a clone
|
||||
or into the Docker image carries the git tag or commit it was built from.
|
||||
`v0.0.1` is an early build without a database. It does not work as this README
|
||||
describes, and `go.mod` retracts it.
|
||||
|
||||
The binary goes to `$(go env GOPATH)/bin`, or to `$GOBIN` when that is set. To
|
||||
see which version an installed binary is:
|
||||
|
||||
```sh
|
||||
sfdupes --version
|
||||
go version -m "$(command -v sfdupes)"
|
||||
```
|
||||
|
||||
`sfdupes --version` prints `sfdupes VERSION`. For a binary from `go install`,
|
||||
`VERSION` is the version Go recorded in it: the release tag, such as `v0.0.3`,
|
||||
or for `@main` a pseudo-version ending in the first 12 characters of the
|
||||
commit's hash. A binary built from a clone or into the Docker image carries the
|
||||
git tag or commit it was built from instead, and `VERSION` is `dev` only when
|
||||
the binary carries no version at all. `go version -m` prints what Go recorded in
|
||||
the binary; its `mod` line names the module's version. Binaries from `v0.0.2`
|
||||
and earlier print `dev`, so check those with `go version -m`.
|
||||
|
||||
From a clone, `make build` writes the binary to `./sfdupes`:
|
||||
|
||||
@@ -262,10 +287,10 @@ sfdupes [command] --help
|
||||
CPUs), `-x` (`--one-file-system`) keeps the walk of each operand on that
|
||||
operand's filesystem, and `--no-default-excludes` lets the walk enter the
|
||||
default excludes, a list of paths; all three are described under "`scan` mode".
|
||||
`sfdupes --version` (or `-v`) prints one line, `sfdupes VERSION`, to stdout and
|
||||
exits 0, writing nothing to stderr. `-h` or `--help`, alone or after a
|
||||
subcommand, prints the help text to stderr and exits 0, writing nothing to
|
||||
stdout.
|
||||
`sfdupes --version` (or `-v`) prints one line, `sfdupes VERSION` (see
|
||||
"Install"), to stdout and exits 0, writing nothing to stderr. `-h` or `--help`,
|
||||
alone or after a subcommand, prints the help text to stderr and exits 0, writing
|
||||
nothing to stdout.
|
||||
|
||||
### Database
|
||||
|
||||
|
||||
@@ -29,6 +29,16 @@
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- releases until 1.0 are `v0.0.N` tags on `main`'s tip, so
|
||||
`go install sneak.berlin/go/sfdupes@latest` installs `main`'s tip once it has
|
||||
passed the build; README "Install" says so, says what `@main` and `v0.0.1`
|
||||
give, and how to check an installed binary. `go.mod` retracts `v0.0.1`, the
|
||||
early build without a database. When the linker set no version,
|
||||
`sfdupes --version` prints the main module's version that Go recorded in the
|
||||
binary, the tag or the pseudo-version of a `go install` build, and `dev` only
|
||||
when neither is known (2026-10-17,
|
||||
https://git.eeqj.de/sneak/sfdupes/issues/116)
|
||||
|
||||
- `scan` creates the database's directory where the database path leads: the
|
||||
path with its last element cut off as text, never cleaned, so a `..` after a
|
||||
symlinked directory is followed through the symlink, as when the file is
|
||||
|
||||
@@ -24,3 +24,5 @@ require (
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
retract v0.0.1 // early build without a database; does not work as the README describes
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -58,10 +59,32 @@ var errNoSubcommand = errors.New("no subcommand")
|
||||
var errWorkersBelowOne = errors.New("--workers must be at least 1")
|
||||
|
||||
// Version is the build version, injected at link time via -ldflags
|
||||
// (see the Makefile); "dev" for a plain go build.
|
||||
// (see the Makefile); empty when the linker set none, as for a binary
|
||||
// built by go install. buildVersion turns it into what --version prints.
|
||||
//
|
||||
//nolint:gochecknoglobals // written only by the linker
|
||||
var Version = "dev"
|
||||
var Version string
|
||||
|
||||
// buildVersion returns the version that sfdupes --version prints
|
||||
// (README "Install"): the version the linker set, when it set one;
|
||||
// otherwise the main module's version that Go recorded in the binary,
|
||||
// a tag such as v0.0.2 or the pseudo-version of an @main build; and
|
||||
// "dev" when neither is known. info is what debug.ReadBuildInfo
|
||||
// returns, nil when the binary carries none. Go records "(devel)" for
|
||||
// a build that has no version, such as one inside a copy of the source
|
||||
// without git.
|
||||
func buildVersion(linkerVersion string, info *debug.BuildInfo) string {
|
||||
if linkerVersion != "" {
|
||||
return linkerVersion
|
||||
}
|
||||
|
||||
if info == nil || info.Main.Version == "" ||
|
||||
info.Main.Version == "(devel)" {
|
||||
return "dev"
|
||||
}
|
||||
|
||||
return info.Main.Version
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Once the reader of a stdout pipe has gone, as in "sfdupes report |
|
||||
@@ -116,7 +139,10 @@ func newRootCommand(stdout, stderr io.Writer) *cobra.Command {
|
||||
var showVersion bool
|
||||
|
||||
printVersion := runE(func(context.Context, []string) error {
|
||||
_, err := fmt.Fprintf(stdout, "sfdupes %s\n", Version)
|
||||
info, _ := debug.ReadBuildInfo()
|
||||
|
||||
_, err := fmt.Fprintf(stdout, "sfdupes %s\n",
|
||||
buildVersion(Version, info))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write stdout: %w", err)
|
||||
}
|
||||
|
||||
+38
-1
@@ -9,6 +9,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -714,6 +715,8 @@ func TestRunVersion(t *testing.T) {
|
||||
|
||||
// README §Subcommands: the version is one line on stdout, with
|
||||
// nothing on stderr, and exits 0.
|
||||
info, _ := debug.ReadBuildInfo()
|
||||
|
||||
for _, arg := range []string{"--version", "-v"} {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
@@ -722,7 +725,7 @@ func TestRunVersion(t *testing.T) {
|
||||
t.Errorf("run(%s) = %d, want %d", arg, code, exitOK)
|
||||
}
|
||||
|
||||
want := "sfdupes " + Version + "\n"
|
||||
want := "sfdupes " + buildVersion(Version, info) + "\n"
|
||||
if got := stdout.String(); got != want {
|
||||
t.Errorf("run(%s) stdout = %q, want %q", arg, got, want)
|
||||
}
|
||||
@@ -733,6 +736,40 @@ func TestRunVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// README "Install": the linker's version wins; otherwise the main
|
||||
// module's version that Go recorded; "dev" when neither is known.
|
||||
tagged := &debug.BuildInfo{Main: debug.Module{Version: "v0.0.2"}}
|
||||
pseudo := &debug.BuildInfo{Main: debug.Module{
|
||||
Version: "v1.0.0-rc2.0.20261016120000-4e4a94d0c2a1",
|
||||
}}
|
||||
devel := &debug.BuildInfo{Main: debug.Module{Version: "(devel)"}}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
linker string
|
||||
info *debug.BuildInfo
|
||||
want string
|
||||
}{
|
||||
{"linker set", "v0.0.2-3-g4e4a94d", tagged, "v0.0.2-3-g4e4a94d"},
|
||||
{"linker set, no build info", "4e4a94d", nil, "4e4a94d"},
|
||||
{"build info with a tag", "", tagged, "v0.0.2"},
|
||||
{"build info with a pseudo-version", "", pseudo,
|
||||
"v1.0.0-rc2.0.20261016120000-4e4a94d0c2a1"},
|
||||
{"(devel)", "", devel, "dev"},
|
||||
{"no build info", "", nil, "dev"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
if got := buildVersion(c.linker, c.info); got != c.want {
|
||||
t.Errorf("%s: buildVersion(%q, ...) = %q, want %q",
|
||||
c.name, c.linker, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunVersionWriteFailureIsFatal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user