#!/bin/bash NEWSVNURL="file:///home/colin/svn-git/soft" SOFTWARE= if [ -d ".git" -a -f ".git/config" -a -f ".git/svn/.metadata" ]; then SOFTWARE=$(git config svn-remote.svn.fetch | cut -d':' -f1 | sed 's,/trunk,,' | trim) fi echo echo "Identified software name: $SOFTWARE" if [ -z "$SOFTWARE" ]; then echo "You must run this in a git-svn clone folder." >&2 exit 1 fi if [ -d ../$SOFTWARE.git ]; then echo "It seems the converted git repository already exists (../$SOFTWARE.git)" exit 1 fi REPLAY= if [ -f ../svn-import.log ]; then REPLAYDATA=$(grep ^$SOFTWARE, ../svn-import.log | tail -n 1) if [ -n "$REPLAYDATA" ]; then resetsha1=$(echo $REPLAYDATA | cut -d',' -f 2) revision=$(echo $REPLAYDATA | cut -d',' -f 3) morerevisionsx=$(echo $REPLAYDATA | cut -d',' -f 4) skiprevisionsx=$(echo $REPLAYDATA | cut -d',' -f 5) synthesized=$(echo $REPLAYDATA | cut -d',' -f 6) echo echo "I have found data from a previous run:" echo echo "Reset SHA1: $resetsha1" echo "Mageia Import SVN Revision: $revision" echo "Extra SVN Revisions to squash:$morerevisionsx" echo "SVN Revisions to skip: $skiprevisionsx" echo echo "Do you want to reply it? [Y/n]" read REPLAY if [ -z "$REPLAY" -o "Y" = "$REPLAY" ]; then REPLAY=y else REPLAY= fi fi fi remotes=$(git remote -v | cut -f1 | sort -u | xargs) if [ -n "$remotes" ]; then echo -n "Removing old remotes '$remotes'..." for remote in $remotes; do git remote rm $remote done echo "done" fi echo -n "Finding UUID for SVN Repository $NEWSVNURL... " NEWSVNUUID=$(svn info $NEWSVNURL | grep "^Repository UUID:" | sed 's/Repository UUID: //') echo $NEWSVNUUID echo -n "Updating SVN Remote config (old value: $(git config svn-remote.svn.url))... " git config svn-remote.svn.url $NEWSVNURL echo "done" echo -n "Updating Internal SVN metadata... " sed -i "s,reposRoot = .*$,reposRoot = $NEWSVNURL,;s,uuid = .*$,uuid = $NEWSVNUUID," .git/svn/.metadata echo "done" echo echo "OK now the fun stuff beings. You need to find the git commit at which you want to" echo "start the magiea import. This will typically require some investigation but the" echo "date will be around February 2011." echo "I will drop you to a shell. When you are done, simply copy the SHA1 sum and exit." echo "The commands you like want are 'git log trunk' and 'git show SHA1'" echo if [ -z "$REPLAY" ]; then echo "Press any key to contiue." read -n 1 bash echo echo "OK you're back :)" echo "What was the SHA1?" read resetsha1 fi if [ -z "$resetsha1" ]; then echo "You did not give any SHA1 sum. I will not reset the git repository and carry on importing" echo "from it's current state. You can quit this script and start again if this is incorrect." else echo "I got SHA1 '$resetsha1'. I hope that's correct". echo echo "I will now reset your git trunk and master branches." if [ -z "$REPLAY" ]; then echo "Is this OK? (press ctrl+c if not)" read -n 1 fi git update-ref -m "Rewind SVN Git to pre-Magiea import" refs/remotes/trunk $resetsha1 git checkout master git reset --hard trunk # Remove any heads that are newer than this (except trunk) resetreldate=$(git show -s --pretty='format:%ar' $resetsha1) resettimestamp=$(git show -s --pretty='format:%at' $resetsha1) echo "Removing any branches/tags newer than '$resetsha1' ($resetreldate)..." git for-each-ref --format='%(refname)' refs/remotes/tags/* refs/remotes/branches/* | \ while read ref; do # As svn tags show as new commits, we mustn't parse $ref directly, but # traverse back up the tag tree to find the first commit that *do not* # result in any changes (i.e. not an svn cp) and use that as our reference # comparison point. refsha1=$(git rev-parse "$ref":) parentref=$ref while [ $(git rev-parse --quiet --verify "$parentref"^: ) = "$refsha1" ]; do parentref="$parentref"^ done reftimestamp=$(git show -s --pretty='format:%at' $parentref) if [ $reftimestamp -gt $resettimestamp ]; then echo " Removing '$ref'" git update-ref -d $ref fi done echo "... done" fi echo echo "I will now run 'svn log' on the software. You should be able to guess the right revision" echo "to base the import on. When done, simply copy (or remember) the revision number and enter it" echo "into this script. You will also be able to (optionall) select several other commits which" echo "you wish to squash into this initial commit." echo if [ -z "$REPLAY" ]; then echo "Press any key to contiue." read -n 1 svn log $NEWSVNURL/$SOFTWARE | less echo echo "OK you're back :)" echo "What was the initial revision number?" read revision fi revision=$(echo $revision | sed 's/r//g') revision=$(( $revision + 0 )) echo "I got revision '$revision'. I hope that's correct". echo if [ -z "$REPLAY" ]; then echo "Now enter any further commits you wish to squash into our initial commit:" read morerevisionsx fi morerevisionsx=$(echo $morerevisionsx | trim) if [ -n "$morerevisionsx" ]; then for rev in $morerevisionsx; do rev=$(echo $rev | sed 's/r//g') rev=$(( $rev + 0 )) if [ $rev -gt 0 ]; then morerevisions="$morerevisions $rev" fi done echo echo "OK, you said you also wanted to squash the following commits:" echo " $morerevisions" echo fi if [ -z "$REPLAY" ]; then echo "Now enter any commits you wish to skip completely (e.g. for commits+reverts)" read skiprevisionsx fi skiprevisionsx=$(echo $skiprevisionsx | trim) if [ -n "$skiprevisionsx" ]; then for rev in $skiprevisionsx; do rev=$(echo $rev | sed 's/r//g') rev=$(( $rev + 0 )) if [ $rev -gt 0 ]; then skiprevisions="$skiprevisions $rev" fi done echo echo "OK, you said you wanted to skip the following commits:" echo " $skiprevisions" echo fi echo echo "The next step is the dangerous bit. We will clear out all files in the current" echo "working copy, run an svn export and then drop you to a shell to investigate the differences" echo "In that shell you must git add any new files that appear and ensure you are happy with" echo "the generate state of things." echo echo "Continue? (press ctrl+c if not)" read -n 1 git reset --hard trunk resetsha1=$(git rev-list --max-count=1 HEAD) # The below is a "safer" verion of rm -rf * that also deletes hidden files find . -depth -not -iwholename '.' -not -iwholename "./.git" -not -iwholename "./.git/*" -not -iwholename "./.git/.*" -delete # We have to create a subversion repository here for the import as we need a repository # for the svn merging below... svn sucks. SVNPATH=$(realpath ../$SOFTWARE.svn) rm -rf $SVNPATH mkdir -p $SVNPATH/export cd $SVNPATH/export svn export --force --ignore-keywords --ignore-externals -r $revision $NEWSVNURL/$SOFTWARE/trunk . cd - svnadmin create $SVNPATH/repo svnrepo=file://$SVNPATH/repo svn import -m "Import" $SVNPATH/export $svnrepo svn checkout $svnrepo $SVNPATH/checkout cd $SVNPATH/checkout if [ -n "$morerevisions" ]; then for rev in $morerevisions; do svn merge -c $rev --ignore-ancestry --accept theirs-full --allow-mixed-revisions $NEWSVNURL/$SOFTWARE/trunk . svn commit -m "Merged r$rev" if [ $? -ne 0 ]; then echo echo "Merging in svn revision $rev failed. You need to rethink your import strategy." echo exit 1 fi done fi cd - svn export --force $svnrepo . rm -rf $SVNPATH echo echo "Dropping you to a shell. Use 'git status', 'git diff' and 'git add'." echo "DO NOT run 'git commit', I'll do that later for you." bash echo echo "OK you're back :)" echo "If everything is OK, and you're ready to continue, then hit return." read -n 1 COMMITTXT=$(mktemp /tmp/commitmsg.XXXXXX) cat >$COMMITTXT <>$COMMITTXT if [ -n "$morerevisions" ]; then for rev in $morerevisions; do svn log -r $rev $NEWSVNURL/$SOFTWARE | tail -n +2 >>$COMMITTXT done fi COMMITDATE=$(svn log -r $revision $NEWSVNURL/$SOFTWARE | head -n2 | tail -n1 | cut -d'|' -f3 | cut -d'(' -f1 | trim) env GIT_COMMITTER_NAME="Mageia SVN-Git Migration" \ GIT_COMMITTER_EMAIL="svn-git-migration@mageia.org" \ GIT_COMMITTER_DATE="$COMMITDATE" \ GIT_AUTHOR_NAME="Mageia SVN-Git Migration" \ GIT_AUTHOR_EMAIL="svn-git-migration@mageia.org" \ GIT_AUTHOR_DATE="$COMMITDATE" \ git commit -a --file=$COMMITTXT rm -f $COMMITTXT echo echo "OK, looks like we're good to go :)" echo "Press any key to continue with the subversion import process". read -n 1 git svn gc sha1=$(git rev-list --max-count=1 HEAD) echo echo -n "Updating trunk branch to include synthesized commit $sha1... " git update-ref -m "Reset to synthesized commit representing Magiea cleaned import (SVN Git)" refs/remotes/trunk $sha1 echo "done" echo -n "Faking git-svn metadata to allow continuation... " printf "0000000: %08x%s\t................" $revision $sha1 | xxd -r -c24 >.git/svn/refs/remotes/trunk/.rev_map.$NEWSVNUUID echo "done" # Keep a small log of the sha1's etc. if [ -z "$REPLAY" ]; then if [ ! -f ../svn-import.log ]; then echo "software,startingcommit,startingrevision,otherrevisions,skiprevisions,fakecommit" >../svn-import.log fi echo "$SOFTWARE,$resetsha1,$revision,$morerevisions,$skiprevisions,$sha1" >>../svn-import.log else sed -i "s/,$synthesized\$/,$sha1/" ../svn-import.log fi echo "Continuing svn fetch" if [ -n "$morerevisions" ]; then for rev in $morerevisions; do git svn fetch -A ../authors-transform.txt -r $(( $revision + 1 )):$(( $rev - 1 )) revision=$rev done fi # Note the revisions to skip have to be after any squashed revisions which we # should really check for.... if [ -n "$skiprevisions" ]; then for rev in $skiprevisions; do git svn fetch -A ../authors-transform.txt -r $(( $revision + 1 )):$(( $rev - 1 )) revision=$rev done fi git svn fetch -A ../authors-transform.txt -r $(( $revision + 1 )):HEAD git reset --hard trunk echo echo "done" echo echo "Now creating bare git repository" git init --bare ../$SOFTWARE.git git remote rm origin git remote add origin ../$SOFTWARE.git #git push --set-upstream master git push origin master 'refs/remotes/*:refs/heads/*' pushd ../$SOFTWARE.git git branch -D trunk git branch -D origin/master 2>/dev/null # Tagging logic inspired by https://github.com/nothingmuch/git-svn-abandon/blob/master/git-svn-abandon-fix-refs git for-each-ref --format='%(refname)' refs/heads/tags | cut -d / -f 4 | \ while read tag; do ref="refs/heads/tags/$tag" refsha1=$(git rev-parse "$ref":) # Find the oldest ancestor for which the tree is the same parentref="$ref" while [ $(git rev-parse --quiet --verify "$parentref"^:) = "$refsha1" ]; do parentref="$parentref"^ done parent=$(git rev-parse "$parentref") # If this ancestor is in master then we can just tag it # otherwise the tag has diverged from master and it's actually more like a # branch than a tag merge=$(git merge-base master $parent) if [ "$merge" = "$parent" ]; then targetref=$parent else targetref="$ref" fi # create an annotated tag based on the last commit in the tag, and delete the "branchy" ref for the tag git show -s --pretty='format:%s%n%n%b' "$ref" | \ env GIT_COMMITTER_NAME="$(git show -s --pretty='format:%an' "$ref")" \ GIT_COMMITTER_EMAIL="$(git show -s --pretty='format:%ae' "$ref")" \ GIT_COMMITTER_DATE="$(git show -s --pretty='format:%ad' "$ref")" \ GIT_AUTHOR_NAME="$(git show -s --pretty='format:%an' "$ref")" \ GIT_AUTHOR_EMAIL="$(git show -s --pretty='format:%ae' "$ref")" \ GIT_AUTHOR_DATE="$(git show -s --pretty='format:%ad' "$ref")" \ git tag -a -F - "$tag" "$targetref" git update-ref -d "$ref" done git gc --aggressive popd