Zsh with git flow and cleanup of branches

NOTE: make sure you you use git-flow-avh (not the default one with brew).
I’ve created a repo with my ZSH setup: https://github.com/justin808/justin808-dotfiles/blob/master/zsh/git-flow.zsh


I’ve got the following setup scripts for zsh with git and git-flow, along with a couple other convenience methods. gist: git-helpers.zsh. That depends on some functions defined here: gist: utility.zsh. The code for these is also shown below. The scripts also have some cleanup commands for removing local branches that are fully merged and then cleaning up the remote branches.

The basic flow I use is:

  1. create the branch This command creates branch feature/some-good-branch-description. Note the sanitization of whatever goes in the feature name parameter.
gffs "some good branch description"
  1. Make changes on that branch, commit, commit, rebase to make commits cleaner
  2. When ready to push new branch to remote at github:
gpthis
  1. If you will have conflicts in what’s on your branch with other changes in develop, then merge whatever’s in develop (or master for hot fix) over to your feature branch so you don’t have merge commits. If you don’t do this, you’ll have to manually finish up the merging and other actions of the git flow scripts. Be sure that you’ve done the proper updating of your develop branch etc. with the remote, before you merge what you think is the latest on the develop branch! Otherwise, your merge might incorrectly do nothing.
# If you want to merge what's in develop to your feature branch
# make sure develop branch is current with remote
gco develop
gup
# be sure to be on correct branch
gco feature/some-good-branch-description
# Bring over latest
git merge origin/develop
# Alternately, you might
# git rebase origin/develop
# But that creates new commits, and anybody else working on that branch needs to be aware of this.
  1. Then create pull request on github. Be sure to note if the source branch is develop for a feature and master for a hot fix. That’s critical!
  2. After code is reviewed and ready to close the pull request (don’t do it on github), you’ll run this command. Note, be sure that develop branch is current with remote!
# Be sure that you've sync'd up develop with remote
gco develop
gup
# be sure to be on correct branch, as next command uses the current branch
gco feature/some-good-branch-description
gfff

Mnemonic:

  • gf = git flow
  • f = feature
  • r = release
  • h = hotfix
  • s = start
  • f = finish

So we have:

  • gffs
  • gfff
  • gfhs
  • gfhf
  • gfrs
  • gfrf

Here’s the code:

# Generic Utilities

using_port() {
  #lsof -i:${1}
  ps -p $(lsof -i:$1 -Fp | cut -c 2-)
}

most_used() {
  history | awk '{a[$4]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head -20
}


echoRun() {
  START=$(date +%s)
  echo "> $1"
  eval time $1
  END=$(date +%s)
  DIFF=$(( $END - $START ))
  echo "It took $DIFF seconds"
}

sanitize() {
  echo $1 | tr ":  /."  "-" | tr -d ",'\""
}


# history grep tail
hgt() {
  fc -l 1 | grep -i --color=auto $1 | tail -n 40
}

#### GIT ####

# http://notes.envato.com/developers/rebasing-merge-commits-in-git/
# https://gist.github.com/590895
function git_current_branch() {
  git symbolic-ref HEAD 2> /dev/null | sed -e 's/refs\/heads\///'
}

alias gpthis='git push origin HEAD:$(git_current_branch) && git branch -u origin/$(git_current_branch) $(git_current_branch) && echo "pushed current branch and set upstream to origin"'
alias s='git status --short'
alias gh='git log --name-status -n'
alias gm='git merge --no-ff'
alias gl='git log'
alias gs='git stash'
alias grhard='git reset --hard'
alias grsoft='git reset --soft'


git-branch-current() { 
    printf "%s\n" $(git branch 2> /dev/null | grep -e ^* | tr -d "\* ")
}
 
git-log-last-pushed-hash() { 
    local currentBranch=$(git-branch-current);
    git log --format="%h" -n 1 origin/${currentBranch}
}
 
git-rebase-unpushed() { 
    git rebase --interactive $(git-log-last-pushed-hash)
}

gitk_everything() {
  gitk --all --date-order $(git log -g --pretty=%H)
}

gitFlowNameForCurrentBranch() {
  # next line using sed is greedy
  # git_current_branch | sed -e 's/.*\///'
  # This is the non-greedy match
  git_current_branch | perl -pe "s|.*?/||"
}

alias gfn=gitFlowNameForCurrentBranch

gffs() {
  feature=`sanitize $1`
  echoRun "git flow feature start $feature"
}

gffp() {
  feature=`sanitize $1`
  echoRun "git flow feature publish $feature"
}

gfff() {
  echoRun "git flow feature finish $(gitFlowNameForCurrentBranch)"
}

gfrs() {
  release=`sanitize $1`
  echoRun "git flow release start $release"
}

gfrp() {
  release=`sanitize $1`
  echoRun "git flow release publish $release"
}

gfrf() {
  echoRun "git flow release finish -Fpn $(gitFlowNameForCurrentBranch)"
}

