My git flow

I have yet to meet an engineer who does not like to optimise. We like solving puzzles, work with constraints and make science apply to real life. But optimisation is high up there in terms of what we like to do.

If you work in IT these days, not just programming, but as a systems administrator, a project manager, even a designer, you’ve likely got in touch with git. git is a distributed source control management tool. In other words, it smartly stores your code (or other artifacts) in a smart way somewhere while at the same time allowing others to work on the same code you are.

I work with git every day, so when my Harvest colleague Lee Jones, told me about his workflow for git, I was naturally interested. Less mouse clicks and more automation may only shave a couple of seconds a day, but in the long run they add up. After he shared it, I modified it slightly for my own needs and am sharing it with you below.

Workflow:

  1. branch-me [name of new branch] - this creates a new branch with my initials prefixed and replaces spaces with hyphens
  2. I make changes to the code base and commit my work
  3. merge-me - this pushes the current branch and opens the GitHub PR page. It attempts to set a human-friendly title (e.g. removes my initials, removes hyphens, capitalizes each word)
  4. I merge the PR in GitHub on the previously opened page
  5. clean-me - this updates local master branch (git pull), checks out the master branch, and deletes the feature branch
  6. Done!

The script is below and I am sourcing it in my ~/.bash_profile file so that it’s available in every session.

#!/bin/bash
## Originally Lee Jones's workflow, adapted to Miguel David on 12/07/2019

export GIT_BRANCH_PREFIX="md-" # HERE: change prefix as needed

# Creates a branch prefixed with a standard prefix and changes any spaces to dashes
function branch-me() {
  if [ "$1" == "" ]
  then
    echo "ERROR: no branch name provided"
    false
  else
    local branch_name=$(echo -n $GIT_BRANCH_PREFIX[email protected] | sed -e "s/ /-/g")
    git checkout -b "${branch_name}"
  fi
}

# Pushes the current branch and opens the PR page. Attempts to set the title
# from the branch name.
function merge-me() {
  local branch_name="$(_git-current-branch)"
  local repo_name="$(_github_repo_name)"
  local branch_name_formated_as_title=$(ruby -r uri -e "puts URI.escape(\"$branch_name\").gsub(/^$GIT_BRANCH_PREFIX/, '').gsub('-', ' ').capitalize")
  local commit_subjects="$(git log --pretty=%s master...${branch_name})"
  local pr_url="https://github.com/${repo_name}/compare/${branch_name}?expand=1"

  if [ "${branch_name}" == "master" ]
  then
    echo "ERROR: you cannot push to master for PR. Switch to a different branch first."
    kill -INT $$
  fi

  # if there are multiple commits, set title by using the branch name
  if [[ "$(echo "${commit_subjects}" | wc -l)" -gt "1" ]]
  then
    pr_url="${pr_url}&title=${branch_name_formated_as_title}"
  fi

  echo "Opening PR for ${repo_name} from the ${branch_name} branch..."
  echo "Pushing ${branch_name} branch to GitHub..."
  git push origin "${branch_name}"
  echo "Opening PR in browser..."
  # Sometimes the compare view does not trigger a new PR - waiting seems to help
  sleep 2
  open "${pr_url}"
}

# switch to master, pull from origin and merge, delete previous branch
function clean-me() {
  local original_branch_name=`_git-current-branch`
  local target_branch_name=${1:-master}
  if [ "$original_branch_name" = "${target_branch_name}" ]
  then
    echo "Already on ${target_branch_name} branch, updating..."
    git fetch origin
    git merge origin/$target_branch_name
  else
    git fetch origin
    git checkout $target_branch_name
    git merge origin/$target_branch_name
    # If the original branch was rebased before merging, `git branch -d` won't
    # work. We can assume it's safe to force deletion if there are effectively
    # no differences between the two branches.
    if [ "$(git diff $target_branch_name..$original_branch_name --shortstat)" == "" ]; then
      git branch -D "${original_branch_name}"
      # Otherwise, try to delete the branch normally.
    else
      if (git branch -d "${original_branch_name}"); then
        echo "Successfully deleted branch '$original_branch_name'!"
      else
        read -p "Do you want to force deletion of branch '$original_branch_name' (y/n)? " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
          git branch -D "${original_branch_name}"
        else
          echo "You said '$REPLY'. Exiting!"
          return 0
        fi
      fi
    fi
  fi
  echo "Cleaned up. NEEEXT!"
}

function _git-current-branch() {
  echo "$(git branch | grep -E '^\*' | awk '{ print $2 }')"
}

function _github_repo_name() {
  local remote_origin_url="$(git config --get remote.origin.url)"
  if $(echo "${remote_origin_url}" | grep -q -E "[email protected]:.*\.git"); then
    echo "$(echo $remote_origin_url | sed -e 's/[email protected]://' | sed -e 's/.git//')"
  else
    echo "ERROR: could not determine GitHub repo name from '${remote_origin_url}'."
    kill -INT $$
  fi
}

I hope this helps!