Created
December 11, 2010 17:40
-
-
Save twidi/737492 to your computer and use it in GitHub Desktop.
A script to transform a linear flow in a git repository (all in master) in a flow named "git-flow" (http://nvie.com/posts/a-successful-git-branching-model/) with a master branch, a develop one, and one for each feature, hotfixe and release.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Explanations : http://bit.ly/gs2qpL | |
# give optionnaly a revision from where to start | |
# if not given initial_ref will be the first ref available | |
initial_ref='757794e524755d2eebc6d881fea026c308a11fd5' | |
# declare below all the steps whith this format : | |
# 'version type name ref' \ | |
# - version is the tag which will be applied on the future master branch. Can be "-" to no tag it Iit when will be just merged in the develop branch without release and merge to master) | |
# - type is the type of branch to create. Can be "feature" or "hotfix". | |
# - name is the name of the branch (full name for a feature, but will be prefixed by "hotfix-" in other case) | |
# (if no name is provided, the version will be used, usefull for a hotfix) | |
# - ref if the ref in the current master branch from which the new branch will start | |
# dont forget to add a space before the slash at the end of each line, and do not add any space or quote in each parameter | |
declare -a all_steps=(\ | |
'1.0.1 feature better-1.0-usability 24be26a54b6a116d5c4843b23e37944ff11a51ea' \ | |
'1.0.2 feature first-full-rewrite-1.0 a3179cc084536194fcd86297f5134a86c74958c4' \ | |
'1.0.3 feature base-debug-engine 2c20dbf610eb0536c4f08b948ed441aab19188cf' \ | |
'1.0.4 feature webkit-long-press-bug 64e7492ebe21513033c37175ec9de917092f3fc6' \ | |
'1.0.5 feature unicode-categories 6056a7efbadc59718d784cc38a47086eb4e4af28' \ | |
'1.0.6 feature quick-context-access a88fdf2f1ecf91a6b2ebd1f7fc65dd9adbd7bf82' \ | |
'1.0.7 feature simple-gestures 7581e38bbab36f4bb61f0030820c36f43278fab6' \ | |
'1.0.7-2 hotfix - 2fdf164768eafa65a2256534ebc1cb87ad663628' \ | |
'1.0.8 feature quick-itemview-context-access 74f0d41a8e18c3d8cf42f29bbc0bc9b3118cc68e' \ | |
) # all_steps | |
# change this to "no" if you don't want branches (features, releases and hotfixes) to be pushed to remote | |
sync_branches_remotely="no" | |
# change this to "yes" if you want branches (features, releases and hotfixes) to be deleted after being merged | |
delete_branches="yes" | |
# change this to "no" if you don't want the old "master" branch be deleted (will be renamed as "legacy") | |
delete_old_master="yes" | |
# you can change some git names here | |
origin=origin | |
master=master | |
newmaster=newmaster | |
develop=develop | |
base_release=release- | |
base_hotfix=hotfix- | |
########## you should not change anything below this line ########## | |
# some usefull functions | |
# return the date of the merge (penultimate one : the one before the merge to have the date of the original last commit) | |
function git-merge-date { | |
echo `git log | grep 'Date:' | head -n 2 | tail -n 1 | sed 's/Date:\s\+//'` | |
} # git-merge-date | |
# return the last ref (used to get the one of the last commit) | |
# if a parameter is given, it's a branch used to restric the list of refs | |
function git-last-ref { | |
local branch="$1" | |
echo `git log $branch | grep '^commit' | head -n 1 | sed 's/commit\s\+//'` | |
} # git-last-ref | |
# get the current branch | |
function git-current-branch { | |
echo `git branch | grep '^*' | sed 's/^*\s\+//'` | |
} # git-current-branch | |
# change the date of a commit | |
# https://git.wiki.kernel.org/index.php/GitFaq#How_can_I_tweak_the_date_of_a_commit_in_the_repo.3F | |
# take 2 parameters : | |
# - the ref of the commit | |
# - the new date (you can use git-rev-date to see the format) | |
function git-update-commit-date { | |
local commit="$1" | |
local date="$2" | |
if [ "$date" ] | |
then | |
git filter-branch --env-filter \ | |
"if test \$GIT_COMMIT = '$commit' | |
then | |
export GIT_AUTHOR_DATE | |
export GIT_COMMITTER_DATE | |
GIT_AUTHOR_DATE='$date' | |
GIT_COMMITTER_DATE='$date' | |
fi" && | |
rm -fr "$(git rev-parse --git-dir)/refs/original/" | |
fi # date | |
} # git-update-commit-date | |
# cherry pick a commit and change the date of the new commit to be the same | |
# you can also pass a branch to get the date (else the current will be used) | |
function git-cherry-pick-and-date { | |
local commit="$1" | |
local branch="$2" | |
local date=`git log $branch --pretty=format:'%H %cD' | grep "^$commit" | sed "s/^$commit //"` | |
git cherry-pick $commit | |
last_error=$? | |
if [ "$last_error" == 0 ] | |
then | |
local last_ref=`git-last-ref` | |
git-update-commit-date $last_ref "$date" | |
fi # error | |
} | |
# merge with a branch given as parameter, and set the merge date to the last commit merged | |
function git-merge { | |
local branch="$1" | |
local current=`git-current-branch` | |
if [ "$current" == "$newmaster" ] | |
then | |
current=$master | |
fi # newmaster | |
git merge --no-ff -m "Merge branch '$branch' into '$current'" $branch | |
local last_ref=`git-last-ref` | |
local merge_date=`git-merge-date` | |
git-update-commit-date $last_ref "$merge_date" | |
} | |
# create a branch from a starting ref, and optionnaly sync it remotely | |
# with the help of http://djwonk.com/blog/2009/04/18/tracking-remote-git-branches/ | |
# take 3 parameters : | |
# - the branch name | |
# - the ref to use as a starting point | |
# - "yes" if you want the branch to be sync remotely | |
# The branch will be the current one after being created | |
function git-create-branch { | |
local branch="$1" | |
local start_ref="$2" | |
local sync_branch_remotely="$3" | |
git checkout -b $branch $start_ref | |
if [ "$sync_branch_remotely" == "yes" ] | |
then | |
git push $origin $branch | |
git checkout $master | |
git branch -f $branch $origin/$branch | |
git checkout $branch | |
fi # sync_branch_remotely | |
} | |
# start here | |
echo "==========> START" | |
git checkout $master | |
last_error=0 | |
# find existing branches | |
old_branches=`git branch | grep -v '^*'` | |
# check initial ref | |
if [ -z "$initial_ref" ] | |
then | |
initial_ref=`git log | grep ^commit | tail -n 1 | sed 's/commit\s\+//'` | |
fi # initial_ref | |
# some configuration | |
git config branch.autosetupmerge true | |
# prepare main branches | |
git-create-branch "$develop" "$initial_ref" "yes" | |
git-create-branch "$newmaster" "$initial_ref" "yes" | |
# prepare initial vars | |
last_develop_ref=$initial_ref | |
last_master_ref=$initial_ref | |
prev_ref=$initial_ref | |
# loop on each step | |
for i_step in `seq 0 \`expr ${#all_steps[@]} - 1\`` | |
do | |
# split fields for this step | |
declare -a step=(${all_steps[$i_step]}) | |
version=${step[0]} | |
mode=${step[1]} | |
name=${step[2]} | |
ref=${step[3]} | |
# new branch name | |
branch=$name | |
source=$develop | |
if [ "$mode" == "hotfix" ] | |
then | |
source=$newmaster | |
if [ "$name" == "-" ] | |
then | |
if [ "$version" == "-" ] | |
then | |
branch="$base_hotfix$i_step" | |
else # version - | |
branch="$base_hotfix$version" | |
fi # version - | |
else # name - | |
branch="$base_hotfix$name" | |
fi # name - | |
fi # hotfix | |
echo "==========> Step $i_step: version=$version, mode=$mode, name=$name, ref=$ref => branch $branch" | |
# create branch | |
git-create-branch "$branch" "$source" "$sync_branches_remotely" | |
# cherry pick commits from master | |
for commit in `git rev-list --reverse $prev_ref..$ref` | |
do | |
git-cherry-pick-and-date $commit $master | |
if [ "$last_error" != 0 ] | |
then | |
read -p "The Cherry pick resulted in an error. You may want to press CTRL-C to abort (or any other key to let the script continue)" | |
last_error=0 | |
fi # last_error | |
done # for commit | |
# if hotfix, merge into master | |
if [ "$mode" == "hotfix" ] | |
then | |
if [ "$sync_branches_remotely" == "yes" ] | |
then | |
git push $origin $branch | |
fi # sync_branches_remotely | |
fi # hotfix | |
# always merge into develop | |
git checkout $develop | |
git-merge $branch | |
git push $origin $develop | |
# create a release | |
if [ "$version" != "-" ] | |
then | |
# create the branch | |
release_branch="$base_release$version" | |
git-create-branch "$release_branch" "$develop" "$sync_branches_remotely" | |
if [ "$sync_branches_remotely" == "yes" ] | |
then | |
git push $origin $release_branch | |
fi # sync_branches_remotely | |
# merge into master | |
git checkout $newmaster | |
git-merge $release_branch | |
git push $origin $newmaster | |
# merge into develop | |
git checkout $develop | |
git-merge $release_branch | |
git push $origin $develop | |
# then tag the release | |
git tag -a -m "version $version" "$version" | |
git push --tags | |
# delete the release branch | |
if [ "$delete_branches" == "yes" ] | |
then | |
git branch -d "$release_branch" | |
fi # delete_branches | |
fi # version | |
# delete branch | |
if [ "$delete_branches" == "yes" ] | |
then | |
git branch -d "$branch" | |
fi # delete_branches | |
# and now the save the ref as the prev_ref | |
prev_ref="$ref" | |
done # for i_step | |
# recreate old branches | |
for old_branch in $old_branches | |
do | |
echo "==========> Work on branch $old_branch" | |
# ask confirmation to see if we continue with this branch | |
echo "Do you want to recreate this branch ("$old_branch") ? Type Y/y to validate (any other key else to cancel)" | |
REPLY="" | |
read -p "> " -n 1 | |
echo "" | |
if [[ $REPLY =~ ^[Yy]$ ]] | |
then | |
git checkout $master | |
# temporary branch name | |
branch="gf_new_$old_branch" | |
# is the old branch remotely synced ? | |
sync_branch_remotely="" | |
if [ `git branch -r | grep "$origin/$old_branch"` ] | |
then | |
sync_branch_remotely="yes" | |
fi # git branch | |
# find the new start_ref | |
old_start_ref=`git merge-base $old_branch $master` | |
branch_info=`git log $old_branch --pretty=format:'%H %cD %s' | grep "^$old_start_ref" | sed "s/^$old_start_ref//"` | |
start_ref=`git log $develop --pretty=format:'%H %cD %s' | grep "$branch_info" | awk '{ print $1 }'` | |
# create the branch | |
git-create-branch "$branch" "$start_ref" "$sync_branch_remotely" | |
# apply all commits | |
old_last_ref=`git-last-ref $old_branch` | |
for commit in `git rev-list --reverse $old_start_ref..$old_last_ref` | |
do | |
git-cherry-pick-and-date $commit $old_branch | |
if [ "$last_error" != 0 ] | |
then | |
read -p "The Cherry pick resulted in an error. The work on this branch ($old_branch) will stop and the new branch will be deleted (Press any key to continue)" | |
echo "" | |
last_error=0 | |
git checkout $develop | |
git branch -D $branch | |
git push $origin :$branch | |
break | |
fi # last_error | |
done # for commit | |
# sync remotely if needed | |
if [ $sync_branch_remotely ] | |
then | |
git push $origin $branch | |
fi # sync_branch_remotely | |
git checkout $develop | |
# delete old branch | |
git branch -D $old_branch | |
if [ $sync_branch_remotely ] | |
then | |
git push $origin :$old_branch | |
fi # sync_branch_remotely | |
# rename new branch | |
git branch -m $branch $old_branch | |
if [ $sync_branch_remotely ] | |
then | |
git push $origin :$branch | |
git push $origin $old_branch:$old_branch | |
fi # sync_branch_remotely | |
git checkout $develop | |
git push $origin $develop | |
fi # REPLY (manage branch) | |
done # old_branch | |
# set newmaster the new master branch | |
# http://limi.co.uk/posts/renaming-master-branch-on-github | |
echo "==========> Move $newmaster to $master" | |
read -p "On Github, change the default branch to \"$newmaster\" (go to \"Admin\"). Then press any key to continue" | |
git checkout $develop | |
git branch -m $master legacy | |
git branch -m $newmaster $master | |
git push $origin :$master | |
git push $origin $master:refs/heads/$master | |
git push $origin legacy:refs/heads/legacy | |
read -p "On Github, change the default branch to \"$master\" (go to \"Admin\"). Then press any key to continue" | |
git push $origin :$newmaster | |
# delete the old master branch if wanted | |
if [ "$delete_old_master" == "yes" ] | |
then | |
git branch -D legacy | |
git push $origin :legacy | |
fi # $delete_old_master | |
echo " | |
==========> DONE !" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment