@@ -80,8 +80,9 @@ type ghRelease struct {
8080}
8181
8282// Check resolves channel against currentVersion and queries GitHub.
83- // currentBuildTime is the ldflag-injected constant.BuildTime; empty is fine.
84- func Check (ctx context.Context , channel , currentVersion , currentBuildTime string ) (* CheckResult , error ) {
83+ // currentRevision is the ldflag-injected constant.GitRevision; empty is
84+ // tolerated (we'll just trust version-string equality in that case).
85+ func Check (ctx context.Context , channel , currentVersion , currentRevision string ) (* CheckResult , error ) {
8586 resolved , rel , err := pickRelease (ctx , channel , currentVersion )
8687 if err != nil {
8788 return nil , err
@@ -98,7 +99,7 @@ func Check(ctx context.Context, channel, currentVersion, currentBuildTime string
9899 PublishedAt : rel .PublishedAt ,
99100 }
100101 if latest == currentVersion {
101- res .UpdateAvailable = nightlyRepublished (rel , currentBuildTime )
102+ res .UpdateAvailable = nightlyRepublished (ctx , rel , currentRevision )
102103 } else {
103104 res .UpdateAvailable = compareVersions (latest , currentVersion ) > 0
104105 }
@@ -111,7 +112,7 @@ func Check(ctx context.Context, channel, currentVersion, currentBuildTime string
111112
112113// Apply downloads + swaps + (optionally) restarts. Each phase is reported
113114// to onState so the dashboard can render progress; CLI passes nil.
114- func Apply (ctx context.Context , opts ApplyOptions , currentVersion , currentBuildTime string , log * zap.SugaredLogger , onState func (State )) error {
115+ func Apply (ctx context.Context , opts ApplyOptions , currentVersion , currentRevision string , log * zap.SugaredLogger , onState func (State )) error {
115116 emit := func (s State ) {
116117 if onState != nil {
117118 onState (s )
@@ -128,9 +129,9 @@ func Apply(ctx context.Context, opts ApplyOptions, currentVersion, currentBuildT
128129
129130 if ! opts .Force {
130131 if latest == currentVersion {
131- if nightlyRepublished (rel , currentBuildTime ) {
132- log .Infof ("nightly tag %s republished after local build (%s) ; reinstalling" ,
133- rel .TagName , currentBuildTime )
132+ if nightlyRepublished (ctx , rel , currentRevision ) {
133+ log .Infof ("nightly tag %s now points at a different commit than local %s ; reinstalling" ,
134+ rel .TagName , currentRevision )
134135 } else {
135136 log .Info ("already up to date" )
136137 emit (StateDone )
@@ -269,30 +270,49 @@ func getJSON(ctx context.Context, url string, out any) error {
269270}
270271
271272// nightlyRepublished reports whether a release whose tag matches the
272- // running version is actually newer than the current binary. Nightly
273- // uses a rolling tag (v1.1.7-next), so version-string equality alone
274- // would mask republished builds. Only meaningful for prereleases; stable
275- // tags don't roll.
276- func nightlyRepublished (rel * ghRelease , currentBuildTime string ) bool {
277- if ! rel .Prerelease || currentBuildTime == "" {
273+ // running version actually points at a different commit than the
274+ // running binary. Nightly uses a rolling tag (v1.1.7-next), so
275+ // version-string equality alone would mask republished builds.
276+ //
277+ // We can't time-compare published_at vs BuildTime: goreleaser publishes
278+ // the release a few seconds after building the artifact, so the same
279+ // artifact would always look "older" than its own release. SHA is the
280+ // only reliable signal.
281+ //
282+ // Conservative on uncertainty: empty currentRevision (bare `go build`)
283+ // or GitHub fetch failure -> false (no spurious update prompts; user
284+ // can --force).
285+ func nightlyRepublished (ctx context.Context , rel * ghRelease , currentRevision string ) bool {
286+ if ! rel .Prerelease || currentRevision == "" {
278287 return false
279288 }
280- built , ok := parseBuildTime ( currentBuildTime )
281- if ! ok {
289+ sha , err := fetchTagCommitSHA ( ctx , rel . TagName )
290+ if err != nil || sha == "" {
282291 return false
283292 }
284- return rel . PublishedAt . After ( built )
293+ return ! shaMatchesRevision ( sha , currentRevision )
285294}
286295
287- // parseBuildTime accepts both ldflag formats: goreleaser's RFC3339
288- // ({{.Date}}) and the Makefile's "2006-01-02-15:04:05".
289- func parseBuildTime (s string ) (time.Time , bool ) {
290- for _ , layout := range []string {time .RFC3339 , "2006-01-02-15:04:05" } {
291- if t , err := time .Parse (layout , s ); err == nil {
292- return t , true
293- }
296+ // shaMatchesRevision compares the short revision baked into the binary
297+ // (goreleaser uses {{.ShortCommit}}, 7 chars; Makefile uses full SHA)
298+ // against the full SHA returned by GitHub. Prefix-match is enough.
299+ func shaMatchesRevision (fullSHA , currentRevision string ) bool {
300+ n := len (currentRevision )
301+ if n == 0 || n > len (fullSHA ) {
302+ return false
303+ }
304+ return strings .EqualFold (fullSHA [:n ], currentRevision )
305+ }
306+
307+ func fetchTagCommitSHA (ctx context.Context , tag string ) (string , error ) {
308+ var c struct {
309+ SHA string `json:"sha"`
310+ }
311+ url := "https://api.github.com/repos/Ehco1996/ehco/commits/" + tag
312+ if err := getJSON (ctx , url , & c ); err != nil {
313+ return "" , err
294314 }
295- return time. Time {}, false
315+ return c . SHA , nil
296316}
297317
298318// compareVersions returns -1/0/1 like semver.Compare. Falls back to
0 commit comments