diff --git a/clean_files.txt b/clean_files.txt index 7e901805fd..fc362c85e7 100644 --- a/clean_files.txt +++ b/clean_files.txt @@ -50,6 +50,7 @@ themes/modern plugins/available/basher.plugin.bash plugins/available/cmd-returned-notify.plugin.bash plugins/available/docker-machine.plugin.bash +plugins/available/git.plugin.bash plugins/available/go.plugin.bash plugins/available/goenv.plugin.bash plugins/available/history.plugin.bash diff --git a/plugins/available/git.plugin.bash b/plugins/available/git.plugin.bash index b2c05b6001..e4efc3fe38 100644 --- a/plugins/available/git.plugin.bash +++ b/plugins/available/git.plugin.bash @@ -1,275 +1,317 @@ +# shellcheck shell=bash cite about-plugin about-plugin 'git helper functions' function git_remote { - about 'adds remote $GIT_HOSTING:$1 to current repo' - group 'git' + about "adds remote $GIT_HOSTING:$1 to current repo" + group "git" - echo "Running: git remote add origin ${GIT_HOSTING}:$1.git" - git remote add origin $GIT_HOSTING:$1.git + echo "Running: git remote add origin ${GIT_HOSTING}:$1.git" + git remote add origin "$GIT_HOSTING:$1".git } function git_first_push { - about 'push into origin refs/heads/master' - group 'git' + about 'push into origin refs/heads/master' + group 'git' - echo "Running: git push origin master:refs/heads/master" - git push origin master:refs/heads/master + echo "Running: git push origin master:refs/heads/master" + git push origin master:refs/heads/master } function git_pub() { - about 'publishes current branch to remote origin' - group 'git' - BRANCH=$(git rev-parse --abbrev-ref HEAD) + about 'publishes current branch to remote origin' + group 'git' + BRANCH=$(git rev-parse --abbrev-ref HEAD) - echo "Publishing ${BRANCH} to remote origin" - git push -u origin $BRANCH + echo "Publishing ${BRANCH} to remote origin" + git push -u origin "$BRANCH" } function git_revert() { - about 'applies changes to HEAD that revert all changes after this commit' - group 'git' + about 'applies changes to HEAD that revert all changes after this commit' + group 'git' - git reset $1 - git reset --soft HEAD@{1} - git commit -m "Revert to ${1}" - git reset --hard + git reset "$1" + git reset --soft "HEAD@{1}" + git commit -m "Revert to ${1}" + git reset --hard } function git_rollback() { - about 'resets the current HEAD to this commit' - group 'git' - - function is_clean() { - if [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]]; then - echo "Your branch is dirty, please commit your changes" - kill -INT $$ - fi - } - - function commit_exists() { - git rev-list --quiet $1 - status=$? - if [ $status -ne 0 ]; then - echo "Commit ${1} does not exist" - kill -INT $$ - fi - } - - function keep_changes() { - while true - do - read -p "Do you want to keep all changes from rolled back revisions in your working tree? [Y/N]" RESP - case $RESP - in - [yY]) - echo "Rolling back to commit ${1} with unstaged changes" - git reset $1 - break - ;; - [nN]) - echo "Rolling back to commit ${1} with a clean working tree" - git reset --hard $1 - break - ;; - *) - echo "Please enter Y or N" - esac - done - } - - if [ -n "$(git symbolic-ref HEAD 2> /dev/null)" ]; then - is_clean - commit_exists $1 - - while true - do - read -p "WARNING: This will change your history and move the current HEAD back to commit ${1}, continue? [Y/N]" RESP - case $RESP - in - [yY]) - keep_changes $1 - break - ;; - [nN]) - break - ;; - *) - echo "Please enter Y or N" - esac - done - else - echo "you're currently not in a git repository" - fi + about 'resets the current HEAD to this commit' + group 'git' + + function is_clean() { + if [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]]; then + echo "Your branch is dirty, please commit your changes" + kill -INT $$ + fi + } + + function commit_exists() { + git rev-list --quiet "$1" + status=$? + if [ $status -ne 0 ]; then + echo "Commit ${1} does not exist" + kill -INT $$ + fi + } + + function keep_changes() { + while true; do + # shellcheck disable=SC2162 + read -p "Do you want to keep all changes from rolled back revisions in your working tree? [Y/N]" RESP + case $RESP in + + [yY]) + echo "Rolling back to commit ${1} with unstaged changes" + git reset "$1" + break + ;; + [nN]) + echo "Rolling back to commit ${1} with a clean working tree" + git reset --hard "$1" + break + ;; + *) + echo "Please enter Y or N" + ;; + esac + done + } + + if [ -n "$(git symbolic-ref HEAD 2> /dev/null)" ]; then + is_clean + commit_exists "$1" + + while true; do + # shellcheck disable=SC2162 + read -p "WARNING: This will change your history and move the current HEAD back to commit ${1}, continue? [Y/N]" RESP + case $RESP in + + [yY]) + keep_changes "$1" + break + ;; + [nN]) + break + ;; + *) + echo "Please enter Y or N" + ;; + esac + done + else + echo "you're currently not in a git repository" + fi } function git_remove_missing_files() { - about "git rm's missing files" - group 'git' + about "git rm's missing files" + group 'git' - git ls-files -d -z | xargs -0 git update-index --remove + git ls-files -d -z | xargs -0 git update-index --remove } # Adds files to git's exclude file (same as .gitignore) function local-ignore() { - about 'adds file or path to git exclude file' - param '1: file or path fragment to ignore' - group 'git' - echo "$1" >> .git/info/exclude + about 'adds file or path to git exclude file' + param '1: file or path fragment to ignore' + group 'git' + echo "$1" >> .git/info/exclude } # get a quick overview for your git repo function git_info() { - about 'overview for your git repo' - group 'git' - - if [ -n "$(git symbolic-ref HEAD 2> /dev/null)" ]; then - # print informations - echo "git repo overview" - echo "-----------------" - echo - - # print all remotes and thier details - for remote in $(git remote show); do - echo $remote: - git remote show $remote - echo - done - - # print status of working repo - echo "status:" - if [ -n "$(git status -s 2> /dev/null)" ]; then - git status -s - else - echo "working directory is clean" - fi - - # print at least 5 last log entries - echo - echo "log:" - git log -5 --oneline - echo - - else - echo "you're currently not in a git repository" - - fi + about 'overview for your git repo' + group 'git' + + if [ -n "$(git symbolic-ref HEAD 2> /dev/null)" ]; then + # print informations + echo "git repo overview" + echo "-----------------" + echo + + # print all remotes and thier details + for remote in $(git remote show); do + echo "$remote": + git remote show "$remote" + echo + done + + # print status of working repo + echo "status:" + if [ -n "$(git status -s 2> /dev/null)" ]; then + git status -s + else + echo "working directory is clean" + fi + + # print at least 5 last log entries + echo + echo "log:" + git log -5 --oneline + echo + + else + echo "you're currently not in a git repository" + + fi } function git_stats { - about 'display stats per author' - group 'git' - -# awesome work from https://github.com/esc/git-stats -# including some modifications - -if [ -n "$(git symbolic-ref HEAD 2> /dev/null)" ]; then - echo "Number of commits per author:" - git --no-pager shortlog -sn --all - AUTHORS=$( git shortlog -sn --all | cut -f2 | cut -f1 -d' ') - LOGOPTS="" - if [ "$1" == '-w' ]; then - LOGOPTS="$LOGOPTS -w" - shift - fi - if [ "$1" == '-M' ]; then - LOGOPTS="$LOGOPTS -M" - shift - fi - if [ "$1" == '-C' ]; then - LOGOPTS="$LOGOPTS -C --find-copies-harder" - shift - fi - for a in $AUTHORS - do - echo '-------------------' - echo "Statistics for: $a" - echo -n "Number of files changed: " - git log $LOGOPTS --all --numstat --format="%n" --author=$a | cut -f3 | sort -iu | wc -l - echo -n "Number of lines added: " - git log $LOGOPTS --all --numstat --format="%n" --author=$a | cut -f1 | awk '{s+=$1} END {print s}' - echo -n "Number of lines deleted: " - git log $LOGOPTS --all --numstat --format="%n" --author=$a | cut -f2 | awk '{s+=$1} END {print s}' - echo -n "Number of merges: " - git log $LOGOPTS --all --merges --author=$a | grep -c '^commit' - done -else - echo "you're currently not in a git repository" -fi + about 'display stats per author' + group 'git' + + # awesome work from https://github.com/esc/git-stats + # including some modifications + + if [ -n "$(git symbolic-ref HEAD 2> /dev/null)" ]; then + echo "Number of commits per author:" + git --no-pager shortlog -sn --all + AUTHORS=$(git shortlog -sn --all | cut -f2 | cut -f1 -d' ') + LOGOPTS="" + if [ "$1" == '-w' ]; then + LOGOPTS="$LOGOPTS -w" + shift + fi + if [ "$1" == '-M' ]; then + LOGOPTS="$LOGOPTS -M" + shift + fi + if [ "$1" == '-C' ]; then + LOGOPTS="$LOGOPTS -C --find-copies-harder" + shift + fi + for a in $AUTHORS; do + echo '-------------------' + echo "Statistics for: $a" + echo -n "Number of files changed: " + # shellcheck disable=SC2086 + git log $LOGOPTS --all --numstat --format="%n" --author="$a" | cut -f3 | sort -iu | wc -l + echo -n "Number of lines added: " + # shellcheck disable=SC2086 + git log $LOGOPTS --all --numstat --format="%n" --author="$a" | cut -f1 | awk '{s+=$1} END {print s}' + echo -n "Number of lines deleted: " + # shellcheck disable=SC2086 + git log $LOGOPTS --all --numstat --format="%n" --author="$a" | cut -f2 | awk '{s+=$1} END {print s}' + echo -n "Number of merges: " + # shellcheck disable=SC2086 + git log $LOGOPTS --all --merges --author="$a" | grep -c '^commit' + done + else + echo "you're currently not in a git repository" + fi } function gittowork() { - about 'Places the latest .gitignore file for a given project type in the current directory, or concatenates onto an existing .gitignore' - group 'git' - param '1: the language/type of the project, used for determining the contents of the .gitignore file' - example '$ gittowork java' - - result=$(curl -L "https://www.gitignore.io/api/$1" 2>/dev/null) - - if [[ $result =~ ERROR ]]; then - echo "Query '$1' has no match. See a list of possible queries with 'gittowork list'" - elif [[ $1 = list ]]; then - echo "$result" - else - if [[ -f .gitignore ]]; then - result=`echo "$result" | grep -v "# Created by http://www.gitignore.io"` - echo ".gitignore already exists, appending..." - echo "$result" >> .gitignore - else - echo "$result" > .gitignore - fi - fi + about 'Places the latest .gitignore file for a given project type in the current directory, or concatenates onto an existing .gitignore' + group 'git' + param '1: the language/type of the project, used for determining the contents of the .gitignore file' + example '$ gittowork java' + + result=$(curl -L "https://www.gitignore.io/api/$1" 2> /dev/null) + + if [[ $result =~ ERROR ]]; then + echo "Query '$1' has no match. See a list of possible queries with 'gittowork list'" + elif [[ $1 = list ]]; then + echo "$result" + else + if [[ -f .gitignore ]]; then + result=$(echo "$result" | grep -v "# Created by http://www.gitignore.io") + echo ".gitignore already exists, appending..." + echo "$result" >> .gitignore + else + echo "$result" > .gitignore + fi + fi } function gitignore-reload() { - about 'Empties the git cache, and readds all files not blacklisted by .gitignore' - group 'git' - example '$ gitignore-reload' - - # The .gitignore file should not be reloaded if there are uncommited changes. - # Firstly, require a clean work tree. The function require_clean_work_tree() - # was stolen with love from https://www.spinics.net/lists/git/msg142043.html - - # Begin require_clean_work_tree() - - # Update the index - git update-index -q --ignore-submodules --refresh - err=0 - - # Disallow unstaged changes in the working tree - if ! git diff-files --quiet --ignore-submodules -- - then - echo >&2 "ERROR: Cannot reload .gitignore: Your index contains unstaged changes." - git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2 - err=1 - fi - - # Disallow uncommited changes in the index - if ! git diff-index --cached --quiet HEAD --ignore-submodules - then - echo >&2 "ERROR: Cannot reload .gitignore: Your index contains uncommited changes." - git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2 - err=1 - fi - - # Prompt user to commit or stash changes and exit - if [ $err = 1 ] - then - echo >&2 "Please commit or stash them." - fi - - # End require_clean_work_tree() - - # If we're here, then there are no uncommited or unstaged changes dangling around. - # Proceed to reload .gitignore - if [ $err = 0 ]; then - # Remove all cached files - git rm -r --cached . - - # Re-add everything. The changed .gitignore will be picked up here and will exclude the files - # now blacklisted by .gitignore - echo >&2 "Running git add ." - git add . - echo >&2 "Files readded. Commit your new changes now." - fi + about 'Empties the git cache, and readds all files not blacklisted by .gitignore' + group 'git' + example '$ gitignore-reload' + + # The .gitignore file should not be reloaded if there are uncommited changes. + # Firstly, require a clean work tree. The function require_clean_work_tree() + # was stolen with love from https://www.spinics.net/lists/git/msg142043.html + + # Begin require_clean_work_tree() + + # Update the index + git update-index -q --ignore-submodules --refresh + err=0 + + # Disallow unstaged changes in the working tree + if ! git diff-files --quiet --ignore-submodules --; then + echo >&2 "ERROR: Cannot reload .gitignore: Your index contains unstaged changes." + git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2 + err=1 + fi + + # Disallow uncommited changes in the index + if ! git diff-index --cached --quiet HEAD --ignore-submodules; then + echo >&2 "ERROR: Cannot reload .gitignore: Your index contains uncommited changes." + git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2 + err=1 + fi + + # Prompt user to commit or stash changes and exit + if [ $err = 1 ]; then + echo >&2 "Please commit or stash them." + fi + + # End require_clean_work_tree() + + # If we're here, then there are no uncommited or unstaged changes dangling around. + # Proceed to reload .gitignore + if [ $err = 0 ]; then + # Remove all cached files + git rm -r --cached . + + # Re-add everything. The changed .gitignore will be picked up here and will exclude the files + # now blacklisted by .gitignore + echo >&2 "Running git add ." + git add . + echo >&2 "Files readded. Commit your new changes now." + fi +} + +function git-changelog() { + # --------------------------------------------------------------- + # ORIGINAL ANSWER: https://stackoverflow.com/a/2979587/10362396 | + # --------------------------------------------------------------- + about 'Creates the git changelog from one point to another by date' + group 'git' + example '$ git-changelog origin/master...origin/release [md|txt]' + + if [[ "$1" != *"..."* ]]; then + echo "Please include the valid 'diff' to make changelog" + return 1 + fi + + local NEXT=$(date +%F) + + if [[ "$2" == "md" ]]; then + echo "# CHANGELOG $1" + + # shellcheck disable=SC2162 + git log "$1" --no-merges --format="%cd" --date=short | sort -u -r | while read DATE; do + echo + echo "### $DATE" + git log --no-merges --format=" * (%h) %s by [%an](mailto:%ae)" --since="$DATE 00:00:00" --until="$DATE 24:00:00" + NEXT=$DATE + done + else + echo "CHANGELOG $1" + echo ---------------------- + + # shellcheck disable=SC2162 + git log "$1" --no-merges --format="%cd" --date=short | sort -u -r | while read DATE; do + echo + echo [$DATE] + git log --no-merges --format=" * (%h) %s by %an <%ae>" --since="$DATE 00:00:00" --until="$DATE 24:00:00" + NEXT=$DATE + done + fi }