gfhs() {
  release=`sanitize $1`
  echoRun "git flow hotfix start $release"
}

gfhp() {
  release=`sanitize $1`
  echoRun "git flow hotfix publish $release"
}

gfhf() {
  echoRun "git flow hotfix finish -Fpn $(gitFlowNameForCurrentBranch)"
}

alias git-diff-master-develop='git log --left-right --graph --cherry-pick master..develop'

alias git-cleanup-merged-branches='git branch --merged develop | grep -v develop | xargs git branch -d'

alias git-cleanup-origin='git remote prune origin'

alias git-cleanup-octopress-merged-branches='git branch --merged source | grep -v source | grep -v master | xargs git branch -d'

git-list-remote-branches-to-remove() {
  git branch -r --merged | grep 'origin/' | grep -v "origin/master$" | grep -v "origin/develop$" | sed 's/\s*origin\///'
}

git-list-remote-branches-to-remove-do-it() {
  git-list-remote-branches-to-remove | xargs -n 1 git push --delete origin
}

And if you so happen to forget to do either of:

  1. Run gup to keep develop and master branches up to date with remote before doing the “finish” commands (gfff or gfhf or gfrf).
  2. Merge the latest in develop over to your feature branch before gfff

Then you’ll have to manually finish some clean up and merging. And you might then relate to this.

1 Like

@martin, @igor, We should revise the scripts above so that before they do anything, they:

  1. save context of current branch
  2. gco master && gup
  3. gco develop && gup
  4. go back to prior working branch

I think that would save a lot of headache from forgetting to gup master and develop.

  • 132 issues
  • 71 PRs
  • Last commit: Sep 25, 2012

Discussion on if gitflow is abandoned:

We really need a feature to abort doing git flow actions under certain circumstances.

Here are a few gotcha situations that really need preconditions met before moving forward.

  1. Starting a feature, release, or hot-fix without first pulling the source branch (master or develop).
  2. Finishing a feature, release, or hot-fix without first merging the source branch into your working branch to resolve in merge conflicts. Any opinion if this is recommended to avoid merges in when finishing features, releases, or hot-fixes?
  3. Creating a release branch when develop is missing commits from master due to some commits being done directly on master rather than using the git-flow process. In this case, these commits probably should get merged to develop before doing the git flow release start.

It seems like I need to change my scripts to always pass the -F option and that would take care of #1.

For example, from the docs for command line arguments.

git flow feature finish [-rFk] <name|nameprefix>
-r rebase instead of merge
-F fetch from $ORIGIN before performing finish
-k keep branch after performing finish

I’m not sure about #2 and #3.

On a related note, this version of git-flow seems very active:

Is there any description of the what’s different between the petervanderdoes fork and the original nvie version?

I updated my gist of git flow helpers with this change.

After this:

  1. Commands use the -F option to fetch
  2. Master and developer are pull --rebase.
  3. Commands use option --showcommands so that one can see what git-flow is doing
gup_develop() {
    current_branch="$(git_current_branch)"
    echo current_branch is $current_branch
    gco develop && gup && gco "$current_branch"
}

gup_master() {
    current_branch="$(git_current_branch)"
    echo current_branch is $current_branch
    gco master && gup && gco "$current_branch"
}

gup_develop_master() {
    gup_develop && gup_master
}

gffs() {
  feature=`sanitize $1`
  gup_develop_master && echoRun "git flow feature start -F --showcommands $feature"
}

gffp() {
  feature=`sanitize $1`
  gup_develop_master && echoRun "git flow feature publish --showcommands $feature"
}

gfff() {
  gup_develop_master && echoRun "git flow feature finish -F --showcommands $(gitFlowNameForCurrentBranch)" &&
        echo "Confirm merge and then push develop" || echo "Examine error messages!"
}

gfrs() {
  release=`sanitize $1`
  gup_develop_master && echoRun "git flow release start -F --showcommands $release"
}

gfrp() {
  release=`sanitize $1`
  gup_develop_master && echoRun "git flow release publish -F --showcommands $release"
}

# Changed to
# 1. Be sure to sync up remotes and pull --rebase
# 2. Not push
gfrf() {
    gup_develop_master && echoRun "git flow release finish -Fn --showcommands $(gitFlowNameForCurrentBranch)" &&
        echo "Confirm merge and then push master" || echo "Examine error messages!"
}

gfhs() {
  release=`sanitize $1`
  gup_develop_master && echoRun "git flow hotfix start -F --showcommands $release"
}

gfhp() {
  release=`sanitize $1`
  gup_develop_master && echoRun "git flow hotfix publish --showcommands $release"
}

# Changed to
# 1. Be sure to sync up remotes and pull --rebase
# 2. Not push
gfhf() {
  gup_develop_master && echoRun "git flow hotfix finish -Fn --showcommands $(gitFlowNameForCurrentBranch)" &&
        echo "Confirm merge and then push master and develop" || echo "Examine error messages!"
}

I’ve created a repo with my ZSH setup: