View on GitHub

bash-funk

bash-funk is a collection of useful commands for Bash 3.2 or higher.

Bash-Funk “git” module

This module only loads if the git commandline client is installed.

The following statements are automatically executed when this module loads:

alias -- -git-ls-branches="git branch -a"
alias -- -git-ls-remotes="git remote -v"
alias -- -git-ls-stashes="git stash list"
alias -- -git-ls-tags="git tag"

The following commands are available when this module is loaded:

  1. -git-branch-name
  2. -git-change-contributor
  3. -git-change-date
  4. -git-cherry-pick
  5. -git-cleanse
  6. -git-clone-shallow
  7. -git-create-empty-branch
  8. -git-delete-branch
  9. -git-delete-commit
  10. -git-delete-local-branch
  11. -git-delete-remote-branch
  12. -git-fetch-pr
  13. -git-log
  14. -git-ls-conflicts
  15. -git-ls-modified
  16. -git-reset-file
  17. -git-squash
  18. -git-switch-remote-protocol
  19. -git-sync-fork
  20. -git-undo
  21. -git-update-branch
  22. -github-upstream-url
  23. -test-all-git

License

SPDX-FileCopyrightText: © Vegard IT GmbH (https://vegardit.com)
SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

-git-branch-name

Usage: -git-branch-name [OPTION]... [PATH]

Prints the name of the currently checked out git branch.

Parameters:
  PATH (default: '.', directory)
      The path to check.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git -C "$_PATH" rev-parse --symbolic-full-name --abbrev-ref HEAD

-git-change-contributor

Usage: -git-change-contributor [OPTION]... OLD_USER_EMAIL NEW_USER_NAME NEW_USER_EMAIL

Updates the author and/or committer name/e-mail of ALL matching commits.

Parameters:
  OLD_USER_EMAIL (required)
      Old user e-mail.
  NEW_USER_NAME (required)
      New user name to set.
  NEW_USER_EMAIL (required)
      New user e-mail to set.

Options:
    --author
        Indicates to change the author date of the commit.
    --committer
        Indicates to change the committer date of the commit.
    --global
        Performs the change against all tags and branches.
    --pull
        Execute 'git pull' before altering the commit(s).
    --push
        Execute 'git push --force' after altering the commit(s).
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Examples:
$  -git-change-contributor --author alice@example.com bob bob@example.com

$  -git-change-contributor --author --committer alice@example.com bob bob@example.com

Implementation:

if [[ $_pull ]]; then
   git pull || return 1
fi

if [[ ! $_author && ! $_committer ]]; then
   echo "-git-change-contributor: Error: The --author and/or --committer flag need to be specified."
   return 1
fi

local filter="
   if [ $_committer ] && [ \"\$GIT_COMMITTER_EMAIL\" = '$_OLD_USER_EMAIL' ]; then
      export GIT_COMMITTER_NAME='$_NEW_USER_NAME'
      export GIT_COMMITTER_EMAIL='$_NEW_USER_EMAIL'
   fi
   if [ $_author ] && [ \"\$GIT_AUTHOR_EMAIL\" = '$_OLD_USER_EMAIL' ]; then
      export GIT_AUTHOR_NAME='$_NEW_USER_NAME'
      export GIT_AUTHOR_EMAIL='$_NEW_USER_EMAIL'
   fi
"

if [[ $_global ]]; then
   FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch --force --env-filter "$filter" --tag-name-filter cat -- --branches --tags
else
   FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch --force --env-filter "$filter"
fi
if [[ $_push ]]; then
   git push
fi

-git-change-date

Usage: -git-change-date [OPTION]... COMMIT_HASH NEW_DATE

Changes the author and/or committer date of the given commit.

Parameters:
  COMMIT_HASH (required)
      Hash of commit to change.
  NEW_DATE (required)
      The new date to set.

Options:
    --author
        Indicates to change the author date of the commit.
    --committer
        Indicates to change the committer date of the commit.
    --pull
        Execute 'git pull' before altering the commit.
    --push
        Execute 'git push --force' after altering the commit.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Examples:
$  -git-change-date --author fe65a726b8f07cbcedc1d4b76fbdbf53678a31cf "\$(date --date '27 days ago')"

$  -git-change-date --author --committer $(git log --format='%H' -n 1) "\$(date --date '15 hours ago')"

Implementation:

if [[ $_pull ]]; then
   git pull || return 1
fi

if [[ ! $_author && ! $_committer ]]; then
   echo "-git-change-date: Error: The --author and/or --committer flag need to be specified."
   return 1
fi

FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch --force --env-filter "
   if [ \$GIT_COMMIT = $_COMMIT_HASH ]; then
      if [ $_author ]; then
         export GIT_AUTHOR_DATE='$_NEW_DATE'
      fi
      if [ $_committer ]; then
         export GIT_COMMITTER_DATE='$_NEW_DATE'
      fi
   fi
"

if [[ $_push ]]; then
   git push
fi

-git-cherry-pick

Usage: -git-cherry-pick [OPTION]... COMMIT_HASHES1 [COMMIT_HASHES]...

Cherry picks a commit into the currently checked out branch.

Parameters:
  COMMIT_HASHES (1 or more required)
      Hashes of commits to cherry pick.

Options:
    --pr PR_NUMBER (integer: 1-?)
        First fetches the pull request with the given number to be able to cherry pick from that PR.
    --pull
        Execute 'git pull' before cherry picking.
    --push
        Execute 'git push --force' after cherry picking.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ $_pull ]]; then
   git pull || return 1
fi

if [[ $_pr ]]; then
   git fetch origin pull/${_pr}/head:pr-${_pr} || return 1
fi

git cherry-pick ${_COMMIT_HASHES[@]} || return 1

if [[ $_push ]]; then
   git push
fi

-git-cleanse

Usage: -git-cleanse [OPTION]...

Reverts any uncomitted changes in the local working tree including untracked files.

Options:
    --pull
        Execute 'git pull' after reset/clean.
-y, --yes
        Answer interactive prompts with 'yes'.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ ! $_yes ]]; then
   read -p "Are you sure you want to erase all uncommitted changes? (y) " -n 1 -r
   echo
   if [[ ! $REPLY =~ ^[Yy]$ ]]; then
      echo "-git-cleanse: Aborting on user request."
      return 0
   fi
fi

git reset --hard HEAD && git clean -dfx || return 1

if [[ $_pull ]]; then
   git pull
fi

-git-clone-shallow

Usage: -git-clone-shallow [OPTION]... REPO_URL [BRANCH_NAME]

Creates a shallow clone of the selected branch of the given repository with a truncated history.

Parameters:
  REPO_URL (required)
      The URL to the git repository to clone.
  BRANCH_NAME (default: 'main')
      The name of the branch to clone.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git clone --depth 1 $_REPO_URL -b $_BRANCH_NAME

-git-create-empty-branch

Usage: -git-create-empty-branch [OPTION]... BRANCH_NAME

Creates a new empty branch in the local repository.

Parameters:
  BRANCH_NAME (required)
      The name of the new branch.

Options:
    --push
        Execute 'git push --set-upstream origin <BRANCH_NAME>' after branch creation.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if git rev-parse --verify ${_BRANCH_NAME} &>/dev/null; then
   echo "-git-create-empty-branch: Error: A branch named [${_BRANCH_NAME}] already exists."
   return 1
fi

git checkout --orphan ${_BRANCH_NAME} &&
git clean -fd &&
git rm -rf . &&
git commit -am "Created empty branch." --allow-empty || return 1

if [[ $_push ]]; then
   git push --set-upstream origin ${_BRANCH_NAME}
fi

-git-delete-branch

Usage: -git-delete-branch [OPTION]... BRANCH_NAME

Deletes a branch in the local and the remote repository.

Parameters:
  BRANCH_NAME (required)
      The name of the branch.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git branch --delete --force $_BRANCH_NAME &&
git fetch origin --prune &&
git push origin --delete $_BRANCH_NAME

-git-delete-commit

Usage: -git-delete-commit [OPTION]... COMMIT_ID

Deletes a specific commit.

Parameters:
  COMMIT_ID (required)
      The id of the commit to delete.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git rebase --onto ${_COMMIT_ID}^ ${_COMMIT_ID}

-git-delete-local-branch

Usage: -git-delete-local-branch [OPTION]... BRANCH_NAME

Deletes a branch in the local repository.

Parameters:
  BRANCH_NAME (required)
      The name of the branch.

Options:
    --force
        Delete the branch irrespective of its merged status.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ $_force ]]; then
   git branch --delete --force $_BRANCH_NAME
else
   git branch --delete $_BRANCH_NAME
 fi

-git-delete-remote-branch

Usage: -git-delete-remote-branch [OPTION]... BRANCH_NAME

Deletes a branch in the remote repository.

Parameters:
  BRANCH_NAME (required)
      The name of the branch.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git fetch origin --prune &&
git push origin --delete $_BRANCH_NAME

-git-fetch-pr

Usage: -git-fetch-pr [OPTION]... PR_NUMBER

Fetches the given pull request.

Parameters:
  PR_NUMBER (required, integer: 1-?)
      The number of the pull request to fetch.

Options:
-c, --checkout
        Checkout the PR branch after fetching.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git fetch origin pull/${_PR_NUMBER}/head:pr-${_PR_NUMBER} || return 1

if [[ $_checkout ]]; then
   git checkout pr-${_PR_NUMBER}
fi

-git-log

Usage: -git-log [OPTION]... [COUNT]

Displays the git log of the current project in a pretty and compact format.

Parameters:
  COUNT (default: '10', integer: ?-?)
      Number of entries to be displayed.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git log --graph -${_COUNT} --branches --remotes --tags --pretty=format:'%C(bold black)%h%Creset %<(70,trunc)%s %C(bold black)(%aN, %cr)%Cred%d' --date-order

-git-ls-conflicts

Usage: -git-ls-conflicts [OPTION]... [PATH]

Prints the name of the all conflicting files in the current directory tree.

Parameters:
  PATH (default: '.', directory)
      The path to check.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git diff --name-only --diff-filter=U "$_PATH"

-git-ls-modified

Usage: -git-ls-modified [OPTION]... [PATH]

Prints the name of the all deleted, changed and newly created files in the current directory tree.

Parameters:
  PATH (default: '.', directory)
      The path to check.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git -C "$_PATH" ls-files -o -m -d --exclude-standard

-git-reset-file

Usage: -git-reset-file [OPTION]... FILE

Reverts the uncommitted changes of the given local FILE to the version of the latest commit.

Parameters:
  FILE (required, file)
      File to reset.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

git checkout -- "$_FILE"

-git-squash

Usage: -git-squash [OPTION]... NUM_COMMITS

Squashes the last n commits into one.

Requirements:
  + Command 'awk' must be available.

Parameters:
  NUM_COMMITS (required, integer: 2-?)
      Number of commits to squash.

Options:
-m, --message COMMIT_MESSAGE
        The commit message to be used instead of the original ones.
    --pull
        Execute 'git pull' before squashing.
    --push
        Execute 'git push --force' after squashing.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ $_pull ]]; then
   git pull || return 1
fi

if [[ $_message ]]; then
   local commitMsg="${_message}"
else
   # load the commit messages, remove duplicates and blank lines
   local commitMsg="$(git log -${_NUM_COMMITS} --pretty=%B | awk 'NF > 0 && !a[$0]++')"
fi

git reset --soft HEAD~${_NUM_COMMITS} &&
git commit --allow-empty-message -m "${commitMsg}" || return 1

if [[ $_push ]]; then
   git push --force
fi

-git-switch-remote-protocol

Usage: -git-switch-remote-protocol [OPTION]... REMOTE_NAME1 [REMOTE_NAME]... PROTOCOL

Switches the protocol of the given remote(s) between HTTPS and SSH.

Parameters:
  REMOTE_NAME (1 or more required)
      The name of the remote, e.g. 'origin', 'upstream'. If not specified all remotes are changed.
  PROTOCOL (required, one of: [https,ssh])
      The new protocol to use.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

local url remote

for remote in "${_REMOTE_NAME[@]}"; do
   if url=$(git remote get-url $_REMOTE_NAME); then
      case "$_PROTOCOL" in
         ssh)
            case "$url" in
               https://*)
                  echo "Switching protocol of remote [$remote] to SSH..."
                  git remote set-url origin "git@${url#https://*}" &&
                  git remote -v | grep "^$remote"
                 ;;
               git@*)
                  echo "Remote [$remote] already uses SSH: $url"
                 ;;
               *)
                  echo "-git-switch-remote-protocol: URL [$url] for remote [$remote] starts with unknown protocol."
                  return 1
                 ;;
            esac
           ;;

         https)
            case "$url" in
               https://*)
                  echo "Remote [$remote] already uses HTTPS: $url"
                 ;;
               git@*)
                  echo "Switching protocol of remote [$remote] to HTTPS..."
                  git remote set-url origin "https://${url#git@*}" &&
                  git remote -v | grep "^$remote"
                 ;;
               *)
                  echo "-git-switch-remote-protocol: URL [$url] for remote [$remote] starts with unknown protocol."
                  return 1
                 ;;
            esac
           ;;
      esac
   else
      return 1
   fi
done

-git-sync-fork

Usage: -git-sync-fork [OPTION]...

Syncs the currently checked out branch of a forked repository with it's upstream repository. Uses 'git rebase -p' instead of 'git merge' by default to prevent an extra commit for the merge operation.

Options:
-b, --branch NAME
        Branch in the forked repository to sync.
    --merge
        Use 'git merge' instead of 'git rebase -p'.
    --push
        Push updates to origin after sync.
    --upstream_branch NAME
        Branch in the upstream repository to sync with.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

local currBranch currBranch_remote currBranch_remoteURL upstreamURL

# e.g. 'main'
if [[ ${_branch:-} ]]; then
   currBranch=$_branch
else
   currBranch=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD) || return 1
fi

# e.g. 'origin'
currBranch_remote=$(git config branch.$currBranch.remote) || return 1
currBranch_remoteURL=$(git config --get remote.$currBranch_remote.url) || return 1

