9442a718da
The honesty half (no fabricated counts) leaves shallow installs permanently count-less. The compare API knows the full graph regardless of local clone depth: GET /repos/<o>/<r>/compare/<current>...<target> returns ahead_by — exactly the behind count the shallow boundary lost. - hermes_cli/banner.py: _github_compare_behind() (bounded, unauthenticated, best-effort); wired into _check_via_rev and the shallow branch of _check_via_local_git. ahead_by==0 with differing tips = local-ahead => 0. - hermes_cli/update_cmd.py: hermes update --check shallow path prints the exact count when recoverable, presence-only wording otherwise. - apps/desktop/electron/update-count.ts: compareApiUrl() + parseCompareBehindCount() pure helpers; main.ts fetches the count when resolveBehindCount() returns null, and the SSH-official passive path stops fabricating behind:1 (uses compare API + updateAvailable flag). - apps/desktop/src/lib/version-status.ts: updateAvailable now applies to the client target too, so a shallow desktop install shows '(update)' instead of nothing (or the old frozen '(+1)'). Fixes #84591; CLI siblings of #78253 / #53479 behavior. E2E: live compare API returned 61/62 for real 61/62-commit gaps and 0 for the reversed (local-ahead) pair; real shallow-clone fixture (depth-1 clone + depth-1 fetch, merge-base broken) recovers the exact count with the API and falls back to the honest sentinel offline.
112 lines
3.8 KiB
TypeScript
112 lines
3.8 KiB
TypeScript
/**
|
|
* Pure derivation of how the app names an update target: the label
|
|
* (`v0.4.2`, `backend v0.4.2 (+12)`, `v0.4.2 · update`), its tooltip, and
|
|
* whether an update is waiting.
|
|
*
|
|
* The statusbar and the command palette both name the same two targets, so the
|
|
* wording lives here once — a palette row and its statusbar item can't drift
|
|
* into describing the same install differently.
|
|
*/
|
|
|
|
import type { UpdateTarget } from '@/lib/update-copy'
|
|
|
|
export interface VersionStatusCopy {
|
|
backendLabel: (version: string) => string
|
|
backendVersion: (version: string) => string
|
|
branch: (branch: string) => string
|
|
clientLabel: (version: string) => string
|
|
commit: (sha: string) => string
|
|
commitsBehind: (count: number, branch: string) => string
|
|
desktopVersion: (version: string) => string
|
|
restart: string
|
|
unknown: string
|
|
update: string
|
|
updateInProgress: string
|
|
}
|
|
|
|
export interface VersionStatusInput {
|
|
/** True while an apply is in flight (including the restart hand-off). */
|
|
applying: boolean
|
|
/** Latest line from the apply stream — leads the tooltip while applying. */
|
|
applyMessage?: string
|
|
behind?: number
|
|
branch?: string
|
|
copy: VersionStatusCopy
|
|
/** Remote mode: the client is one of two versions on screen, so it says so. */
|
|
remote: boolean
|
|
/** The apply reached the restart stage — labels `restart`, not `update`. */
|
|
restarting: boolean
|
|
/** Client only: short commit sha of the running build. */
|
|
sha?: null | string
|
|
target: UpdateTarget
|
|
/** An update the commit count can't express (shallow clones, pip installs). */
|
|
updateAvailable?: boolean
|
|
version?: null | string
|
|
}
|
|
|
|
export interface VersionStatusResult {
|
|
/** Secondary text beside the label — the commit sha, when it adds anything. */
|
|
detail?: string
|
|
/** An update is waiting: callers tint the row with it. */
|
|
hasUpdate: boolean
|
|
label: string
|
|
tooltip?: string
|
|
/** Nothing identifies this target yet — callers hide the row. */
|
|
unknown: boolean
|
|
}
|
|
|
|
export function resolveVersionStatus({
|
|
applyMessage,
|
|
applying,
|
|
behind = 0,
|
|
branch,
|
|
copy,
|
|
remote,
|
|
restarting,
|
|
sha = null,
|
|
target,
|
|
updateAvailable,
|
|
version = null
|
|
}: VersionStatusInput): VersionStatusResult {
|
|
const client = target === 'client'
|
|
const busy = applying || restarting
|
|
// updateAvailable covers every "behind but uncountable" shape: shallow
|
|
// installer clones (behind === null upstream, coalesced to 0 by callers),
|
|
// SSH-official presence-only checks, and pip installs. It applies to BOTH
|
|
// targets — the client statusbar item is how a shallow desktop install
|
|
// learns it's stale at all.
|
|
const available = behind > 0 || !!updateAvailable
|
|
|
|
// A client with no version still identifies itself by sha; a backend can't.
|
|
const named = version ?? (client ? sha : null) ?? copy.unknown
|
|
|
|
const base = !client
|
|
? copy.backendLabel(named)
|
|
: remote
|
|
? copy.clientLabel(named)
|
|
: (version && `v${version}`) || named
|
|
|
|
// Commits behind is the precise diff; `(update)` is the fallback for a
|
|
// backend that knows it's stale but can't count (pip, non-git checkout).
|
|
const hint = busy ? '' : behind > 0 ? ` (+${behind})` : available ? ` (${copy.update})` : ''
|
|
|
|
const tooltip = [
|
|
busy && (applyMessage || copy.updateInProgress),
|
|
!busy && behind > 0 && copy.commitsBehind(behind, (client ? branch : 'main') || '...'),
|
|
!busy && behind <= 0 && available && copy.update,
|
|
version && (client ? copy.desktopVersion(version) : copy.backendVersion(version)),
|
|
client && sha && copy.commit(sha),
|
|
client && branch && copy.branch(branch)
|
|
]
|
|
.filter(Boolean)
|
|
.join(' · ')
|
|
|
|
return {
|
|
detail: client && version && sha && !busy && !remote ? sha : undefined,
|
|
hasUpdate: !busy && available,
|
|
label: busy ? `${base} · ${restarting ? copy.restart : copy.update}` : `${base}${hint}`,
|
|
tooltip: tooltip || undefined,
|
|
unknown: !version && !(client && sha)
|
|
}
|
|
}
|