feat(version): add stale binary detection with startup warning
Add detection for when the installed gt binary is out of date with the source repository. This helps catch issues where commands fail mysteriously because the installed binary doesn't have recent fixes. Changes: - Add internal/version package with stale binary detection logic - Add startup warning in PersistentPreRunE when binary is stale - Add gt doctor check for stale-binary - Use prefix matching for commit comparison (handles short vs full hash) The warning is non-blocking and only shows once per shell session via the GT_STALE_WARNED environment variable. Resolves: gt-ud912 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ Town root protection:
|
||||
- pre-checkout-hook Verify pre-checkout hook prevents branch switches (fixable)
|
||||
|
||||
Infrastructure checks:
|
||||
- stale-binary Check if gt binary is up to date with repo
|
||||
- daemon Check if daemon is running (fixable)
|
||||
- repo-fingerprint Check database has valid repo fingerprint (fixable)
|
||||
- boot-health Check Boot watchdog health (vet mode)
|
||||
@@ -116,6 +117,7 @@ func runDoctor(cmd *cobra.Command, args []string) error {
|
||||
d.Register(doctor.NewGlobalStateCheck())
|
||||
|
||||
// Register built-in checks
|
||||
d.Register(doctor.NewStaleBinaryCheck())
|
||||
d.Register(doctor.NewTownGitCheck())
|
||||
d.Register(doctor.NewTownRootBranchCheck())
|
||||
d.Register(doctor.NewPreCheckoutHookCheck())
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/version"
|
||||
)
|
||||
|
||||
var infoCmd = &cobra.Command{
|
||||
@@ -39,7 +40,7 @@ Examples:
|
||||
}
|
||||
|
||||
if commit := resolveCommitHash(); commit != "" {
|
||||
info["commit"] = shortCommit(commit)
|
||||
info["commit"] = version.ShortCommit(commit)
|
||||
}
|
||||
if branch := resolveBranch(); branch != "" {
|
||||
info["branch"] = branch
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/version"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
|
||||
@@ -108,10 +109,52 @@ func checkBeadsDependency(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for stale binary (warning only, doesn't block)
|
||||
checkStaleBinaryWarning()
|
||||
|
||||
// Check beads version
|
||||
return CheckBeadsVersion()
|
||||
}
|
||||
|
||||
// staleBinaryWarned tracks if we've already warned about stale binary in this session.
|
||||
// We use an environment variable since the binary restarts on each command.
|
||||
var staleBinaryWarned = os.Getenv("GT_STALE_WARNED") == "1"
|
||||
|
||||
// checkStaleBinaryWarning checks if the installed binary is stale and prints a warning.
|
||||
// This is a non-blocking check - errors are silently ignored.
|
||||
func checkStaleBinaryWarning() {
|
||||
// Only warn once per shell session
|
||||
if staleBinaryWarned {
|
||||
return
|
||||
}
|
||||
|
||||
repoRoot, err := version.GetRepoRoot()
|
||||
if err != nil {
|
||||
// Can't find repo - silently skip (might be running from non-dev environment)
|
||||
return
|
||||
}
|
||||
|
||||
info := version.CheckStaleBinary(repoRoot)
|
||||
if info.Error != nil {
|
||||
// Check failed - silently skip
|
||||
return
|
||||
}
|
||||
|
||||
if info.IsStale {
|
||||
staleBinaryWarned = true
|
||||
os.Setenv("GT_STALE_WARNED", "1")
|
||||
|
||||
msg := fmt.Sprintf("gt binary is stale (built from %s, repo at %s)",
|
||||
version.ShortCommit(info.BinaryCommit), version.ShortCommit(info.RepoCommit))
|
||||
if info.CommitsBehind > 0 {
|
||||
msg = fmt.Sprintf("gt binary is %d commits behind (built from %s, repo at %s)",
|
||||
info.CommitsBehind, version.ShortCommit(info.BinaryCommit), version.ShortCommit(info.RepoCommit))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s %s\n", style.WarningPrefix, msg)
|
||||
fmt.Fprintf(os.Stderr, " %s Run 'gt install' to update\n", style.ArrowPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute runs the root command and returns an exit code.
|
||||
// The caller (main) should call os.Exit with this code.
|
||||
func Execute() int {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/version"
|
||||
)
|
||||
|
||||
// Version information - set at build time via ldflags
|
||||
@@ -28,9 +29,9 @@ var versionCmd = &cobra.Command{
|
||||
branch := resolveBranch()
|
||||
|
||||
if commit != "" && branch != "" {
|
||||
fmt.Printf("gt version %s (%s: %s@%s)\n", Version, Build, branch, shortCommit(commit))
|
||||
fmt.Printf("gt version %s (%s: %s@%s)\n", Version, Build, branch, version.ShortCommit(commit))
|
||||
} else if commit != "" {
|
||||
fmt.Printf("gt version %s (%s: %s)\n", Version, Build, shortCommit(commit))
|
||||
fmt.Printf("gt version %s (%s: %s)\n", Version, Build, version.ShortCommit(commit))
|
||||
} else {
|
||||
fmt.Printf("gt version %s (%s)\n", Version, Build)
|
||||
}
|
||||
@@ -39,6 +40,11 @@ var versionCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
|
||||
// Pass the build-time commit to the version package for stale binary checks
|
||||
if Commit != "" {
|
||||
version.SetCommit(Commit)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCommitHash() string {
|
||||
@@ -57,13 +63,6 @@ func resolveCommitHash() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func shortCommit(hash string) string {
|
||||
if len(hash) > 12 {
|
||||
return hash[:12]
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
func resolveBranch() string {
|
||||
if Branch != "" {
|
||||
return Branch
|
||||
|
||||
Reference in New Issue
Block a user