In last two weeks I was working on our Git integration. At least I got rid of our SVN server having moved everything into Git repository. In this post I'll show how the migration was done and what commands I did use during the process.
Retrieving project from SVN
Firstly I had to retrieve the content from SVN and add to a git repository.
Standard SVN project directory structure
There are two ways to do this.
mkdir <<git_directory>>
cd <<git_directory>>
mkdir <<project_directory>>
cd <<project_directory>>
git svn init https://host/svn/Path/Project
git svn fetch
The shorter way to do the same:
mkdir <<git_directory>>
cd <<git_directory>>
git svn clone https://host/svn/Path/Project
This second one will generate a Project directory for the project automatically.
Non-standard SVN project directory structure
At the company we are using non standard SVN directory structure. The difference in our case is quite small: we have a second "tags" directory, called "releases" for the release tags. Therefore it had to be added to git svn commands to recognise these directories too.
Parameters in use
- -s - standard svn directory structure
- -t tags - defines (additional) "tags" directory (multiple can be used)
- -T trunk - defines "trunk"'s directory
- -b branches - defines (additional) "branches" directory (multiple can be used)
Comment: It is a bit strange that git svn clones svn tags as git branches. It is because the svn tags are mutable but git tags are not. You can commit changes on svn tags even if it is not a good practice.
For further parametes visit the git help page.
mkdir <<project_directory>>
cd <<project_directory>>
git svn init https://host/svn/Path/Project -s -t tags -t releases
git svn fetch
or
git svn clone https://host/svn/Path/Project -s -t tags -t releases
Checkout branch and creating local branches
To get the branches and tags on you current local git repository you can use the following commands:
git branch- returns the local branches (and the master)git branch -r- returns the remote branches (and the trunk)git branch -a- returns the local and remote branchesgit tag- returns the (local) tags
Now you can see what branches you can use. To check out one of them you can use the checkout command.
Comment: If you check out a remote branch and you commit something on that, it won't move the branch pointer because it is on the remote system (server). If there is no pointer on a commit, than it can be identified by the hash code. But to remember such a hash code is not easy even though it can be looked up from the history. Therefore the suggested way is to create a local branch for each remote branch what is in use
git checkout <branchname>- checks out a branch. It there was no local branch with the given name, it would check out the remote one.git checkout remotes/<remotebranchname>- checks out a remote branch with the given name.git checkout -b <newbranch> <branchname>- checks out a branch into a new local branchgit branch <oldbranch> <newbranch>- creates a new branch pointer pointing to the exactly the same commit.
Commiting changes on git repository
Now, you have a checked out branch what you can work on. During your work you can commit back your changes into your local repository. To do this you have to use the commit command. The commit will push all the changes what is in the index. So before you commit the changes, you have to add the to the index using the add command.
git add <filename>git add -Agit add -i
And now you can commit the changes
git commit -m "<Commit comment>"- commits into the local repository what is in the index.git commit -m "<Commit comment>" -a- commits all changes automatically - without add
Synchronization between git and svn
Pushing changes back to SVN
Before (d)committing make sure you are on the right branch. Push the commits upstream- git svn dcommit - pushes everything
- git svn dcommit --interactive - asking which commit would be pushed to upstream
Updating Git repository from svn
git SVN rebase- retrieves the changes from the server and rebases the local modifications on it. This operation effects only the current branch.git SVN fetch- retrieve the changes from the SVN but doesn't make the rebase. If you use this you have to take care of the linear history what SVN supports.
Adding new branch to SVN repository
git svn branch -n -m "<<Commit comment>>" <<branchName>>
git svn branch -m "<<Commit comment>>" <<branchName>>
http://stackoverflow.com/questions/266395/git-svn-how-do-i-create-a-new-svn-branch-via-git
Merging branches
Merging branches in Git SVN can be a tedious job. The reason is the SubVersion doesn't support anything else just linear history. When you merge an other branch, it will appear in your SVN history as one commit and you can't follow up the changes on the other branch. Unlike SVN, Git supports forking history. The SVN way therefore to squash (aggregate) your changes on the other branch (it's not a big thing, because Git stores snapshots instead of deltas) and merge them back. So, how to do this:
git checkout <<mainBranch>>
git merge --squash <<subBranch>>
#solve conflict
git commit -m "Merge" #if it's neccessary
git svn dcommit -n #to check that the prober branch is selected.
git svn dcommit
SVN commit after Git rebase
Though we switched to use Git, the company policy is still being SVN. Therefore I have to commit back everything from the Git repository every day at the end of the day.
The team use the Git repository as a real Git repository and when a feature is done, sometimes the feature branch is not squashed but rebased. And the master branch pointer is merged to the end of the history.
This step is not SVN compatible because the main branch will contain every commit from the feature branch instead of a squashed commit.
It's made even worse that the git svn dcommit always commit everything onto the branch of which pointer is closest to the current pointer. Therefore the changes will be committed on the feature branch instead of the main branch.
TODO add graph
To solve this situation I'm usually using the following steps:
git checkout -b tmp remotes/<<main branch>>- checking out the current SVN main branch pointer into the newly created tmp branch.git svn rebase- fetch out the changes from svn and move the pointer.git merge --squash <<local main branch>>- merging the changes back to the tmp pointer as one commitgit commit -m "Merge"- committing the changesgit svn dcommit -n- double checking the SVN branch name.git svn dcommit- pushing the changes up to SVN repository.git checkout <<local main branch>>git merge tmp- moving the tmp pointer to the end of the branchgit branch -D tmp- removing the tmp pointer
With these steps we can maintain our Git history and SVN history as well.
after this sometimes it is needed the local pointer to be moved to the remote pointer. git mergeIgnore list
Creating ignore list git svn show-ignore > .gitignoreCreating tags for svn tags
$ git-for-each-ref refs/remotes/origin/tags | cut -d / -f 5- | while read ref do git tag -a "$ref" -m"say farewell to SVN" "refs/remotes/origin/tags/$ref" git push origin ":refs/heads/tags/$ref" git push origin tag "$ref" done
Adding release branches
git config --add svn-remote.svn.tags Metro/UserService/releases/*:refs/remotes/releases/* git svn fetch -r 66000:HEAD --allCreating new branch and adding to the SVN
-n dry run git svn branch -m "<<branch creation commit message>>" <<branch name>>Start git daemon
git daemon --base-path=. --export-all --enable=receive-pack --verbose --reuseaddrReferences
http://gitready.com/advanced/2009/02/16/convert-git-svn-tag-branches-to-real-tags.htmlREMOVE
Merging branches
If there hasn't been any other commit on the SVN during your work there won't be any problem with the push (dcommit). As well as if you haven't made any change on the local Git repository whilst somebody has committed changes into the SVN, the fetch will work. But this is usually not the case and you have to manage the merging and probably you have to resolve the conflicts. To do it you need some basic knowledge about git merge and git rebase. Please visit the linked pages where you can find nice explanation of these commands.
after this sometimes it is needed the local pointer to be moved to the remote pointer. git merge