upstreamURL=$(git remote get-url "upstream" 2>/dev/null) || true
if [[ ! $upstreamURL ]]; then
   # if forked repo is on github try to get the upstream URL via github API
   local githubRepo="${currBranch_remoteURL#*github.com/}"
   githubRepo="${githubRepo%.git}"
   if [[ ! $currBranch_remoteURL == *github.com/* ]] || ! upstreamURL="$(-github-upstream-url "${githubRepo}")"; then
      echo "-git-sync-fork: No remote 'upstream' defined. You can add it using 'git remote add upstream [REMOTE_URL]'."
      return 1
   fi
   echo "Adding remote 'upstream $upstreamURL'..."
   git remote add upstream $upstreamURL || return 1
fi

local _upstream_branch=${_upstream_branch:-$currBranch}

echo "Fetching updates from 'upstream/$_upstream_branch'..."
git fetch upstream $_upstream_branch &&
git checkout $_branch || return 1

echo "Incorporating updates from 'upstream/$_upstream_branch' into '$currBranch'..."
if [[ $_merge ]]; then
   git merge upstream/$_upstream_branch || return 1
else
   git rebase -p upstream/$_upstream_branch || return 1
fi

if [[ $_push ]]; then
   echo "Pushing updates to 'origin/$currBranch'..."
   git push --follow-tags --force origin $currBranch
fi

-git-undo

Usage: -git-undo [OPTION]... [NUM_COMMITS]

Removes the last N commits from the commit history.

Parameters:
  NUM_COMMITS (default: '1', integer: 1-?)
      Number of commits to undo.

Options:
    --push
        Push updates to origin after undo.
    --reset
        Removes any changes from the working tree.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ $_reset ]]; then
   git reset --hard HEAD~${_NUM_COMMITS} && git clean -dfx || return 1
else
   git reset --soft HEAD~${_NUM_COMMITS} || return 1
fi

if [[ $_push ]]; then
   git push --force
fi

-git-update-branch

Usage: -git-update-branch [OPTION]... [BRANCH] MAIN

Updates the given branch using 'git rebase -p' by default.

Parameters:
  BRANCH
      Name of the branch to update.
  MAIN (required)
      Name of the branch to get updates from.

Options:
    --merge
        Use 'git merge' instead of 'git rebase -p'. Rule of thumb: use 'git rebase -p' for updating personal branches and 'git merge' for updating shared branches with commits by others.
    --push
        Push updates to origin after sync.
    -----------------------------
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

if [[ ! ${_BRANCH:-} ]]; then
   _BRANCH=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD) || return 1
fi

git checkout $_MAIN &&
git pull &&
git checkout $_BRANCH || return 1

echo "Incorporating updates from '$_MAIN' into '$_BRANCH'..."
if [[ $_merge ]]; then
   git merge $_MAIN || return 1
else
   git rebase -p $_MAIN || return 1
fi

if [[ $_push ]]; then
   echo "Pushing updates to 'origin/$_BRANCH'..."
   git push --follow-tags --force origin $_BRANCH
fi

-github-upstream-url

Usage: -github-upstream-url [OPTION]... REPO

Prints the upstream URL in case the given GitHub repository is a fork.

Parameters:
  REPO (required)
      The github repository to check, e.g. 'vegardit/bash-funk'.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

hash wget &>/dev/null && local get="wget -qO-" || local get="curl -fs"

$get https://api.github.com/repos/$_REPO | grep -A100 '"parent":' | grep clone_url | head -n1 | cut -d'"' -f4
return ${PIPESTATUS[0]}

-test-all-git

Usage: -test-all-git [OPTION]...

Performs a selftest of all functions of this module by executing each function with option '--selftest'.

Options:
    --help
        Prints this help.
    --tracecmd
        Enables bash debug mode (set -x).
    --selftest
        Performs a self-test.
    --
        Terminates the option list.

Implementation:

-git-branch-name --selftest && echo || return 1
-git-change-contributor --selftest && echo || return 1
-git-change-date --selftest && echo || return 1
-git-cherry-pick --selftest && echo || return 1
-git-cleanse --selftest && echo || return 1
-git-clone-shallow --selftest && echo || return 1
-git-create-empty-branch --selftest && echo || return 1
-git-delete-branch --selftest && echo || return 1
-git-delete-commit --selftest && echo || return 1
-git-delete-local-branch --selftest && echo || return 1
-git-delete-remote-branch --selftest && echo || return 1
-git-fetch-pr --selftest && echo || return 1
-git-log --selftest && echo || return 1
-git-ls-conflicts --selftest && echo || return 1
-git-ls-modified --selftest && echo || return 1
-git-reset-file --selftest && echo || return 1
-git-squash --selftest && echo || return 1
-git-switch-remote-protocol --selftest && echo || return 1
-git-sync-fork --selftest && echo || return 1
-git-undo --selftest && echo || return 1
-git-update-branch --selftest && echo || return 1
-github-upstream-url --selftest && echo || return 1