From 2138e2662b600521be94b1bc91514e8dfd1399fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Thu, 26 Mar 2026 19:37:04 +0100 Subject: [PATCH] wip: started working on planning --- tools/UPDATE_PROCESS.md | 517 +++++++++++++++++++++++++++++++++++++ tools/check_for_upgrade.sh | 3 + tools/upgrade.sh | 35 +++ 3 files changed, 555 insertions(+) create mode 100644 tools/UPDATE_PROCESS.md diff --git a/tools/UPDATE_PROCESS.md b/tools/UPDATE_PROCESS.md new file mode 100644 index 000000000..6f9d33395 --- /dev/null +++ b/tools/UPDATE_PROCESS.md @@ -0,0 +1,517 @@ +# 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) diff --git a/tools/check_for_upgrade.sh b/tools/check_for_upgrade.sh index 44dbb7b31..3e41a2335 100644 --- a/tools/check_for_upgrade.sh +++ b/tools/check_for_upgrade.sh @@ -39,6 +39,7 @@ function current_epoch() { echo $(( EPOCHSECONDS / 60 / 60 / 24 )) } +# TODO: change this to support stable releases function is_update_available() { local branch branch=${"$(builtin cd -q "$ZSH"; git config --local oh-my-zsh.branch)":-master} @@ -211,6 +212,8 @@ function handle_update() { return fi + # TODO: somewhere like here we should support fast-tracking of security patches + # Check if there are updates available before proceeding if ! is_update_available; then update_last_updated_file diff --git a/tools/upgrade.sh b/tools/upgrade.sh index 01719d217..dfb9151b9 100755 --- a/tools/upgrade.sh +++ b/tools/upgrade.sh @@ -191,6 +191,8 @@ if is_tty; then RESET=$(printf '\033[0m') fi +## START Migrations + # Update upstream remote to ohmyzsh org git remote -v | while read remote url extra; do case "$url" in @@ -213,6 +215,14 @@ git remote -v | while read remote url extra; do break done +# Set default release mode +# We should really move away from `.branch`, and keep it only for forks +# then we can set `.release` and `.remote` as the right ones for us +# Release options: master, release/YY0M, vYY0M.x.y +if [[ -z "$(git config --local oh-my-zsh.release)" ]]; then + git config --local oh-my-zsh.release "master" +fi + # Set git-config values known to fix git errors # Line endings (#4069) git config core.eol lf @@ -225,11 +235,14 @@ git config receive.fsck.zeroPaddedFilemode ignore resetAutoStash=$(git config --bool rebase.autoStash 2>/dev/null) git config rebase.autoStash true +## END migrations + local ret=0 # repository settings remote=${"$(git config --local oh-my-zsh.remote)":-origin} branch=${"$(git config --local oh-my-zsh.branch)":-master} +release=${"$(git config --local oh-my-zsh.release)":-master} # repository state last_head=$(git symbolic-ref --quiet --short HEAD || git rev-parse HEAD) @@ -242,6 +255,28 @@ last_commit=$(git rev-parse "$branch") if [[ $verbose_mode != silent ]]; then printf "${BLUE}%s${RESET}\n" "Updating Oh My Zsh" fi + +# master -> git pull +# release/YY0M -> git pull origin release/YY0M +# vYY0M.x.y -> only update if release/ branch of the tag has a security update +case "$release" in + master|release/*) git pull --quiet --rebase "$remote" "$release" ;; + v*) ;; # do nothing, but we should really make sure that security patches are fast-tracked: if the "$release" tag is in branch release/YY0M, we should pull that branch and then check out the latest tag, and inform the user that we upgraded their release + *) echo "error: unknown release '$release'" && exit 1 ;; # here we should show how to fix the issue +esac + +# test: are we updating on the main remote or from a fork? +# if main remote then +# case "$release" in +# master|release/*) git pull --quiet --rebase "$remote" "$release" ;; +# release/*) git pull --quiet --rebase "$remote" "$release" ;; +# v*) ;; # nothing +# *) echo "error: unknown release '$release'" && exit 1 ;; +# esac +# fi + +# if master or release/* do just git pull $remote $branch + if LANG= git pull --quiet --rebase $remote $branch; then # Check if it was really updated or not if [[ "$(git rev-parse HEAD)" = "$last_commit" ]]; then