# check_for_upgrade.sh: Update Process ## Visual State Diagram ```mermaid stateDiagram-v2 [*] --> Bootstrap state Bootstrap { [*] --> CheckLegacyUpdateFile CheckLegacyUpdateFile: check [[ -f ~/.zsh-update && ! -f $ZSH_CACHE_DIR/.zsh-update ]] CheckLegacyUpdateFile --> MigrateLegacyFile: TRUE CheckLegacyUpdateFile --> LoadMode: FALSE MigrateLegacyFile: mv ~/.zsh-update $ZSH_CACHE_DIR/.zsh-update MigrateLegacyFile --> LoadMode LoadMode: read update mode from zstyle LoadMode --> LegacyModeFallback: fail LegacyModeFallback: set update_mode=prompt LegacyModeFallback --> LegacyPromptFlag LegacyPromptFlag: evaluate DISABLE_UPDATE_PROMPT LegacyPromptFlag --> LegacyAutoFlag LegacyAutoFlag: evaluate DISABLE_AUTO_UPDATE LegacyAutoFlag --> PreFlightGate LoadMode --> PreFlightGate: success } PreFlightGate: aggregated early-return gate PreFlightGate --> ExitNoUpdate: mode=disabled PreFlightGate --> ExitNoUpdate: write permission or owner check failed PreFlightGate --> ExitNoUpdate: tty gate failed PreFlightGate --> ExitNoUpdate: git command unavailable PreFlightGate --> ExitNoUpdate: repository check failed PreFlightGate --> ModeDispatch: all checks pass ExitNoUpdate: unset update_mode; return state ModeDispatch { [*] --> SetupBackgroundHooks: mode=background-alpha [*] --> RunHandleUpdate: otherwise } SetupBackgroundHooks: autoload -Uz add-zsh-hook SetupBackgroundHooks --> RegisterBgPrecmd RegisterBgPrecmd: add-zsh-hook precmd _omz_bg_update RegisterBgPrecmd --> BackgroundStatusHook RunHandleUpdate --> HandleUpdateCore state HandleUpdateCore { [*] --> LockCleanupCheck LockCleanupCheck: load datetime and read lock mtime LockCleanupCheck --> CheckStaleAge: lock exists LockCleanupCheck --> AcquireLock: no lock CheckStaleAge: stale if lock older than 24h CheckStaleAge --> RemoveStaleLock: TRUE CheckStaleAge --> AcquireLock: FALSE RemoveStaleLock: rm -rf $ZSH/log/update.lock RemoveStaleLock --> AcquireLock AcquireLock: mkdir $ZSH/log/update.lock AcquireLock --> ExitHandle: fail (lock exists) AcquireLock --> InstallTrap: success InstallTrap: install trap to cleanup lock and functions InstallTrap --> LoadStatusFile LoadStatusFile: source status file and validate last epoch LoadStatusFile --> InitStatusFile: fail or empty LAST_EPOCH LoadStatusFile --> FrequencyCheck: success InitStatusFile: update_last_updated_file InitStatusFile --> ExitHandle FrequencyCheck: resolve frequency from zstyle or default FrequencyCheck --> PeriodElapsed PeriodElapsed: enough days elapsed since last check PeriodElapsed --> RepoCheck: TRUE PeriodElapsed --> ExitHandle: FALSE RepoCheck: verify ZSH path is a git repository RepoCheck --> CheckUpdateAvailable: success RepoCheck --> ExitHandle: fail CheckUpdateAvailable --> ReminderOrTypedInput: update available (return 0) CheckUpdateAvailable --> ExitHandleWithTimestamp: no update (return 1) ExitHandleWithTimestamp: update_last_updated_file ExitHandleWithTimestamp --> ExitHandle ReminderOrTypedInput --> ReminderExit: mode=reminder ReminderOrTypedInput --> TypedInputCheck: mode!=background-alpha ReminderOrTypedInput --> ModeAutoGate: mode=background-alpha TypedInputCheck: detect pending typed input from terminal TypedInputCheck --> ReminderExit: input detected TypedInputCheck --> ModeAutoGate: no input ReminderExit: echo reminder message ReminderExit --> ExitHandle ModeAutoGate --> RunUpgrade: mode is auto or background alpha ModeAutoGate --> PromptUser: mode=prompt PromptUser: read -r -k 1 option PromptUser --> RunUpgrade: yes or enter PromptUser --> WriteTimestampOnly: [nN] PromptUser --> ManualMsg: other WriteTimestampOnly: update_last_updated_file WriteTimestampOnly --> ManualMsg ManualMsg: echo manual update hint ManualMsg --> ExitHandle RunUpgrade --> ExitHandle ExitHandle --> [*] } state CheckUpdateAvailable { [*] --> ReadBranch ReadBranch: read configured branch default master ReadBranch --> ReadRemote ReadRemote: read configured remote default origin ReadRemote --> ReadRemoteUrl ReadRemoteUrl: read remote URL from git config ReadRemoteUrl --> ParseRemoteStyle ParseRemoteStyle --> AssumeUpdateYes: non-GitHub remote ParseRemoteStyle --> ValidateOfficialRepo: GitHub URL ValidateOfficialRepo --> AssumeUpdateYes: repo != ohmyzsh/ohmyzsh ValidateOfficialRepo --> LocalHeadCheck: repo == ohmyzsh/ohmyzsh LocalHeadCheck: resolve local branch HEAD hash LocalHeadCheck --> AssumeUpdateYes: fail LocalHeadCheck --> RemoteHeadFetch: success RemoteHeadFetch --> UseCurl: curl available RemoteHeadFetch --> UseWget: wget available RemoteHeadFetch --> UseFetch: fetch available RemoteHeadFetch --> AssumeUpdateNo: none available UseCurl: fetch remote head using curl UseWget: fetch remote head using wget UseFetch: fetch remote head using fetch utility UseCurl --> CompareHeads: success UseCurl --> AssumeUpdateNo: fail UseWget --> CompareHeads: success UseWget --> AssumeUpdateNo: fail UseFetch --> CompareHeads: success UseFetch --> AssumeUpdateNo: fail CompareHeads: compare local and remote head hashes CompareHeads --> MergeBaseCheck: TRUE CompareHeads --> AssumeUpdateNo: FALSE MergeBaseCheck: compute merge base from both hashes MergeBaseCheck --> AssumeUpdateYes: fail MergeBaseCheck --> EvaluateAncestry: success EvaluateAncestry: decide if local is behind remote EvaluateAncestry --> AssumeUpdateYes: TRUE EvaluateAncestry --> AssumeUpdateNo: FALSE AssumeUpdateYes --> [*] AssumeUpdateNo --> [*] } state RunUpgrade { [*] --> ResolveVerbose ResolveVerbose: resolve verbose mode from zstyle ResolveVerbose --> CheckP10kPrompt CheckP10kPrompt: check instant prompt setting CheckP10kPrompt --> ForceSilent: TRUE CheckP10kPrompt --> UpgradePath: FALSE ForceSilent: verbose_mode=silent ForceSilent --> UpgradePath UpgradePath --> InteractiveUpgrade: mode != background-alpha UpgradePath --> SilentCaptureUpgrade: mode=background-alpha InteractiveUpgrade: run upgrade script with interactive verbosity InteractiveUpgrade --> UpdateTimestampOnly: success InteractiveUpgrade --> SilentCaptureUpgrade: fail SilentCaptureUpgrade: run upgrade script and capture output SilentCaptureUpgrade --> UpdateSuccessStatus: success SilentCaptureUpgrade --> UpdateErrorStatus: fail UpdateTimestampOnly: update_last_updated_file UpdateSuccessStatus: write status file with success UpdateErrorStatus: write status file with captured error UpdateTimestampOnly --> [*] UpdateSuccessStatus --> [*] UpdateErrorStatus --> [*] } state BackgroundStatusHook { [*] --> RegisterStatusHook RegisterStatusHook: register precmd status hook RegisterStatusHook --> PollStatus PollStatus: poll status file on each precmd PollStatus --> WaitMore: status file not ready PollStatus --> PrintSuccess: EXIT_STATUS==0 PollStatus --> PrintFailure: EXIT_STATUS!=0 WaitMore: return 1 (continue polling) WaitMore --> [*] PrintSuccess: print success message PrintSuccess --> CleanupStatusHook PrintFailure: print error message with details PrintFailure --> CleanupStatusHook CleanupStatusHook: reset status file and deregister status hook CleanupStatusHook --> [*] } SetupBackgroundHooks --> BackgroundStatusHook ExitNoUpdate --> [*] ``` --- ## State Transition Table ### BOOTSTRAP Phase | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | START | — | — | CheckLegacyUpdateFile | | CheckLegacyUpdateFile | `[[ -f ~/.zsh-update && ! -f $ZSH_CACHE_DIR/.zsh-update ]]` | TRUE | MigrateLegacyFile | | CheckLegacyUpdateFile | — | FALSE (no legacy file) | LoadMode | | MigrateLegacyFile | — | `mv ~/.zsh-update $ZSH_CACHE_DIR/.zsh-update` | LoadMode | | LoadMode | `zstyle -s ':omz:update' mode update_mode` | SUCCESS | PreFlightGate | | LoadMode | — | FAIL (zstyle missing) | LegacyModeFallback | | LegacyModeFallback | — | `set update_mode=prompt` (default) | LegacyPromptFlag | | LegacyPromptFlag | `[[ $DISABLE_UPDATE_PROMPT != true ]]` | TRUE | LegacyAutoFlag | | LegacyPromptFlag | — | FALSE | SetAutoMode | | SetAutoMode | — | `update_mode=auto` | LegacyAutoFlag | | LegacyAutoFlag | `[[ $DISABLE_AUTO_UPDATE != true ]]` | TRUE | PreFlightGate | | LegacyAutoFlag | — | FALSE | SetDisabledMode | | SetDisabledMode | — | `update_mode=disabled` | PreFlightGate | --- ### PRE-FLIGHT GATE (Early Exit Checks) | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | PreFlightGate | `[[ $update_mode = disabled ]]` | TRUE | ExitNoUpdate | | PreFlightGate | `[[ ! -w $ZSH \|\| ! -O $ZSH ]]` | TRUE | ExitNoUpdate | | PreFlightGate | `[[ ! -t 1 && ${POWERLEVEL9K_INSTANT_PROMPT:-off} == off ]]` | TRUE | ExitNoUpdate | | PreFlightGate | `! command git --version 2>&1 >/dev/null` | TRUE | ExitNoUpdate | | PreFlightGate | `(cd $ZSH; ! git rev-parse --is-inside-work-tree &>/dev/null)` | TRUE | ExitNoUpdate | | PreFlightGate | — | ALL FALSE | ModeDispatch | | ExitNoUpdate | — | `unset update_mode; return` | [*] | --- ### MODE DISPATCH & BACKGROUND SETUP | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | ModeDispatch | `[[ $update_mode = background-alpha ]]` | TRUE | SetupBackgroundHooks | | ModeDispatch | — | FALSE | RunHandleUpdate | | SetupBackgroundHooks | — | `autoload -Uz add-zsh-hook` | RegisterBgPrecmd | | RegisterBgPrecmd | — | `add-zsh-hook precmd _omz_bg_update` | BackgroundStatusHook | | RunHandleUpdate | — | enter HandleUpdateCore | HandleUpdateCore | --- ### HANDLE UPDATE CORE (Main Logic) #### Lock Management | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | HandleUpdateCore | — | `zmodload zsh/datetime` | LockCleanupCheck | | LockCleanupCheck | `zstat +mtime $ZSH/log/update.lock 2>/dev/null` | SUCCESS (mtime exists) | CheckStaleAge | | LockCleanupCheck | — | FAIL (no lock file) | AcquireLock | | CheckStaleAge | `(mtime + 86400) < EPOCHSECONDS` | TRUE (older than 24h) | RemoveStaleLock | | CheckStaleAge | — | FALSE | AcquireLock | | RemoveStaleLock | — | `rm -rf $ZSH/log/update.lock` | AcquireLock | | AcquireLock | `mkdir $ZSH/log/update.lock 2>/dev/null` | SUCCESS | InstallTrap | | AcquireLock | — | FAIL (lock exists) | ExitHandle | | InstallTrap | — | `trap "...cleanup..." EXIT INT QUIT` | LoadStatusFile | #### Status File Validation | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | LoadStatusFile | `source $ZSH_CACHE_DIR/.zsh-update 2>/dev/null && [[ -n $LAST_EPOCH ]]` | SUCCESS | FrequencyCheck | | LoadStatusFile | — | FAIL or missing LAST_EPOCH | InitStatusFile | | InitStatusFile | — | `update_last_updated_file` (writes LAST_EPOCH) | ExitHandle | #### Frequency Check | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | FrequencyCheck | `zstyle -s ':omz:update' frequency epoch_target` | SUCCESS | PeriodElapsed | | FrequencyCheck | — | FAIL | SetDefaultFrequency | | SetDefaultFrequency | — | `epoch_target=${UPDATE_ZSH_DAYS:-13}` | PeriodElapsed | | PeriodElapsed | `(current_epoch - LAST_EPOCH) >= epoch_target` | TRUE | RepoCheck | | PeriodElapsed | — | FALSE | ExitHandle | #### Git Repository Check | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | RepoCheck | `(cd $ZSH && LANG= git rev-parse)` | SUCCESS | CheckUpdateAvailable | | RepoCheck | — | FAIL | ExitHandle | #### Post-Update Decision | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | CheckUpdateAvailable | — | function returns TRUE | ReminderOrTypedInput | | CheckUpdateAvailable | — | function returns FALSE | ExitHandleWithTimestamp | | ExitHandleWithTimestamp | — | `update_last_updated_file` | ExitHandle | --- ### REMINDER OR INPUT CHECK | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | ReminderOrTypedInput | `[[ $update_mode = reminder ]]` | TRUE | ReminderExit | | ReminderOrTypedInput | `[[ $update_mode != background-alpha ]]` | TRUE | TypedInputCheck | | ReminderOrTypedInput | — | FALSE (background-alpha) | ModeAutoGate | | TypedInputCheck | `has_typed_input` | TRUE | ReminderExit | | TypedInputCheck | — | FALSE | ModeAutoGate | | ReminderExit | — | `echo "[oh-my-zsh] It's time to update!..."` | ExitHandle | **has_typed_input internals:** - `stty --save` - `stty -icanon` - `zselect -t 0 -r 0` (poll stdin fd 0) - `stty $termios` (restore) --- ### UPDATE MODE GATE | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | ModeAutoGate | `[[ $update_mode = (auto\|background-alpha) ]]` | TRUE | RunUpgrade | | ModeAutoGate | — | FALSE (prompt) | PromptUser | #### Prompt Mode | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | PromptUser | — | `read -r -k 1 option` | ProcessResponse | | ProcessResponse | `[[ $option = [yY$'\n'] ]]` | TRUE | RunUpgrade | | ProcessResponse | `[[ $option = [nN] ]]` | TRUE | WriteTimestampOnly | | ProcessResponse | — | OTHER | ManualMsg | | WriteTimestampOnly | — | `update_last_updated_file` | ManualMsg | | ManualMsg | — | `echo "[oh-my-zsh] You can update manually..."` | ExitHandle | --- ### RUN UPGRADE SUBPROCESS | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | RunUpgrade | — | `zstyle -s ':omz:update' verbose verbose_mode` | ResolveVerbose | | ResolveVerbose | — | SET to `default` if missing | CheckP10kPrompt | | CheckP10kPrompt | `[[ ${POWERLEVEL9K_INSTANT_PROMPT:-off} != off ]]` | TRUE | ForceSilent | | CheckP10kPrompt | — | FALSE | UpgradePath | | ForceSilent | — | `verbose_mode=silent` | UpgradePath | | UpgradePath | `[[ $update_mode != background-alpha ]]` | TRUE | InteractiveUpgrade | | UpgradePath | — | FALSE | SilentCaptureUpgrade | #### Interactive Mode (TTY + user interaction) | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | InteractiveUpgrade | `LANG= ZSH=$ZSH zsh -f $ZSH/tools/upgrade.sh -i -v $verbose_mode` | SUCCESS (exit 0) | UpdateTimestampOnly | | InteractiveUpgrade | — | FAIL (exit >0) | SilentCaptureUpgrade | | UpdateTimestampOnly | — | `update_last_updated_file` | ExitHandle | #### Silent Mode (Background/Capture) | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | SilentCaptureUpgrade | `error=$(LANG= ZSH=$ZSH zsh -f $ZSH/tools/upgrade.sh -i -v silent 2>&1)` | SUCCESS | UpdateSuccessStatus | | SilentCaptureUpgrade | — | FAIL (nonzero exit) | UpdateErrorStatus | | UpdateSuccessStatus | — | `update_last_updated_file 0 "Update successful"` | ExitHandle | | UpdateErrorStatus | — | `update_last_updated_file $exit_status "$error"` | ExitHandle | --- ### CHECK UPDATE AVAILABLE (Nested Function) #### Configuration Retrieval | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | CheckUpdateAvailable | — | `cd $ZSH; git config --local oh-my-zsh.branch` | ReadRemote | | ReadRemote | — | `cd $ZSH; git config --local oh-my-zsh.remote` | ReadRemoteUrl | | ReadRemoteUrl | — | `cd $ZSH; git config remote.$remote.url` | ParseRemoteStyle | #### Remote Validation | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | ParseRemoteStyle | URL matches `https://github.com/*` or `git@github.com:*` | TRUE | ValidateOfficialRepo | | ParseRemoteStyle | — | FALSE (non-GitHub) | AssumeUpdateYes | | ValidateOfficialRepo | `[[ $repo = ohmyzsh/ohmyzsh ]]` | TRUE | LocalHeadCheck | | ValidateOfficialRepo | — | FALSE | AssumeUpdateYes | #### Local HEAD Retrieval | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | LocalHeadCheck | `cd $ZSH; git rev-parse $branch 2>/dev/null` | SUCCESS | RemoteHeadFetch | | LocalHeadCheck | — | FAIL | AssumeUpdateYes | #### Remote HEAD Fetch (Prefer curl > wget > fetch) | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | RemoteHeadFetch | `(( ${+commands[curl]} ))` | TRUE | UseCurl | | RemoteHeadFetch | `(( ${+commands[wget]} ))` | TRUE | UseWget | | RemoteHeadFetch | `(( ${+commands[fetch]} ))` | TRUE | UseFetch | | RemoteHeadFetch | — | NONE (no http tool) | AssumeUpdateNo | | UseCurl | `curl --connect-timeout 2 -fsSL -H 'Accept: application/vnd.github.v3.sha' $api_url 2>/dev/null` | SUCCESS | CompareHeads | | UseCurl | — | FAIL | AssumeUpdateNo | | UseWget | `wget -T 2 -O- --header='Accept: application/vnd.github.v3.sha' $api_url 2>/dev/null` | SUCCESS | CompareHeads | | UseWget | — | FAIL | AssumeUpdateNo | | UseFetch | `HTTP_ACCEPT='...' fetch -T 2 -o - $api_url 2>/dev/null` | SUCCESS | CompareHeads | | UseFetch | — | FAIL | AssumeUpdateNo | #### Head Comparison | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | CompareHeads | `[[ $local_head != $remote_head ]]` | TRUE | MergeBaseCheck | | CompareHeads | — | FALSE (equal) | AssumeUpdateNo | #### Ancestry Check | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | MergeBaseCheck | `cd $ZSH; git merge-base $local_head $remote_head 2>/dev/null` | SUCCESS | EvaluateAncestry | | MergeBaseCheck | — | FAIL | AssumeUpdateYes | | EvaluateAncestry | `[[ $base != $remote_head ]]` | TRUE | AssumeUpdateYes | | EvaluateAncestry | — | FALSE | AssumeUpdateNo | #### Results | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | AssumeUpdateYes | — | `return 0` (update available) | return from function | | AssumeUpdateNo | — | `return 1` (no update) | return from function | --- ### BACKGROUND UPDATE STATUS HOOK | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | BackgroundStatusHook | — | register precmd → `_omz_bg_update_status` | PollStatus | | PollStatus | `[[ ! -f $ZSH_CACHE_DIR/.zsh-update ]]` | TRUE | WaitMore | | PollStatus | `source $ZSH_CACHE_DIR/.zsh-update` | SUCCESS + `[[ -z $EXIT_STATUS \|\| -z $ERROR ]]` | WaitMore | | PollStatus | `[[ $EXIT_STATUS -eq 0 ]]` | TRUE | PrintSuccess | | PollStatus | `[[ $EXIT_STATUS -ne 0 ]]` | TRUE | PrintFailure | | WaitMore | — | `return 1` (continue polling on next precmd) | PollStatus (next precmd) | | PrintSuccess | — | `print -P "\n%F{green}[oh-my-zsh] Update successful.%f"` | CleanupStatusHook | | PrintFailure | — | `print -P "\n%F{red}[oh-my-zsh] There was an error updating:%f"; printf "${ERROR}"` | CleanupStatusHook | | CleanupStatusHook | `(( TRY_BLOCK_ERROR == 0 ))` | TRUE | DeregisterHook | | DeregisterHook | — | `update_last_updated_file` (reset status file) | DeregisterStatusFunc | | DeregisterStatusFunc | — | `add-zsh-hook -d precmd _omz_bg_update_status` | [*] | --- ### EXIT & CLEANUP | State | Condition | Command/Check | Next State | |-------|-----------|---|---| | ExitHandle | — | trap fires (EXIT/INT/QUIT) → `rm -rf $ZSH/log/update.lock` | TrapExit | | TrapExit | — | `unset update_mode` | TrapExit2 | | TrapExit | — | `unset -f current_epoch is_update_available ...` | TrapExit2 | | TrapExit2 | — | `return $ret` | [*] | --- ## Key Functions (Always Available) ### current_epoch() ```zsh zmodload zsh/datetime echo $(( EPOCHSECONDS / 60 / 60 / 24 )) ``` Returns the current day count since epoch. ### is_update_available() Returns 0 (update available) or 1 (no update). See "CHECK UPDATE AVAILABLE" section above. ### update_last_updated_file() - Called with no args → writes `LAST_EPOCH=$(current_epoch)` - Called with `exit_status` and `error` → writes status + error msg ### update_ohmyzsh() Calls `upgrade.sh` subprocesses (interactive or silent capture). Returns exit code of upgrade operation. ### has_typed_input() Polls stdin with stty/zselect. Returns 0 if input detected, 1 if not. --- ## Summary Statistics - **Total States (conceptual):** 60+ - **Total Transitions:** 80+ - **Early Exit Points (PreFlightGate):** 5 conditions - **Update Decision Points:** 3 (mode, frequency, availability) - **User Prompt Paths:** 2 (prompt mode vs. auto) - **Background Poller States:** 4 - **Nested Function Depth:** 2 (handle_update → is_update_available)