]> src.twobees.de Git - dotfiles.git/blob - stow/oh-my-zsh/.oh-my-zsh/tools/check_for_upgrade.sh
cc527e72dcac8c61398a20e589f0f62ecfcd9ee2
[dotfiles.git] / stow / oh-my-zsh / .oh-my-zsh / tools / check_for_upgrade.sh
1 # Migrate .zsh-update file to $ZSH_CACHE_DIR
2 if [[ -f ~/.zsh-update && ! -f "${ZSH_CACHE_DIR}/.zsh-update" ]]; then
3   mv ~/.zsh-update "${ZSH_CACHE_DIR}/.zsh-update"
4 fi
5
6 # Get user's update preferences
7 #
8 # Supported update modes:
9 # - prompt (default): the user is asked before updating when it's time to update
10 # - auto: the update is performed automatically when it's time
11 # - reminder: a reminder is shown to the user when it's time to update
12 # - disabled: automatic update is turned off
13 zstyle -s ':omz:update' mode update_mode || {
14   update_mode=prompt
15
16   # If the mode zstyle setting is not set, support old-style settings
17   [[ "$DISABLE_UPDATE_PROMPT" != true ]] || update_mode=auto
18   [[ "$DISABLE_AUTO_UPDATE" != true ]] || update_mode=disabled
19 }
20
21 # Cancel update if:
22 # - the automatic update is disabled.
23 # - the current user doesn't have write permissions nor owns the $ZSH directory.
24 # - git is unavailable on the system.
25 if [[ "$update_mode" = disabled ]] \
26    || [[ ! -w "$ZSH" || ! -O "$ZSH" ]] \
27    || ! command -v git &>/dev/null; then
28   unset update_mode
29   return
30 fi
31
32 function current_epoch() {
33   zmodload zsh/datetime
34   echo $(( EPOCHSECONDS / 60 / 60 / 24 ))
35 }
36
37 function is_update_available() {
38   local branch
39   branch=${"$(builtin cd -q "$ZSH"; git config --local oh-my-zsh.branch)":-master}
40
41   local remote remote_url remote_repo
42   remote=${"$(builtin cd -q "$ZSH"; git config --local oh-my-zsh.remote)":-origin}
43   remote_url=$(builtin cd -q "$ZSH"; git config remote.$remote.url)
44
45   local repo
46   case "$remote_url" in
47   https://github.com/*) repo=${${remote_url#https://github.com/}%.git} ;;
48   git@github.com:*) repo=${${remote_url#git@github.com:}%.git} ;;
49   *)
50     # If the remote is not using GitHub we can't check for updates
51     # Let's assume there are updates
52     return 0 ;;
53   esac
54
55   # If the remote repo is not the official one, let's assume there are updates available
56   [[ "$repo" = ohmyzsh/ohmyzsh ]] || return 0
57   local api_url="https://api.github.com/repos/${repo}/commits/${branch}"
58
59   # Get local HEAD. If this fails assume there are updates
60   local local_head
61   local_head=$(builtin cd -q "$ZSH"; git rev-parse $branch 2>/dev/null) || return 0
62
63   # Get remote HEAD. If no suitable command is found assume there are updates
64   # On any other error, skip the update (connection may be down)
65   local remote_head
66   remote_head=$(
67     if (( ${+commands[curl]} )); then
68       curl --connect-timeout 2 -fsSL -H 'Accept: application/vnd.github.v3.sha' $api_url 2>/dev/null
69     elif (( ${+commands[wget]} )); then
70       wget -T 2 -O- --header='Accept: application/vnd.github.v3.sha' $api_url 2>/dev/null
71     elif (( ${+commands[fetch]} )); then
72       HTTP_ACCEPT='Accept: application/vnd.github.v3.sha' fetch -T 2 -o - $api_url 2>/dev/null
73     else
74       exit 0
75     fi
76   ) || return 1
77
78   # Compare local and remote HEADs (if they're equal there are no updates)
79   [[ "$local_head" != "$remote_head" ]] || return 1
80
81   # If local and remote HEADs don't match, check if there's a common ancestor
82   # If the merge-base call fails, $remote_head might not be downloaded so assume there are updates
83   local base
84   base=$(builtin cd -q "$ZSH"; git merge-base $local_head $remote_head 2>/dev/null) || return 0
85
86   # If the common ancestor ($base) is not $remote_head,
87   # the local HEAD is older than the remote HEAD
88   [[ $base != $remote_head ]]
89 }
90
91 function update_last_updated_file() {
92   echo "LAST_EPOCH=$(current_epoch)" >! "${ZSH_CACHE_DIR}/.zsh-update"
93 }
94
95 function update_ohmyzsh() {
96   if ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" --interactive; then
97     update_last_updated_file
98   fi
99 }
100
101 function has_typed_input() {
102   # Created by Philippe Troin <phil@fifi.org>
103   # https://zsh.org/mla/users/2022/msg00062.html
104   emulate -L zsh
105   zmodload zsh/zselect
106
107   # Back up stty settings prior to disabling canonical mode
108   # Consider that no input can be typed if stty fails
109   # (this might happen if stdin is not a terminal)
110   local termios
111   termios=$(stty --save 2>/dev/null) || return 1
112   {
113     # Disable canonical mode so that typed input counts
114     # regardless of whether Enter was pressed
115     stty -icanon
116
117     # Poll stdin (fd 0) for data ready to be read
118     zselect -t 0 -r 0
119     return $?
120   } always {
121     # Restore stty settings
122     stty $termios
123   }
124 }
125
126 () {
127   emulate -L zsh
128
129   local epoch_target mtime option LAST_EPOCH
130
131   # Remove lock directory if older than a day
132   zmodload zsh/datetime
133   zmodload -F zsh/stat b:zstat
134   if mtime=$(zstat +mtime "$ZSH/log/update.lock" 2>/dev/null); then
135     if (( (mtime + 3600 * 24) < EPOCHSECONDS )); then
136       command rm -rf "$ZSH/log/update.lock"
137     fi
138   fi
139
140   # Check for lock directory
141   if ! command mkdir "$ZSH/log/update.lock" 2>/dev/null; then
142     return
143   fi
144
145   # Remove lock directory on exit. `return $ret` is important for when trapping a SIGINT:
146   #  The return status from the function is handled specially. If it is zero, the signal is
147   #  assumed to have been handled, and execution continues normally. Otherwise, the shell
148   #  will behave as interrupted except that the return status of the trap is retained.
149   #  This means that for a CTRL+C, the trap needs to return the same exit status so that
150   #  the shell actually exits what it's running.
151   trap "
152     ret=\$?
153     unset update_mode
154     unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh 2>/dev/null
155     command rm -rf '$ZSH/log/update.lock'
156     return \$ret
157   " EXIT INT QUIT
158
159   # Create or update .zsh-update file if missing or malformed
160   if ! source "${ZSH_CACHE_DIR}/.zsh-update" 2>/dev/null || [[ -z "$LAST_EPOCH" ]]; then
161     update_last_updated_file
162     return
163   fi
164
165   # Number of days before trying to update again
166   zstyle -s ':omz:update' frequency epoch_target || epoch_target=${UPDATE_ZSH_DAYS:-13}
167   # Test if enough time has passed until the next update
168   if (( ( $(current_epoch) - $LAST_EPOCH ) < $epoch_target )); then
169     return
170   fi
171
172   # Test if Oh My Zsh directory is a git repository
173   if ! (builtin cd -q "$ZSH" && LANG= git rev-parse &>/dev/null); then
174     echo >&2 "[oh-my-zsh] Can't update: not a git repository."
175     return
176   fi
177
178   # Check if there are updates available before proceeding
179   if ! is_update_available; then
180     return
181   fi
182
183   # If in reminder mode or user has typed input, show reminder and exit
184   if [[ "$update_mode" = reminder ]] || has_typed_input; then
185     printf '\r\e[0K' # move cursor to first column and clear whole line
186     echo "[oh-my-zsh] It's time to update! You can do that by running \`omz update\`"
187     return 0
188   fi
189
190   # Don't ask for confirmation before updating if in auto mode
191   if [[ "$update_mode" = auto ]]; then
192     update_ohmyzsh
193     return $?
194   fi
195
196   # Ask for confirmation and only update on 'y', 'Y' or Enter
197   # Otherwise just show a reminder for how to update
198   echo -n "[oh-my-zsh] Would you like to update? [Y/n] "
199   read -r -k 1 option
200   [[ "$option" = $'\n' ]] || echo
201   case "$option" in
202     [yY$'\n']) update_ohmyzsh ;;
203     [nN]) update_last_updated_file ;&
204     *) echo "[oh-my-zsh] You can update manually by running \`omz update\`" ;;
205   esac
206 }
207
208 unset update_mode
209 unset -f current_epoch is_update_available update_last_updated_file update_ohmyzsh