warzone2100/tools/gitsvngateway/gitsvngateway

311 lines
8.1 KiB
Bash
Executable File

#!/bin/bash
# A tool to move commits from svn to git and from git to svn
# Copyright (C) 2009 Warzone Resurrection Project
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
function echo_c {
echo -n -e "$color"
echo $*
echo -n -e "$reset"
}
function is_merge {
# check if this commit has two parents
git show --pretty=format:%P $1 | head -n1 | grep -q " "
}
function cherry_pick_from {
if is_merge $1
then
mainline=-m1
else
mainline=
fi
git cherry-pick $mainline $1
}
function same_commit {
log_lines=`git log --pretty=oneline $1..$2 | wc -l`
if [ $log_lines -eq 0 ]
then
return 0
else
return 1
fi
}
function branch_exists {
git rev-parse --verify $1 > /dev/null 2>&1
}
function usage {
cat << EOF
Usage: gitsvngateway [options] LOCAL_BRANCH REMOTE_SVN_BRANCH
Put new commits from LOCAL_BRANCH and REMOTE_SVN_BRANCH on top of each other and commit to SVN and push to REMOTE
REMOTE is "origin" by default
Example: gitsvngateway master svn/trunk
Options:
-h, --help Display this message
-n, --dry-run Do not push or commit to SVN and leave branches in their original state
-p, --prefix=PREFIX Use PREFIX instead of "_gitsvngateway/" for branches
-r, --remote=REMOTE git push to REMOTE instead of to "origin"
-a, --automatic When there is a merge conflict clean up and abort
-f, --no-fetch Do not do an initial fetch to see if there are new commits from SVN
EOF
exit 1
}
function revert_and_exit {
echo_c "== Reverting changes and exitting"
git rebase --abort > /dev/null
git reset -q --hard > /dev/null
git checkout -q $p/new/${git}
git branch -f ${git} $p/new/${git}
git checkout -q ${git}
git branch -f $p/${git} $p/backup/${git}
git branch -D $p/backup/${git} > /dev/null
git branch -D $p/tocommit/${svn} > /dev/null
git branch -D $p/new/${git} > /dev/null
rm -f $mergefile
exit $1
}
color="\033[35m"
reset="\033[0m"
p=_gitsvngateway
dry_run=false
remote=origin
automatic=false
no_fetch=false
squash=false
TEMP=`getopt -o hnp:r:afs --long help,dry-run,prefix:,remote:,automatic,no-fetch,squash \
-n 'gitsvngateway' -- "$@"`
if [ $? != 0 ] ; then usage; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"
while true ; do
case "$1" in
-h|--help) usage; ;;
-n|--dry-run) dry_run=true; shift;;
-p|--prefix) p="$2"; shift 2;;
-r|--remote) remote="$2"; shift 2;;
-a|--automatic) automatic=true; shift;;
-f|--no-fetch) no_fetch=true; shift;;
-s|--squash) squash=true; shift;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
git=$1
svn=$2
mergefile=$p.$git.$svn.merge
mergefile=${mergefile//\//_}
# svn -> git
if ! branch_exists $p/${git} || ! branch_exists $p/${svn}
then
echo_c "== Storing pre-commit states"
git branch $p/${git} ${git}
git branch $p/${svn} ${svn}
echo_c "== You need to run this command on all branches you want to keep in sync."
echo_c "== Otherwise it will not be able to determine which commits from git svn fetch are new."
echo_c "== Please do this now and restart this script (with the same arguments) to continue."
exit
fi
if ! branch_exists $p/tocommit/${svn}
then
if ! $no_fetch
then
echo_c "== Fetching commits from SVN"
$dry_run || git svn fetch
fi
if same_commit $p/${svn} ${svn} && same_commit $p/${git} ${git}
then
echo_c "== No new commits from SVN and no new local commits"
exit
fi
# so we can reset it later when the user decides to abort
git branch $p/backup/${git} $p/${git}
# mark current state so we know what the local changes were we need to commit to SVN
git branch -f $p/new/${git} ${git}
if same_commit $p/${svn} ${svn}
then
echo_c "== No new changes from SVN"
else
echo_c "== Rebasing commits from ${svn} onto ${git}"
git branch -f $p/new/${svn} ${svn}
if ! git rebase --onto ${git} $p/${svn} $p/new/${svn}
then
if $automatic
then
revert_and_exit 2
else
echo_c "== Finish the rebase and restart this script (with the same arguments)"
exit 2
fi
fi
git branch -f ${git} $p/new/${svn}
git checkout -q ${git}
git branch -D $p/new/${svn} > /dev/null
fi
git branch $p/tocommit/${svn} ${svn}
rm -f $mergefile # make sure we start a new merge
fi
if branch_exists $p/tocommit/${svn}
then
# git -> svn
echo_c "== Rebasing commits from ${git} onto ${svn}"
git checkout -q $p/tocommit/${svn}
hashes=`git log --pretty=oneline --first-parent $p/${git}..$p/new/${git} | cut -f 1 --delimiter=" " | tac`
for hash in $hashes; do
if is_merge $hash
then
if ! $squash
then
echo_c "== Merge detected for $hash"
echo_c "== Commits contained in this merge:"
git log --pretty=oneline $p/${git}..$hash | tail -n+2
amount=`git log --pretty=oneline $p/${git}..$hash | tail -n+2 | wc -l`
echo_c "== $amount commit(s) found, will unravel if 15 or less"
if [ $amount -le 15 ]
then
echo_c "== Going ahead with unraveling the merge"
if ! [ -s "$mergefile" ]
then
git log --pretty=oneline $p/${git}..$hash | cut -f 1 --delimiter=" " | tail -n+2 | tac > $mergefile
fi
hashes2=`cat $mergefile`
echo_c "== Commits still to merge:"
cat $mergefile
for hash2 in $hashes2; do
echo_c "== Merging $hash2"
# remove the first line from the merge file
sed -i '1d' $mergefile
if ! cherry_pick_from $hash2
then
if $automatic
then
revert_and_exit 2
else
echo_c "== Could not merge! Please fix, commit and restart the script."
echo_c "== To use the already existing log message use:"
echo_c "git commit -a -C $hash2"
exit 2
fi
fi
done
# cleanum
rm $mergefile
# to keep track of where we are
git branch -f $p/${git} $hash
echo_c "== Done processing the merge"
continue
else
echo_c "== Squashing this merge"
fi
fi
fi
# to keep track of where we are in case of a failed merge
git branch -f $p/${git} $hash
if ! cherry_pick_from $hash
then
if $automatic
then
revert_and_exit 2
else
echo_c "== Could not merge! Please fix, commit and restart the script."
echo_c "== To use the already existing log message use:"
echo_c "git commit -a -C $hash"
exit 2
fi
fi
done
# everything is done, try to get it back into SVN and origin
if ! same_commit ${svn} $p/tocommit/${svn}
then
echo
echo_c "== These changes are staged for commit to SVN:"
git log ${svn}..$p/tocommit/${svn} | cat
if ! $automatic
then
echo_c -n -e "\nDo you want to commit these changes to SVN? [Y/n]: "
read -n1 answer
echo_c
fi
if $automatic || [ "x$answer" == "x" ]
then
echo_c "== Committing to SVN"
$dry_run || git svn dcommit
echo_c "== Getting back the changes from SVN"
$dry_run || git svn fetch
else
revert_and_exit 1
fi
else
echo_c "== Nothing to commit to SVN"
fi
git checkout -q ${git}
if ! same_commit $remote/${git} ${git}
then
echo_c "== Pushing the changes to $remote"
$dry_run || git push $remote ${git} || exit 1
else
echo_c "== No local changes, so not pushing to $remote"
fi
if ! $dry_run
then
echo_c "== Marking current state of ${git} and ${svn}"
git branch -f $p/${git} ${git}
git branch -f $p/${svn} ${svn}
else
revert_and_exit 0
fi
git branch -D $p/backup/${git} > /dev/null
git branch -D $p/tocommit/${svn} > /dev/null
git branch -D $p/new/${git} > /dev/null
exit 0
fi
echo_c "== Not supposed to reach this line!"
exit 1