311 lines
8.1 KiB
Bash
Executable File
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
|