浏览代码

feat(updater): add `background-alpha` update mode (preview) (#11928)

NOTE: this feature is in alpha / preview mode, it is not guaranteed to work 100% of the
time in all cases. If you experience any issues, open an issue or search for an open
one describing your same situation.

To use this, use the zstyle update mode settings [1] with the value `background-alpha`:

  zstyle ':omz:update' mode background-alpha

[1] https://github.com/ohmyzsh/ohmyzsh#getting-updates
Marc Cornellà 7 月之前
父节点
当前提交
29b99c2c7b
共有 1 个文件被更改,包括 156 次插入74 次删除
  1. 156 74
      tools/check_for_upgrade.sh

+ 156 - 74
tools/check_for_upgrade.sh

@@ -9,6 +9,7 @@ fi
 # - prompt (default): the user is asked before updating when it's time to update
 # - auto: the update is performed automatically when it's time
 # - reminder: a reminder is shown to the user when it's time to update
+# - background-alpha: an experimental update-on-the-background option
 # - disabled: automatic update is turned off
 zstyle -s ':omz:update' mode update_mode || {
   update_mode=prompt
@@ -91,13 +92,37 @@ function is_update_available() {
 }
 
 function update_last_updated_file() {
-  echo "LAST_EPOCH=$(current_epoch)" >! "${ZSH_CACHE_DIR}/.zsh-update"
+  local exit_status="$1" error="$2"
+
+  if [[ -z "${1}${2}" ]]; then
+    echo "LAST_EPOCH=$(current_epoch)" >! "${ZSH_CACHE_DIR}/.zsh-update"
+    return
+  fi
+
+  cat >! "${ZSH_CACHE_DIR}/.zsh-update" <<EOD
+LAST_EPOCH=$(current_epoch)
+EXIT_STATUS=${exit_status}
+ERROR='${error//\'/’}'
+EOD
 }
 
 function update_ohmyzsh() {
+  local verbose_mode
   zstyle -s ':omz:update' verbose verbose_mode || verbose_mode=default
-  if ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" -i -v $verbose_mode; then
+
+  if [[ "$update_mode" != background-alpha ]] \
+    && LANG= ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" -i -v $verbose_mode; then
     update_last_updated_file
+    return $?
+  fi
+
+  local exit_status error
+  if error=$(LANG= ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" -i -v silent 2>&1); then
+    update_last_updated_file 0 "Update successful"
+  else
+    exit_status=$?
+    update_last_updated_file $exit_status "$error"
+    return $exit_status
   fi
 }
 
@@ -126,88 +151,145 @@ function has_typed_input() {
   }
 }
 
-() {
-  emulate -L zsh
+function handle_update() {
+  () {
+    emulate -L zsh
 
-  local epoch_target mtime option LAST_EPOCH
+    local epoch_target mtime option LAST_EPOCH
 
-  # Remove lock directory if older than a day
-  zmodload zsh/datetime
-  zmodload -F zsh/stat b:zstat
-  if mtime=$(zstat +mtime "$ZSH/log/update.lock" 2>/dev/null); then
-    if (( (mtime + 3600 * 24) < EPOCHSECONDS )); then
-      command rm -rf "$ZSH/log/update.lock"
+    # Remove lock directory if older than a day
+    zmodload zsh/datetime
+    zmodload -F zsh/stat b:zstat
+    if mtime=$(zstat +mtime "$ZSH/log/update.lock" 2>/dev/null); then
+      if (( (mtime + 3600 * 24) < EPOCHSECONDS )); then
+        command rm -rf "$ZSH/log/update.lock"
+      fi
     fi
-  fi
 
-  # Check for lock directory
-  if ! command mkdir "$ZSH/log/update.lock" 2>/dev/null; then
-    return
-  fi
+    # Check for lock directory
+    if ! command mkdir "$ZSH/log/update.lock" 2>/dev/null; then
+      return
+    fi
 
-  # Remove lock directory on exit. `return $ret` is important for when trapping a SIGINT:
-  #  The return status from the function is handled specially. If it is zero, the signal is
-  #  assumed to have been handled, and execution continues normally. Otherwise, the shell
-  #  will behave as interrupted except that the return status of the trap is retained.
-  #  This means that for a CTRL+C, the trap needs to return the same exit status so that
-  #  the shell actually exits what it's running.
-  trap "
-    ret=\$?
-    unset update_mode
-    unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh 2>/dev/null
-    command rm -rf '$ZSH/log/update.lock'
-    return \$ret
-  " EXIT INT QUIT
-
-  # Create or update .zsh-update file if missing or malformed
-  if ! source "${ZSH_CACHE_DIR}/.zsh-update" 2>/dev/null || [[ -z "$LAST_EPOCH" ]]; then
-    update_last_updated_file
-    return
-  fi
+    # Remove lock directory on exit. `return $ret` is important for when trapping a SIGINT:
+    #  The return status from the function is handled specially. If it is zero, the signal is
+    #  assumed to have been handled, and execution continues normally. Otherwise, the shell
+    #  will behave as interrupted except that the return status of the trap is retained.
+    #  This means that for a CTRL+C, the trap needs to return the same exit status so that
+    #  the shell actually exits what it's running.
+    trap "
+      ret=\$?
+      unset update_mode
+      unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh handle_update 2>/dev/null
+      command rm -rf '$ZSH/log/update.lock'
+      return \$ret
+    " EXIT INT QUIT
 
-  # Number of days before trying to update again
-  zstyle -s ':omz:update' frequency epoch_target || epoch_target=${UPDATE_ZSH_DAYS:-13}
-  # Test if enough time has passed until the next update
-  if (( ( $(current_epoch) - $LAST_EPOCH ) < $epoch_target )); then
-    return
-  fi
+    # Create or update .zsh-update file if missing or malformed
+    if ! source "${ZSH_CACHE_DIR}/.zsh-update" 2>/dev/null || [[ -z "$LAST_EPOCH" ]]; then
+      update_last_updated_file
+      return
+    fi
 
-  # Test if Oh My Zsh directory is a git repository
-  if ! (builtin cd -q "$ZSH" && LANG= git rev-parse &>/dev/null); then
-    echo >&2 "[oh-my-zsh] Can't update: not a git repository."
-    return
-  fi
+    # Number of days before trying to update again
+    zstyle -s ':omz:update' frequency epoch_target || epoch_target=${UPDATE_ZSH_DAYS:-13}
+    # Test if enough time has passed until the next update
+    if (( ( $(current_epoch) - $LAST_EPOCH ) < $epoch_target )); then
+      return
+    fi
 
-  # Check if there are updates available before proceeding
-  if ! is_update_available; then
-    update_last_updated_file
-    return
-  fi
+    # Test if Oh My Zsh directory is a git repository
+    if ! (builtin cd -q "$ZSH" && LANG= git rev-parse &>/dev/null); then
+      echo >&2 "[oh-my-zsh] Can't update: not a git repository."
+      return
+    fi
 
-  # If in reminder mode or user has typed input, show reminder and exit
-  if [[ "$update_mode" = reminder ]] || has_typed_input; then
-    printf '\r\e[0K' # move cursor to first column and clear whole line
-    echo "[oh-my-zsh] It's time to update! You can do that by running \`omz update\`"
-    return 0
-  fi
+    # Check if there are updates available before proceeding
+    if ! is_update_available; then
+      update_last_updated_file
+      return
+    fi
 
-  # Don't ask for confirmation before updating if in auto mode
-  if [[ "$update_mode" = auto ]]; then
-    update_ohmyzsh
-    return $?
-  fi
+    # If in reminder mode or user has typed input, show reminder and exit
+    if [[ "$update_mode" = reminder ]] || { [[ "$update_mode" != background-alpha ]] && has_typed_input }; then
+      printf '\r\e[0K' # move cursor to first column and clear whole line
+      echo "[oh-my-zsh] It's time to update! You can do that by running \`omz update\`"
+      return 0
+    fi
 
-  # Ask for confirmation and only update on 'y', 'Y' or Enter
-  # Otherwise just show a reminder for how to update
-  echo -n "[oh-my-zsh] Would you like to update? [Y/n] "
-  read -r -k 1 option
-  [[ "$option" = $'\n' ]] || echo
-  case "$option" in
-    [yY$'\n']) update_ohmyzsh ;;
-    [nN]) update_last_updated_file ;&
-    *) echo "[oh-my-zsh] You can update manually by running \`omz update\`" ;;
-  esac
+    # Don't ask for confirmation before updating if in auto mode
+    if [[ "$update_mode" = (auto|background-alpha) ]]; then
+      update_ohmyzsh
+      return $?
+    fi
+
+    # Ask for confirmation and only update on 'y', 'Y' or Enter
+    # Otherwise just show a reminder for how to update
+    echo -n "[oh-my-zsh] Would you like to update? [Y/n] "
+    read -r -k 1 option
+    [[ "$option" = $'\n' ]] || echo
+    case "$option" in
+      [yY$'\n']) update_ohmyzsh ;;
+      [nN]) update_last_updated_file ;&
+      *) echo "[oh-my-zsh] You can update manually by running \`omz update\`" ;;
+    esac
+  }
+
+  unset update_mode
+  unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh handle_update
 }
 
