diff options
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/changelog.sh | 431 | ||||
-rw-r--r-- | tools/check_for_upgrade.sh | 121 | ||||
-rwxr-xr-x | tools/install.sh | 379 | ||||
-rwxr-xr-x | tools/theme_chooser.sh | 1 | ||||
-rw-r--r-- | tools/uninstall.sh | 38 | ||||
-rwxr-xr-x[-rw-r--r--] | tools/upgrade.sh | 133 |
6 files changed, 924 insertions, 179 deletions
diff --git a/tools/changelog.sh b/tools/changelog.sh new file mode 100755 index 000000000..836afef14 --- /dev/null +++ b/tools/changelog.sh @@ -0,0 +1,431 @@ +#!/usr/bin/env zsh + +############################## +# CHANGELOG SCRIPT CONSTANTS # +############################## + +#* Holds the list of valid types recognized in a commit subject +#* and the display string of such type +local -A TYPES +TYPES=( + build "Build system" + chore "Chore" + ci "CI" + docs "Documentation" + feat "Features" + fix "Bug fixes" + perf "Performance" + refactor "Refactor" + style "Style" + test "Testing" +) + +#* Types that will be displayed in their own section, +#* in the order specified here. +local -a MAIN_TYPES +MAIN_TYPES=(feat fix perf docs) + +#* Types that will be displayed under the category of other changes +local -a OTHER_TYPES +OTHER_TYPES=(refactor style other) + +#* Commit types that don't appear in $MAIN_TYPES nor $OTHER_TYPES +#* will not be displayed and will simply be ignored. + + +############################ +# COMMIT PARSING UTILITIES # +############################ + +function parse-commit { + + # This function uses the following globals as output: commits (A), + # subjects (A), scopes (A) and breaking (A). All associative arrays (A) + # have $hash as the key. + # - commits holds the commit type + # - subjects holds the commit subject + # - scopes holds the scope of a commit + # - breaking holds the breaking change warning if a commit does + # make a breaking change + + function commit:type { + local type="$(sed -E 's/^([a-zA-Z_\-]+)(\(.+\))?!?: .+$/\1/' <<< "$1")" + + # If $type doesn't appear in $TYPES array mark it as 'other' + if [[ -n "${(k)TYPES[(i)$type]}" ]]; then + echo $type + else + echo other + fi + } + + function commit:scope { + local scope + + # Try to find scope in "type(<scope>):" format + scope=$(sed -nE 's/^[a-zA-Z_\-]+\((.+)\)!?: .+$/\1/p' <<< "$1") + if [[ -n "$scope" ]]; then + echo "$scope" + return + fi + + # If no scope found, try to find it in "<scope>:" format + # Make sure it's not a type before printing it + scope=$(sed -nE 's/^([a-zA-Z_\-]+): .+$/\1/p' <<< "$1") + if [[ -z "${(k)TYPES[(i)$scope]}" ]]; then + echo "$scope" + fi + } + + function commit:subject { + # Only display the relevant part of the commit, i.e. if it has the format + # type[(scope)!]: subject, where the part between [] is optional, only + # displays subject. If it doesn't match the format, returns the whole string. + sed -E 's/^[a-zA-Z_\-]+(\(.+\))?!?: (.+)$/\2/' <<< "$1" + } + + # Return subject if the body or subject match the breaking change format + function commit:is-breaking { + local subject="$1" body="$2" message + + if [[ "$body" =~ "BREAKING CHANGE: (.*)" || \ + "$subject" =~ '^[^ :\)]+\)?!: (.*)$' ]]; then + message="${match[1]}" + # remove CR characters (might be inserted in GitHub UI commit description form) + message="${message//$'\r'/}" + # skip next paragraphs (separated by two newlines or more) + message="${message%%$'\n\n'*}" + # ... and replace newlines with spaces + echo "${message//$'\n'/ }" + else + return 1 + fi + } + + # Return truncated hash of the reverted commit + function commit:is-revert { + local subject="$1" body="$2" + + if [[ "$subject" = Revert* && \ + "$body" =~ "This reverts commit ([^.]+)\." ]]; then + echo "${match[1]:0:7}" + else + return 1 + fi + } + + # Parse commit with hash $1 + local hash="$1" subject body warning rhash + subject="$(command git show -s --format=%s $hash)" + body="$(command git show -s --format=%b $hash)" + + # Commits following Conventional Commits (https://www.conventionalcommits.org/) + # have the following format, where parts between [] are optional: + # + # type[(scope)][!]: subject + # + # commit body + # [BREAKING CHANGE: warning] + + # commits holds the commit type + commits[$hash]="$(commit:type "$subject")" + # scopes holds the commit scope + scopes[$hash]="$(commit:scope "$subject")" + # subjects holds the commit subject + subjects[$hash]="$(commit:subject "$subject")" + + # breaking holds whether a commit has breaking changes + # and its warning message if it does + if warning=$(commit:is-breaking "$subject" "$body"); then + breaking[$hash]="$warning" + fi + + # reverts holds commits reverted in the same release + if rhash=$(commit:is-revert "$subject" "$body"); then + reverts[$hash]=$rhash + fi +} + +############################# +# RELEASE CHANGELOG DISPLAY # +############################# + +function display-release { + + # This function uses the following globals: output, version, + # commits (A), subjects (A), scopes (A), breaking (A) and reverts (A). + # + # - output is the output format to use when formatting (raw|text|md) + # - version is the version in which the commits are made + # - commits, subjects, scopes, breaking, and reverts are associative arrays + # with commit hashes as keys + + # Remove commits that were reverted + local hash rhash + for hash rhash in ${(kv)reverts}; do + if (( ${+commits[$rhash]} )); then + # Remove revert commit + unset "commits[$hash]" "subjects[$hash]" "scopes[$hash]" "breaking[$hash]" + # Remove reverted commit + unset "commits[$rhash]" "subjects[$rhash]" "scopes[$rhash]" "breaking[$rhash]" + fi + done + + # If no commits left skip displaying the release + if (( $#commits == 0 )); then + return + fi + + ##* Formatting functions + + # Format the hash according to output format + # If no parameter is passed, assume it comes from `$hash` + function fmt:hash { + #* Uses $hash from outer scope + local hash="${1:-$hash}" + case "$output" in + raw) printf "$hash" ;; + text) printf "\e[33m$hash\e[0m" ;; # red + md) printf "[\`$hash\`](https://github.com/ohmyzsh/ohmyzsh/commit/$hash)" ;; + esac + } + + # Format headers according to output format + # Levels 1 to 2 are considered special, the rest are formatted + # the same, except in md output format. + function fmt:header { + local header="$1" level="$2" + case "$output" in + raw) + case "$level" in + 1) printf "$header\n$(printf '%.0s=' {1..${#header}})\n\n" ;; + 2) printf "$header\n$(printf '%.0s-' {1..${#header}})\n\n" ;; + *) printf "$header:\n\n" ;; + esac ;; + text) + case "$level" in + 1|2) printf "\e[1;4m$header\e[0m\n\n" ;; # bold, underlined + *) printf "\e[1m$header:\e[0m\n\n" ;; # bold + esac ;; + md) printf "$(printf '%.0s#' {1..${level}}) $header\n\n" ;; + esac + } + + function fmt:scope { + #* Uses $scopes (A) and $hash from outer scope + local scope="${1:-${scopes[$hash]}}" + + # Get length of longest scope for padding + local max_scope=0 padding=0 + for hash in ${(k)scopes}; do + max_scope=$(( max_scope < ${#scopes[$hash]} ? ${#scopes[$hash]} : max_scope )) + done + + # If no scopes, exit the function + if [[ $max_scope -eq 0 ]]; then + return + fi + + # Get how much padding is required for this scope + padding=$(( max_scope < ${#scope} ? 0 : max_scope - ${#scope} )) + padding="${(r:$padding:: :):-}" + + # If no scope, print padding and 3 spaces (equivalent to "[] ") + if [[ -z "$scope" ]]; then + printf "${padding} " + return + fi + + # Print [scope] + case "$output" in + raw|md) printf "[$scope]${padding} " ;; + text) printf "[\e[38;5;9m$scope\e[0m]${padding} " ;; # red 9 + esac + } + + # If no parameter is passed, assume it comes from `$subjects[$hash]` + function fmt:subject { + #* Uses $subjects (A) and $hash from outer scope + local subject="${1:-${subjects[$hash]}}" + + # Capitalize first letter of the subject + subject="${(U)subject:0:1}${subject:1}" + + case "$output" in + raw) printf "$subject" ;; + # In text mode, highlight (#<issue>) and dim text between `backticks` + text) sed -E $'s|#([0-9]+)|\e[32m#\\1\e[0m|g;s|`([^`]+)`|`\e[2m\\1\e[0m`|g' <<< "$subject" ;; + # In markdown mode, link to (#<issue>) issues + md) sed -E 's|#([0-9]+)|[#\1](https://github.com/ohmyzsh/ohmyzsh/issues/\1)|g' <<< "$subject" ;; + esac + } + + function fmt:type { + #* Uses $type from outer scope + local type="${1:-${TYPES[$type]:-${(C)type}}}" + [[ -z "$type" ]] && return 0 + case "$output" in + raw|md) printf "$type: " ;; + text) printf "\e[4m$type\e[24m: " ;; # underlined + esac + } + + ##* Section functions + + function display:version { + fmt:header "$version" 2 + } + + function display:breaking { + (( $#breaking != 0 )) || return 0 + + case "$output" in + raw) fmt:header "BREAKING CHANGES" 3 ;; + text|md) fmt:header "⚠ BREAKING CHANGES" 3 ;; + esac + + local hash subject + for hash message in ${(kv)breaking}; do + echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject "${message}")" + done | sort + echo + } + + function display:type { + local hash type="$1" + + local -a hashes + hashes=(${(k)commits[(R)$type]}) + + # If no commits found of type $type, go to next type + (( $#hashes != 0 )) || return 0 + + fmt:header "${TYPES[$type]}" 3 + for hash in $hashes; do + echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" + done | sort -k3 # sort by scope + echo + } + + function display:others { + local hash type + + # Commits made under types considered other changes + local -A changes + changes=(${(kv)commits[(R)${(j:|:)OTHER_TYPES}]}) + + # If no commits found under "other" types, don't display anything + (( $#changes != 0 )) || return 0 + + fmt:header "Other changes" 3 + for hash type in ${(kv)changes}; do + case "$type" in + other) echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" ;; + *) echo " - $(fmt:hash) $(fmt:scope)$(fmt:type)$(fmt:subject)" ;; + esac + done | sort -k3 # sort by scope + echo + } + + ##* Release sections order + + # Display version header + display:version + + # Display breaking changes first + display:breaking + + # Display changes for commit types in the order specified + for type in $MAIN_TYPES; do + display:type "$type" + done + + # Display other changes + display:others +} + +function main { + # $1 = until commit, $2 = since commit + local until="$1" since="$2" + + # $3 = output format (--text|--raw|--md) + # --md: uses markdown formatting + # --raw: outputs without style + # --text: uses ANSI escape codes to style the output + local output=${${3:-"--text"}#--*} + + if [[ -z "$until" ]]; then + until=HEAD + fi + + if [[ -z "$since" ]]; then + # If $since is not specified: + # 1) try to find the version used before updating + # 2) try to find the first version tag before $until + since=$(command git config --get oh-my-zsh.lastVersion 2>/dev/null) || \ + since=$(command git describe --abbrev=0 --tags "$until^" 2>/dev/null) || \ + unset since + elif [[ "$since" = --all ]]; then + unset since + fi + + # Commit classification arrays + local -A commits subjects scopes breaking reverts + local truncate=0 read_commits=0 + local hash version tag + + # Get the first version name: + # 1) try tag-like version, or + # 2) try name-rev, or + # 3) try branch name, or + # 4) try short hash + version=$(command git describe --tags $until 2>/dev/null) \ + || version=$(command git name-rev --no-undefined --name-only --exclude="remotes/*" $until 2>/dev/null) \ + || version=$(command git symbolic-ref --quiet --short $until 2>/dev/null) \ + || version=$(command git rev-parse --short $until 2>/dev/null) + + # Get commit list from $until commit until $since commit, or until root + # commit if $since is unset, in short hash form. + # --first-parent is used when dealing with merges: it only prints the + # merge commit, not the commits of the merged branch. + command git rev-list --first-parent --abbrev-commit --abbrev=7 ${since:+$since..}$until | while read hash; do + # Truncate list on versions with a lot of commits + if [[ -z "$since" ]] && (( ++read_commits > 35 )); then + truncate=1 + break + fi + + # If we find a new release (exact tag) + if tag=$(command git describe --exact-match --tags $hash 2>/dev/null); then + # Output previous release + display-release + # Reinitialize commit storage + commits=() + subjects=() + scopes=() + breaking=() + reverts=() + # Start work on next release + version="$tag" + read_commits=1 + fi + + parse-commit "$hash" + done + + display-release + + if (( truncate )); then + echo " ...more commits omitted" + echo + fi +} + +cd "$ZSH" + +# Use raw output if stdout is not a tty +if [[ ! -t 1 && -z "$3" ]]; then + main "$1" "$2" --raw +else + main "$@" +fi diff --git a/tools/check_for_upgrade.sh b/tools/check_for_upgrade.sh index 05b31e8d4..29a48b880 100644 --- a/tools/check_for_upgrade.sh +++ b/tools/check_for_upgrade.sh @@ -1,60 +1,91 @@ -#!/usr/bin/env zsh +# Migrate .zsh-update file to $ZSH_CACHE_DIR +if [[ -f ~/.zsh-update && ! -f "${ZSH_CACHE_DIR}/.zsh-update" ]]; then + mv ~/.zsh-update "${ZSH_CACHE_DIR}/.zsh-update" +fi + +# Cancel update if: +# - the automatic update is disabled. +# - the current user doesn't have write permissions nor owns the $ZSH directory. +# - git is unavailable on the system. +if [[ "$DISABLE_AUTO_UPDATE" = true ]] \ + || [[ ! -w "$ZSH" || ! -O "$ZSH" ]] \ + || ! command -v git &>/dev/null; then + return +fi -zmodload zsh/datetime -function _current_epoch() { - echo $(( $EPOCHSECONDS / 60 / 60 / 24 )) +function current_epoch() { + zmodload zsh/datetime + echo $(( EPOCHSECONDS / 60 / 60 / 24 )) } -function _update_zsh_update() { - echo "LAST_EPOCH=$(_current_epoch)" >! ${ZSH_CACHE_DIR}/.zsh-update +function update_last_updated_file() { + echo "LAST_EPOCH=$(current_epoch)" >! "${ZSH_CACHE_DIR}/.zsh-update" } -function _upgrade_zsh() { - env ZSH=$ZSH sh $ZSH/tools/upgrade.sh - # update the zsh file - _update_zsh_update +function update_ohmyzsh() { + if ZSH="$ZSH" zsh -f "$ZSH/tools/upgrade.sh" --interactive; then + update_last_updated_file + fi } -epoch_target=$UPDATE_ZSH_DAYS -if [[ -z "$epoch_target" ]]; then - # Default to old behavior - epoch_target=13 -fi +() { + emulate -L zsh -# Cancel upgrade if the current user doesn't have write permissions for the -# oh-my-zsh directory. -[[ -w "$ZSH" ]] || return 0 + local epoch_target mtime option LAST_EPOCH -# Cancel upgrade if git is unavailable on the system -whence git >/dev/null || return 0 + # 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 -if mkdir "$ZSH/log/update.lock" 2>/dev/null; then - if [ -f ${ZSH_CACHE_DIR}/.zsh-update ]; then - . ${ZSH_CACHE_DIR}/.zsh-update + # Check for lock directory + if ! command mkdir "$ZSH/log/update.lock" 2>/dev/null; then + return + fi - if [[ -z "$LAST_EPOCH" ]]; then - _update_zsh_update && return 0 - fi + # Remove lock directory on exit. `return 1` 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. + trap " + unset -f current_epoch update_last_updated_file update_ohmyzsh + command rm -rf '$ZSH/log/update.lock' + return 1 + " EXIT INT QUIT - epoch_diff=$(($(_current_epoch) - $LAST_EPOCH)) - if [ $epoch_diff -gt $epoch_target ]; then - if [ "$DISABLE_UPDATE_PROMPT" = "true" ]; then - _upgrade_zsh - else - echo "[Oh My Zsh] Would you like to update? [Y/n]: \c" - read line - if [[ "$line" == Y* ]] || [[ "$line" == y* ]] || [ -z "$line" ]; then - _upgrade_zsh - else - _update_zsh_update - fi - fi - fi - else - # create the zsh file - _update_zsh_update + # 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 - rmdir $ZSH/log/update.lock -fi + # Number of days before trying to update again + 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 + + # Ask for confirmation before updating unless disabled + if [[ "$DISABLE_UPDATE_PROMPT" = true ]]; then + update_ohmyzsh + else + # input sink to swallow all characters typed before the prompt + # and add a newline if there wasn't one after characters typed + while read -t -k 1 option; do true; done + [[ "$option" != ($'\n'|"") ]] && echo + + 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 ;; + esac + fi +} diff --git a/tools/install.sh b/tools/install.sh index 0cc020053..cfc2808fe 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -1,44 +1,94 @@ -main() { - # Use colors, but only if connected to a terminal, and that terminal - # supports them. - if which tput >/dev/null 2>&1; then - ncolors=$(tput colors) - fi - if [ -t 1 ] && [ -n "$ncolors" ] && [ "$ncolors" -ge 8 ]; then - RED="$(tput setaf 1)" - GREEN="$(tput setaf 2)" - YELLOW="$(tput setaf 3)" - BLUE="$(tput setaf 4)" - BOLD="$(tput bold)" - NORMAL="$(tput sgr0)" - else - RED="" - GREEN="" - YELLOW="" - BLUE="" - BOLD="" - NORMAL="" - fi +#!/bin/sh +# +# This script should be run via curl: +# sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +# or via wget: +# sh -c "$(wget -qO- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +# or via fetch: +# sh -c "$(fetch -o - https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +# +# As an alternative, you can first download the install script and run it afterwards: +# wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh +# sh install.sh +# +# You can tweak the install behavior by setting variables when running the script. For +# example, to change the path to the Oh My Zsh repository: +# ZSH=~/.zsh sh install.sh +# +# Respects the following environment variables: +# ZSH - path to the Oh My Zsh repository folder (default: $HOME/.oh-my-zsh) +# REPO - name of the GitHub repo to install from (default: ohmyzsh/ohmyzsh) +# REMOTE - full remote URL of the git repo to install (default: GitHub via HTTPS) +# BRANCH - branch to check out immediately after install (default: master) +# +# Other options: +# CHSH - 'no' means the installer will not change the default shell (default: yes) +# RUNZSH - 'no' means the installer will not run zsh after the install (default: yes) +# KEEP_ZSHRC - 'yes' means the installer will not replace an existing .zshrc (default: no) +# +# You can also pass some arguments to the install script to set some these options: +# --skip-chsh: has the same behavior as setting CHSH to 'no' +# --unattended: sets both CHSH and RUNZSH to 'no' +# --keep-zshrc: sets KEEP_ZSHRC to 'yes' +# For example: +# sh install.sh --unattended +# or: +# sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended +# +set -e - # Only enable exit-on-error after the non-critical colorization stuff, - # which may fail on systems lacking tput or terminfo - set -e +# Track if $ZSH was provided +custom_zsh=${ZSH:+yes} - if ! command -v zsh >/dev/null 2>&1; then - printf "${YELLOW}Zsh is not installed!${NORMAL} Please install zsh first!\n" - exit - fi +# Default settings +ZSH=${ZSH:-~/.oh-my-zsh} +REPO=${REPO:-ohmyzsh/ohmyzsh} +REMOTE=${REMOTE:-https://github.com/${REPO}.git} +BRANCH=${BRANCH:-master} - if [ ! -n "$ZSH" ]; then - ZSH=~/.oh-my-zsh - fi +# Other options +CHSH=${CHSH:-yes} +RUNZSH=${RUNZSH:-yes} +KEEP_ZSHRC=${KEEP_ZSHRC:-no} - if [ -d "$ZSH" ]; then - printf "${YELLOW}You already have Oh My Zsh installed.${NORMAL}\n" - printf "You'll need to remove $ZSH if you want to re-install.\n" - exit - fi +command_exists() { + command -v "$@" >/dev/null 2>&1 +} + +fmt_error() { + printf '%sError: %s%s\n' "$BOLD$RED" "$*" "$RESET" >&2 +} + +fmt_underline() { + printf '\033[4m%s\033[24m\n' "$*" +} + +fmt_code() { + # shellcheck disable=SC2016 # backtic in single-quote + printf '`\033[38;5;247m%s%s`\n' "$*" "$RESET" +} + +setup_color() { + # Only use colors if connected to a terminal + if [ -t 1 ]; then + RED=$(printf '\033[31m') + GREEN=$(printf '\033[32m') + YELLOW=$(printf '\033[33m') + BLUE=$(printf '\033[34m') + BOLD=$(printf '\033[1m') + RESET=$(printf '\033[m') + else + RED="" + GREEN="" + YELLOW="" + BLUE="" + BOLD="" + RESET="" + fi +} + +setup_ohmyzsh() { # Prevent the cloned repository from having insecure permissions. Failing to do # so causes compinit() calls to fail with "command not found: compdef" errors # for users with insecure umasks (e.g., "002", allowing group writability). Note @@ -46,69 +96,228 @@ main() { # precedence over umasks except for filesystems mounted with option "noacl". umask g-w,o-w - printf "${BLUE}Cloning Oh My Zsh...${NORMAL}\n" - command -v git >/dev/null 2>&1 || { - echo "Error: git is not installed" + echo "${BLUE}Cloning Oh My Zsh...${RESET}" + + command_exists git || { + fmt_error "git is not installed" exit 1 } - # The Windows (MSYS) Git is not compatible with normal use on cygwin - if [ "$OSTYPE" = cygwin ]; then - if git --version | grep msysgit > /dev/null; then - echo "Error: Windows/MSYS Git is not supported on Cygwin" - echo "Error: Make sure the Cygwin git package is installed and is first on the path" - exit 1 - fi + + ostype=$(uname) + if [ -z "${ostype%CYGWIN*}" ] && git --version | grep -q msysgit; then + fmt_error "Windows/MSYS Git is not supported on Cygwin" + fmt_error "Make sure the Cygwin git package is installed and is first on the \$PATH" + exit 1 fi - env git clone --depth=1 https://github.com/robbyrussell/oh-my-zsh.git "$ZSH" || { - printf "Error: git clone of oh-my-zsh repo failed\n" + + git clone -c core.eol=lf -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + --depth=1 --branch "$BRANCH" "$REMOTE" "$ZSH" || { + fmt_error "git clone of oh-my-zsh repo failed" exit 1 } + echo +} + +setup_zshrc() { + # Keep most recent old .zshrc at .zshrc.pre-oh-my-zsh, and older ones + # with datestamp of installation that moved them aside, so we never actually + # destroy a user's original zshrc + echo "${BLUE}Looking for an existing zsh config...${RESET}" - printf "${BLUE}Looking for an existing zsh config...${NORMAL}\n" + # Must use this exact name so uninstall.sh can find it + OLD_ZSHRC=~/.zshrc.pre-oh-my-zsh if [ -f ~/.zshrc ] || [ -h ~/.zshrc ]; then - printf "${YELLOW}Found ~/.zshrc.${NORMAL} ${GREEN}Backing up to ~/.zshrc.pre-oh-my-zsh${NORMAL}\n"; - mv ~/.zshrc ~/.zshrc.pre-oh-my-zsh; + # Skip this if the user doesn't want to replace an existing .zshrc + if [ "$KEEP_ZSHRC" = yes ]; then + echo "${YELLOW}Found ~/.zshrc.${RESET} ${GREEN}Keeping...${RESET}" + return + fi + if [ -e "$OLD_ZSHRC" ]; then + OLD_OLD_ZSHRC="${OLD_ZSHRC}-$(date +%Y-%m-%d_%H-%M-%S)" + if [ -e "$OLD_OLD_ZSHRC" ]; then + fmt_error "$OLD_OLD_ZSHRC exists. Can't back up ${OLD_ZSHRC}" + fmt_error "re-run the installer again in a couple of seconds" + exit 1 + fi + mv "$OLD_ZSHRC" "${OLD_OLD_ZSHRC}" + + echo "${YELLOW}Found old ~/.zshrc.pre-oh-my-zsh." \ + "${GREEN}Backing up to ${OLD_OLD_ZSHRC}${RESET}" + fi + echo "${YELLOW}Found ~/.zshrc.${RESET} ${GREEN}Backing up to ${OLD_ZSHRC}${RESET}" + mv ~/.zshrc "$OLD_ZSHRC" fi - printf "${BLUE}Using the Oh My Zsh template file and adding it to ~/.zshrc${NORMAL}\n" - cp "$ZSH"/templates/zshrc.zsh-template ~/.zshrc + echo "${GREEN}Using the Oh My Zsh template file and adding it to ~/.zshrc.${RESET}" + sed "/^export ZSH=/ c\\ - export ZSH=\"$ZSH\" - " ~/.zshrc > ~/.zshrc-omztemp - mv -f ~/.zshrc-omztemp ~/.zshrc - - # If this user's login shell is not already "zsh", attempt to switch. - TEST_CURRENT_SHELL=$(expr "$SHELL" : '.*/\(.*\)') - if [ "$TEST_CURRENT_SHELL" != "zsh" ]; then - # If this platform provides a "chsh" command (not Cygwin), do it, man! - if hash chsh >/dev/null 2>&1; then - printf "${BLUE}Time to change your default shell to zsh!${NORMAL}\n" - chsh -s $(grep /zsh$ /etc/shells | tail -1) - # Else, suggest the user do so manually. +export ZSH=\"$ZSH\" +" "$ZSH/templates/zshrc.zsh-template" > ~/.zshrc-omztemp + mv -f ~/.zshrc-omztemp ~/.zshrc + + echo +} + +setup_shell() { + # Skip setup if the user wants or stdin is closed (not running interactively). + if [ "$CHSH" = no ]; then + return + fi + + # If this user's login shell is already "zsh", do not attempt to switch. + if [ "$(basename -- "$SHELL")" = "zsh" ]; then + return + fi + + # If this platform doesn't provide a "chsh" command, bail out. + if ! command_exists chsh; then + cat <<EOF +I can't change your shell automatically because this system does not have chsh. +${BLUE}Please manually change your default shell to zsh${RESET} +EOF + return + fi + + echo "${BLUE}Time to change your default shell to zsh:${RESET}" + + # Prompt for user choice on changing the default login shell + printf '%sDo you want to change your default shell to zsh? [Y/n]%s ' \ + "$YELLOW" "$RESET" + read -r opt + case $opt in + y*|Y*|"") echo "Changing the shell..." ;; + n*|N*) echo "Shell change skipped."; return ;; + *) echo "Invalid choice. Shell change skipped."; return ;; + esac + + # Check if we're running on Termux + case "$PREFIX" in + *com.termux*) termux=true; zsh=zsh ;; + *) termux=false ;; + esac + + if [ "$termux" != true ]; then + # Test for the right location of the "shells" file + if [ -f /etc/shells ]; then + shells_file=/etc/shells + elif [ -f /usr/share/defaults/etc/shells ]; then # Solus OS + shells_file=/usr/share/defaults/etc/shells else - printf "I can't change your shell automatically because this system does not have chsh.\n" - printf "${BLUE}Please manually change your default shell to zsh!${NORMAL}\n" + fmt_error "could not find /etc/shells file. Change your default shell manually." + return + fi + + # Get the path to the right zsh binary + # 1. Use the most preceding one based on $PATH, then check that it's in the shells file + # 2. If that fails, get a zsh path from the shells file, then check it actually exists + if ! zsh=$(command -v zsh) || ! grep -qx "$zsh" "$shells_file"; then + if ! zsh=$(grep '^/.*/zsh$' "$shells_file" | tail -1) || [ ! -f "$zsh" ]; then + fmt_error "no zsh binary found or not present in '$shells_file'" + fmt_error "change your default shell manually." + return + fi fi fi - printf "${GREEN}" - echo ' __ __ ' - echo ' ____ / /_ ____ ___ __ __ ____ _____/ /_ ' - echo ' / __ \/ __ \ / __ `__ \/ / / / /_ / / ___/ __ \ ' - echo '/ /_/ / / / / / / / / / / /_/ / / /_(__ ) / / / ' - echo '\____/_/ /_/ /_/ /_/ /_/\__, / /___/____/_/ /_/ ' - echo ' /____/ ....is now installed!' - echo '' - echo '' - echo 'Please look over the ~/.zshrc file to select plugins, themes, and options.' - echo '' - echo 'p.s. Follow us at https://twitter.com/ohmyzsh.' - echo '' - echo 'p.p.s. Get stickers and t-shirts at https://shop.planetargon.com.' - echo '' - printf "${NORMAL}" - env zsh -l + # We're going to change the default shell, so back up the current one + if [ -n "$SHELL" ]; then + echo "$SHELL" > ~/.shell.pre-oh-my-zsh + else + grep "^$USERNAME:" /etc/passwd | awk -F: '{print $7}' > ~/.shell.pre-oh-my-zsh + fi + + # Actually change the default shell to zsh + if ! chsh -s "$zsh"; then + fmt_error "chsh command unsuccessful. Change your default shell manually." + else + export SHELL="$zsh" + echo "${GREEN}Shell successfully changed to '$zsh'.${RESET}" + fi + + echo +} + +main() { + # Run as unattended if stdin is not a tty + if [ ! -t 0 ]; then + RUNZSH=no + CHSH=no + fi + + # Parse arguments + while [ $# -gt 0 ]; do + case $1 in + --unattended) RUNZSH=no; CHSH=no ;; + --skip-chsh) CHSH=no ;; + --keep-zshrc) KEEP_ZSHRC=yes ;; + esac + shift + done + + setup_color + + if ! command_exists zsh; then + echo "${YELLOW}Zsh is not installed.${RESET} Please install zsh first." + exit 1 + fi + + if [ -d "$ZSH" ]; then + echo "${YELLOW}The \$ZSH folder already exists ($ZSH).${RESET}" + if [ "$custom_zsh" = yes ]; then + cat <<EOF + +You ran the installer with the \$ZSH setting or the \$ZSH variable is +exported. You have 3 options: + +1. Unset the ZSH variable when calling the installer: + $(fmt_code "ZSH= sh install.sh") +2. Install Oh My Zsh to a directory that doesn't exist yet: + $(fmt_code "ZSH=path/to/new/ohmyzsh/folder sh install.sh") +3. (Caution) If the folder doesn't contain important information, + you can just remove it with $(fmt_code "rm -r $ZSH") + +EOF + else + echo "You'll need to remove it if you want to reinstall." + fi + exit 1 + fi + + setup_ohmyzsh + setup_zshrc + setup_shell + + printf %s "$GREEN" + cat <<'EOF' + __ __ + ____ / /_ ____ ___ __ __ ____ _____/ /_ + / __ \/ __ \ / __ `__ \/ / / / /_ / / ___/ __ \ +/ /_/ / / / / / / / / / / /_/ / / /_(__ ) / / / +\____/_/ /_/ /_/ /_/ /_/\__, / /___/____/_/ /_/ + /____/ ....is now installed! + + +EOF + cat <<EOF +Before you scream Oh My Zsh! please look over the ~/.zshrc file to select plugins, themes, and options. + +• Follow us on Twitter: $(fmt_underline https://twitter.com/ohmyzsh) +• Join our Discord server: $(fmt_underline https://discord.gg/ohmyzsh) +• Get stickers, shirts, coffee mugs and other swag: $(fmt_underline https://shop.planetargon.com/collections/oh-my-zsh) + +EOF + printf %s "$RESET" + + if [ $RUNZSH = no ]; then + echo "${YELLOW}Run zsh to try it out.${RESET}" + exit + fi + + exec zsh -l } -main +main "$@" diff --git a/tools/theme_chooser.sh b/tools/theme_chooser.sh index 82ae5857c..3883f1d37 100755 --- a/tools/theme_chooser.sh +++ b/tools/theme_chooser.sh @@ -25,6 +25,7 @@ function theme_preview() { print "$fg[blue]${(l.((${COLUMNS}-${#THEME_NAME}-5))..─.)}$reset_color $THEME_NAME $fg[blue]───$reset_color" source "$THEMES_DIR/$THEME" cols=$(tput cols) + (exit 1) print -P "$PROMPT $RPROMPT" } diff --git a/tools/uninstall.sh b/tools/uninstall.sh index bf2244be8..6a0e7b4c7 100644 --- a/tools/uninstall.sh +++ b/tools/uninstall.sh @@ -9,26 +9,32 @@ if [ -d ~/.oh-my-zsh ]; then rm -rf ~/.oh-my-zsh fi -echo "Looking for original zsh config..." -if [ -f ~/.zshrc.pre-oh-my-zsh ] || [ -h ~/.zshrc.pre-oh-my-zsh ]; then - echo "Found ~/.zshrc.pre-oh-my-zsh -- Restoring to ~/.zshrc"; - - if [ -f ~/.zshrc ] || [ -h ~/.zshrc ]; then - ZSHRC_SAVE=".zshrc.omz-uninstalled-$(date +%Y%m%d%H%M%S)"; - echo "Found ~/.zshrc -- Renaming to ~/${ZSHRC_SAVE}"; - mv ~/.zshrc ~/"${ZSHRC_SAVE}"; - fi - - mv ~/.zshrc.pre-oh-my-zsh ~/.zshrc; +if [ -e ~/.zshrc ]; then + ZSHRC_SAVE=~/.zshrc.omz-uninstalled-$(date +%Y-%m-%d_%H-%M-%S) + echo "Found ~/.zshrc -- Renaming to ${ZSHRC_SAVE}" + mv ~/.zshrc "${ZSHRC_SAVE}" +fi - echo "Your original zsh config was restored. Please restart your session." +echo "Looking for original zsh config..." +ZSHRC_ORIG=~/.zshrc.pre-oh-my-zsh +if [ -e "$ZSHRC_ORIG" ]; then + echo "Found $ZSHRC_ORIG -- Restoring to ~/.zshrc" + mv "$ZSHRC_ORIG" ~/.zshrc + echo "Your original zsh config was restored." else - if hash chsh >/dev/null 2>&1; then - echo "Switching back to bash" - chsh -s /bin/bash + echo "No original zsh config found" +fi + +if hash chsh >/dev/null 2>&1 && [ -f ~/.shell.pre-oh-my-zsh ]; then + old_shell=$(cat ~/.shell.pre-oh-my-zsh) + echo "Switching your shell back to '$old_shell':" + if chsh -s "$old_shell"; then + rm -f ~/.shell.pre-oh-my-zsh else - echo "You can edit /etc/passwd to switch your default shell back to bash" + echo "Could not change default shell. Change it manually by running chsh" + echo "or editing the /etc/passwd file." fi fi echo "Thanks for trying out Oh My Zsh. It's been uninstalled." +echo "Don't forget to restart your terminal!" diff --git a/tools/upgrade.sh b/tools/upgrade.sh index 25b2de27a..38fac3ce0 100644..100755 --- a/tools/upgrade.sh +++ b/tools/upgrade.sh @@ -1,39 +1,106 @@ +#!/usr/bin/env zsh -# Use colors, but only if connected to a terminal, and that terminal -# supports them. -if which tput >/dev/null 2>&1; then - ncolors=$(tput colors) -fi -if [ -t 1 ] && [ -n "$ncolors" ] && [ "$ncolors" -ge 8 ]; then - RED="$(tput setaf 1)" - GREEN="$(tput setaf 2)" - YELLOW="$(tput setaf 3)" - BLUE="$(tput setaf 4)" - BOLD="$(tput bold)" - NORMAL="$(tput sgr0)" -else - RED="" - GREEN="" - YELLOW="" - BLUE="" - BOLD="" - NORMAL="" +if [ -z "$ZSH_VERSION" ]; then + exec zsh "$0" "$@" fi -printf "${BLUE}%s${NORMAL}\n" "Updating Oh My Zsh" cd "$ZSH" -if git pull --rebase --stat origin master -then - printf '%s' "$GREEN" - printf '%s\n' ' __ __ ' - printf '%s\n' ' ____ / /_ ____ ___ __ __ ____ _____/ /_ ' - printf '%s\n' ' / __ \/ __ \ / __ `__ \/ / / / /_ / / ___/ __ \ ' - printf '%s\n' '/ /_/ / / / / / / / / / / /_/ / / /_(__ ) / / / ' - printf '%s\n' '\____/_/ /_/ /_/ /_/ /_/\__, / /___/____/_/ /_/ ' - printf '%s\n' ' /____/ ' - printf "${BLUE}%s\n" "Hooray! Oh My Zsh has been updated and/or is at the current version." - printf "${BLUE}${BOLD}%s${NORMAL}\n" "To keep up on the latest news and updates, follow us on twitter: https://twitter.com/ohmyzsh" - printf "${BLUE}${BOLD}%s${NORMAL}\n" "Get your Oh My Zsh swag at: https://shop.planetargon.com/" + +# Use colors, but only if connected to a terminal +# and that terminal supports them. + +local -a RAINBOW +local RED GREEN YELLOW BLUE BOLD DIM UNDER RESET + +if [ -t 1 ]; then + RAINBOW=( + "$(printf '\033[38;5;196m')" + "$(printf '\033[38;5;202m')" + "$(printf '\033[38;5;226m')" + "$(printf '\033[38;5;082m')" + "$(printf '\033[38;5;021m')" + "$(printf '\033[38;5;093m')" + "$(printf '\033[38;5;163m')" + ) + + RED=$(printf '\033[31m') + GREEN=$(printf '\033[32m') + YELLOW=$(printf '\033[33m') + BLUE=$(printf '\033[34m') + BOLD=$(printf '\033[1m') + DIM=$(printf '\033[2m') + UNDER=$(printf '\033[4m') + RESET=$(printf '\033[m') +fi + +# Update upstream remote to ohmyzsh org +git remote -v | while read remote url extra; do + case "$url" in + https://github.com/robbyrussell/oh-my-zsh(|.git)) + git remote set-url "$remote" "https://github.com/ohmyzsh/ohmyzsh.git" + break ;; + git@github.com:robbyrussell/oh-my-zsh(|.git)) + git remote set-url "$remote" "git@github.com:ohmyzsh/ohmyzsh.git" + break ;; + esac +done + +# Set git-config values known to fix git errors +# Line endings (#4069) +git config core.eol lf +git config core.autocrlf false +# zeroPaddedFilemode fsck errors (#4963) +git config fsck.zeroPaddedFilemode ignore +git config fetch.fsck.zeroPaddedFilemode ignore +git config receive.fsck.zeroPaddedFilemode ignore +# autostash on rebase (#7172) +resetAutoStash=$(git config --bool rebase.autoStash 2>/dev/null) +git config rebase.autoStash true + +local ret=0 + +# Update Oh My Zsh +printf "${BLUE}%s${RESET}\n" "Updating Oh My Zsh" +last_commit=$(git rev-parse HEAD) +if git pull --rebase --stat origin master; then + # Check if it was really updated or not + if [[ "$(git rev-parse HEAD)" = "$last_commit" ]]; then + message="Oh My Zsh is already at the latest version." + else + message="Hooray! Oh My Zsh has been updated!" + + # Save the commit prior to updating + git config oh-my-zsh.lastVersion "$last_commit" + + # Print changelog to the terminal + if [[ "$1" = --interactive ]]; then + "$ZSH/tools/changelog.sh" HEAD "$last_commit" + fi + + printf "${BLUE}%s \`${BOLD}%s${RESET}${BLUE}\`${RESET}\n" "You can see the changelog with" "omz changelog" + fi + + printf '%s %s__ %s %s %s %s %s__ %s\n' $RAINBOW $RESET + printf '%s ____ %s/ /_ %s ____ ___ %s__ __ %s ____ %s_____%s/ /_ %s\n' $RAINBOW $RESET + printf '%s / __ \%s/ __ \ %s / __ `__ \%s/ / / / %s /_ / %s/ ___/%s __ \ %s\n' $RAINBOW $RESET + printf '%s/ /_/ /%s / / / %s / / / / / /%s /_/ / %s / /_%s(__ )%s / / / %s\n' $RAINBOW $RESET + printf '%s\____/%s_/ /_/ %s /_/ /_/ /_/%s\__, / %s /___/%s____/%s_/ /_/ %s\n' $RAINBOW $RESET + printf '%s %s %s %s /____/ %s %s %s %s\n' $RAINBOW $RESET + printf '\n' + printf "${BLUE}%s${RESET}\n" "$message" + printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "To keep up with the latest news and updates, follow us on Twitter:" "https://twitter.com/ohmyzsh" + printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "Want to get involved in the community? Join our Discord:" "https://discord.gg/ohmyzsh" + printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "Get your Oh My Zsh swag at:" "https://shop.planetargon.com/collections/oh-my-zsh" else - printf "${RED}%s${NORMAL}\n" 'There was an error updating. Try again later?' + ret=$? + printf "${RED}%s${RESET}\n" 'There was an error updating. Try again later?' fi + +# Unset git-config values set just for the upgrade +case "$resetAutoStash" in + "") git config --unset rebase.autoStash ;; + *) git config rebase.autoStash "$resetAutoStash" ;; +esac + +# Exit with `1` if the update failed +exit $ret |