Skip to Content

Woah, git just wanna have func

Many developers, new and old, curse git from the depths of their bowels. However, once you get past the learning curve, it’s surprisingly fun to tinker and automate common git tasks. The following are a collection of bash functions and aliases that accelerate my workflow considerably:

glS

“git pickaxe with filter”

A lot of magic occurs when you combine fuzzy finders with git. But first,

@@@@@@@
@^_^@@@@@
@@@@@@@
WAKE UP SHEEPLE
WAKE UP SHEEPLE
WAKE UP SHEEPLE

You can search the diffs of all your commits with git log -G or git log -S.

The author continues yelling hoarsely about the git log --pickaxe option as HR drags him away.

One of the less well-known, but most useful git log features is the pickaxe option, which lets you search the actual code changes in all commits. To make it more convenient to use, the following function adds the --stat option to see which files were changed. And to filter out unwanted commits, you can use the --grep and --invert-grep options. Once you identify a commit you want to see, you can easily view its changes by running git show <commit hash>

# Example:
#     glS "//specific comment somewhere in the code"
#     glS "important_var_with_specific_name ="
#
# Definition:
glS () {
    git log --stat -S$1 --grep="Whitespace cleanup" --invert-grep
}

gcz/glz

“interactive fuzzy git checkout/log”

Descriptive names for branches can get unbearably long. By piping the git branch output through fzf or fzy, you can fuzzy select the branch you want from a list, sorted by when branches were last updated.

alias gcz='git checkout $(git branch --sort="-authordate" | fzy)'
alias glz='git log $(git branch --sort="-authordate" | fzy)'

gla

“git log by author, simplified”

Many times, it’s useful to see a list of all of the commits from a particular person. The following function allows you to specify a particular author. If no author is specified, it defaults to showing commits made by you.

# Example:
#     gla jsmith
#
# Definition:
gla() {
  cmd="git log --author=${1:-$(whoami)}"
  printf "$cmd\n\n"; eval "$cmd"
}

gPb

“git push current branch”

For the same reasons as above, it’s nice to have a faster way of pushing out branches with long names. This command will push out the current branch you’re on (assuming your remote is origin). To delete the branch, just append --delete.

# Example:
#     gPb --delete
#
# Definition:
alias gPb='git push origin $(git rev-parse --abbrev-ref HEAD)'

gshf

“git show file at old commit hash”

A lot of times I want to enter the time machine and see what a file looked like sometime in history. The following function accepts a git commit hash and file name, then lets you fuzzily select the desired file from the possible candidates. Using git show, it displays the file at that revision.

You can even write the output to a file so your favorite editor can stomach it.

# Example:
# gshf ca09238 myfile.c > myfile-old.c
#
# Definition:
gshf() {
  if [ "$#" -eq 2 ]; then
    cd "$(git rev-parse --show-toplevel)"
    # Adjust the ripgrep file extensions below to the files desired.
    local file=$(git ls-tree -r "$1" --name-only | rg "$2" | rg '\.c$|\.h$|\.cpp$|\.hpp$|\.py' | fzy)
    local cmd="git show $1:./${file}"
    echo "$cmd" && eval "$cmd"
  else
    echo "Usage: gshf <hash> <filename>"
  fi
}

gspr

“git save, pull, and restore”

Developers often want to pull from a central repo, but are stymied by messages about “Unstaged changes…". The traditional way to deal with this is to stash your local changes, git pull --rebase, and finally git stash pop to restore your stashed changes. This command handles it in a single step. If no stashing is needed, it just does the rebase.

gspr() {
  if ! git diff-index --quiet HEAD --; then
    echo "git stash save; git pull --rebase; git stash pop"
    git stash save; git pull --rebase; git stash pop
  else
    echo "git pull --rebase"
    git pull --rebase
  fi
}

gcfu

“configure git username and email quickly”

One thing I occasionally find irksome is configuring a new git repo with my user name and email. It seems to take a disproportionate amount of typing for what is really a small task. This command lets you do the same thing with just 4 keystrokes, followed by your name and email.

# Example:
#     gcfu "dp12" "dp12@myemail.com"
#
# Definition:
gcfu() {
  if [ "$#" -eq 2 ]; then
    cmd1="git config --local user.name \"$1\""
    cmd2="git config --local user.email \"$2\""
    echo -n "Exec \n$cmd1\n$cmd2 [y/n]? "
    read reply
    if [ $reply != "${reply#[Yy]}" ]; then
      eval "$cmd1"
      eval "$cmd2"
      printf "\ngit config --local [user] is now:"
      git config --local --get-regexp user
    fi
  else
    echo "Usage: gcfu <username> <email>"
  fi
}

BONUS:

grrauth

“Rewrite author/committer in history, the bash function way!"

Have you ever misconfigured your name and email and then made a series of commits? Github supplies a handy script to fix it for you. But if you want to execute the script, you have to copy it to every repo you want to do the change in and then execute it. I think it’s much easier to deploy as a bash function, so I converted the script into one. If no arguments other than the old email are given, it pulls the new username and email from your git config.

# Example:
#     grrauth wrong-email@mymail.com
#
# Definition:
grrauth() {
  if [ "$#" -gt 0 ]; then
    export OLD_EMAIL="${1:-$(git config user.email)}"
    export CORRECT_EMAIL="${2:-$(git config user.email)}"
    export CORRECT_NAME="${3:-$(git config user.name)}"
    echo "Set commits with email \"$OLD_EMAIL\" to author/committer:"
    echo "\"$CORRECT_NAME <$CORRECT_EMAIL>\"? [Y]es/[N]o"
    read REPLY
    if [[ $REPLY =~ ^[Yy] ]]; then
      git filter-branch -f --env-filter \
      'if [ "$GIT_COMMITTER_EMAIL" = "${OLD_EMAIL}" ]
      then
          export GIT_COMMITTER_NAME="${CORRECT_NAME}"
          export GIT_COMMITTER_EMAIL="${CORRECT_EMAIL}"
      fi
      if [ "$GIT_AUTHOR_EMAIL" = "${OLD_EMAIL}" ]
      then
          export GIT_AUTHOR_NAME="${CORRECT_NAME}"
          export GIT_AUTHOR_EMAIL="${CORRECT_EMAIL}"
      fi' --tag-name-filter cat -- --branches --tags
    fi
  else
    echo "Usage: grrauth <old email>"
    echo "       grrauth <old email> <new email> <new name>"
  fi
}