-unset update_mode
-unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh
+case "$update_mode" in
+  background-alpha)
+    autoload -Uz add-zsh-hook
+
+    _omz_bg_update() {
+      # do the update in a subshell
+      (handle_update) &|
+
+      # register update results function
+      add-zsh-hook precmd _omz_bg_update_status
+
+      # deregister background function
+      add-zsh-hook -d precmd _omz_bg_update
+      unset -f _omz_bg_update
+    }
+
+    _omz_bg_update_status() {
+      {
+        local LAST_EPOCH EXIT_STATUS ERROR
+        if [[ ! -f "$ZSH_CACHE_DIR"/.zsh-update ]]; then
+          return 1
+        fi
+
+        # check update results until timeout is reached
+        . "$ZSH_CACHE_DIR/.zsh-update"
+        if [[ -z "$EXIT_STATUS" || -z "$ERROR" ]]; then
+          return 1
+        fi
+
+        if [[ "$EXIT_STATUS" -eq 0 ]]; then
+          print -P "\n%F{green}[oh-my-zsh] Update successful.%f"
+          return 0
+        elif [[ "$EXIT_STATUS" -ne 0 ]]; then
+          print -P "\n%F{red}[oh-my-zsh] There was an error updating:%f"
+          printf "\n${fg[yellow]}%s${reset_color}" "$ERROR"
+          return 0
+        fi
+      } always {
+        if (( TRY_BLOCK_ERROR == 0 )); then
+          # if last update results have been handled, remove them from the status file
+          update_last_updated_file
+
+          # deregister background function
+          add-zsh-hook -d precmd _omz_bg_update_status
+          unset -f _omz_bg_update_status
+        fi
+      }
+    }
+
+    add-zsh-hook precmd _omz_bg_update
+    ;;
+  *)
+    handle_update ;;
+esac