Skip to content

Instantly share code, notes, and snippets.

@michal-niedzwiedzki
Last active August 29, 2015 14:21
Show Gist options
  • Save michal-niedzwiedzki/e5fb4d355006fc2b5c90 to your computer and use it in GitHub Desktop.
Save michal-niedzwiedzki/e5fb4d355006fc2b5c90 to your computer and use it in GitHub Desktop.
#!/bin/bash
##
## Candidate
##
## A tool to manage remote branches selected as candidates for release.
## Run it inside git repo working copy. Make sure all changes are staged.
## Local database saved in .candidate.sqlite in repo root.
##
## Usage:
##
## candidate show
## - to seee list of all candidate branches selected for release
## in "repo branch commit" format
##
## candidate pick <remote-repo> <remote-branch>
## - to select remote repo/branch as a candidate for release
## (will perform hard reset if unsuccessful)
##
## candidate drop <repote-repo> <remote-branch>
## - to withdraw remote repo/branch candidacy
##
## candidate rebuild
## - to pull all branches previously selected as candidates
## (will perform hard reset on conflicting branches)
##
## candidate dup <local-branch>
## - to duplicate all candidate branches from another on another local branch to current
##
## candidate start-over
## - to scrap current local branch and its remote ustream branch
##
## candidate --help
## - to display this very help message and exit
##
PROGRAM=$0
PROGNAME=$( basename $0 )
# database setup
sqlite=sqlite3
schema=2
# check tooling
which $sqlite > /dev/null
if [ "$?" != "0" ]
then
echo "$PROGNAME: could not find sqlite executable: $sqlite" >&2
exit 1
fi
which git > /dev/null
if [ "$?" != "0" ]
then
echo "$PROGNAME: could not find git executable" >&2
exit 1
fi
# check command
COMMAND=$1
if [ -z $COMMAND ]
then
echo "$PROGNAME: command parameter missing, check $PROGNAME --help" >&2
exit 1
fi
shift
# check --help
if [ "--help" == "$COMMAND" ]
then
cat $0 | grep -e "^##" | sed "s/^##/ /g"
exit 0
fi
# check repo root dir and current branch
ROOT=$( git rev-parse --show-toplevel )
CURRENT=$( git symbolic-ref --short HEAD )
if [ -z $CURRENT ]
then
echo "$PROGNAME: could not determine branch" >&2
exit 1
fi
git diff --exit-code --quiet
if [ "$?" != "0" ]
then
echo "$PROGNAME: unstaged changes found, aborting" >&2
exit 1
fi
# check sqlite file and update schema if needed
DB=$ROOT/.candidate.sqlite
if [ ! -f $DB ]
then
$sqlite $DB "CREATE TABLE schema ( version INT NOT NULL )"
fi
current_schema=`$sqlite $DB "SELECT COALESCE(max(version), 0) FROM schema"`
if [ "$schema" != "$current_schema" ]
then
test $current_schema -lt 1 && $sqlite $DB "CREATE TABLE candidates ( base VARCHAR NOT NULL, repo VARCHAR NOT NULL, branch VARCHAR NOT NULL )"
test $current_schema -lt 2 && $sqlite $DB "ALTER TABLE candidates ADD COLUMN hash VARCHAR"
$sqlite $DB "INSERT INTO schema VALUES ($schema)"
fi
# command "show"
if [ "show" == "$COMMAND" ]
then
$sqlite $DB --separator " " "SELECT repo, branch, hash FROM candidates WHERE base = '$CURRENT'"
fi
# command "pick"
if [ "pick" == "$COMMAND" ]
then
repo=$1
test -z $repo && echo "$PROGNAME: missing remote repo name" >&2 && exit 1
shift
branch=$1
test -z $branch && echo "$PROGNAME: missing remote branch name" >&2 && exit 1
shift
git pull --no-ff --no-edit $repo $branch
if [ $? -ne 0 ]
then
echo "$PROGNAME: conflicts, resetting head, not picking as candidate" >&2
git reset HEAD --hard
exit 1
fi
commit=$( git rev-parse --short --verify HEAD )
$sqlite $DB "INSERT INTO candidates VALUES ('$CURRENT', '$repo', '$branch', '$commit')"
echo "$PROGNAME: $repo/$branch selected as candidate for $CURRENT at $commit"
fi
# command "drop"
if [ "drop" == "$COMMAND" ]
then
repo=$1
test -z $repo && echo "$PROGNAME: missing remote repo name" >&2 && exit 1
shift
branch=$1
test -z $branch && echo "$PROGNAME: missing remote branch name" >&2 && exit 1
shift
$sqlite $DB "DELETE FROM candidates WHERE base = '$CURRENT' AND repo = '$repo' AND branch = '$branch'"
fi
# command "rebuild"
if [ "rebuild" == "$COMMAND" ]
then
updates=$( $sqlite $DB "SELECT DISTINCT repo, branch FROM candidates WHERE base = '$CURRENT'" )
for ln in $updates
do
repo=$( echo $ln | cut -d "|" -f 1 )
branch=$( echo $ln | cut -d "|" -f 2 )
git pull --no-ff --no-edit $repo $branch
if [ $? -ne 0 ]
then
echo "$PROGNAME: conflicts, resetting head, skipping $repo $branch" >&2
git reset HEAD --hard
fi
done
fi
# command "dup"
if [ "dup" == "$COMMAND" ]
then
branch=$1
test -z $branch && echo "$PROGNAME: missing local branch name" >&2 && exit 1
shift
$sqlite $DB "INSERT INTO candidates SELECT '$CURRENT', repo, branch FROM candidates WHERE base = '$branch'"
fi
# command "start-over"
if [ "start-over" == "$COMMAND" ]
then
git branch -D $CURRENT && git push :$CURRENT
fi
@michal-niedzwiedzki
Copy link
Author

This program helps selecting branches as candidates for release. All it really does is: it keeps record of merged branches and automates pulling and recreating integration branches. Here's the flow:

  1. Create integration branch, say release-1.0.0: git checkout -b release-1.0.0 master
  2. To pick branch "awesome-feature" in remote repo "JohnDoe" as a candidate: candidate pick JohnDoe awesome-feature
  3. Pick additional branches if you want. More branches can be added at any stage.
  4. Integrate added branches: candidate rebuild
  5. Conflicts? Remove problematic branch: candidate drop SloppyCoder shitty-code and git reset HEAD --hard``, then rebuild:candidate rebuild```

To show list of branches in build: candidate show

Something went terribly bad?

  1. Just create another integration branch: git checkout -b release-1.0.0-FIXED master
  2. Copy build from old branch: candidate dup release-1.0.0
  3. Remove problematic branches: candidate drop SloppyCoder shitty-code
  4. Carry on.

To redo the integration from scratch: candidate start-over. Warning: this will remove current branch from local AND remote upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment