Today I needed to create an empty gh-pages
branch on one of my projects, and didn't want to fork the code on the master branch, then remove it all.
Using SVN repositories as git submodules
With the Subversion VCS, one way to import external modules or libraries into a code tree is by defining the svn:externals property of your repository. Subversion will then check out the specified revision (or the latest revision) of the other repository into your source tree when checking out your code.
Submodules are basically the same thing in the "git" world.
And since git can talk to subversion repositories with git svn, we should be able to specify a third-party SVN repository as a submodule in our own git repository, right? Sadly the answer is currently: No.
Here is a workaround that I have been using to at least achieve a similar effect, while keeping both SVN and git happy. This assumes that you have a local git repository that is also available somewhere else "up-stream" (such as github), and you want to import an external SVN repository into your source tree.
1: Add a tracking branch for SVN
Add a section referring to your desired SVN repository to your .git/config
file:
(...) [svn-remote "product-details"] url = http://svn.mozilla.org/libs fetch = product-details:refs/remotes/product-details-svn
Note that in the fetch
line, the part before the colon refers to the branch you want to check out of SVN (for example: trunk
), and the part after that will be our local remote branch location, i.e. product-details-svn
will be our remote branch name.
Now, fetch the remote data from SVN, specifying a revision range unless you want to check out the entire history of that repository:
git svn fetch product-details -r59506:HEAD
git will check out the remote branch.
2: clone the tracking branch locally
Now we have a checked-out SVN tracking branch, but to use it as a submodule, we must make a real git repository from it -- a branch of our current repository will keep everything in one place and work as well. So let's check out the tracking branch into a new branch:
git checkout -b product-details-git product-details-svn
As git status
can confirm, you'll now have (at least) two branches: master and product-details-git.
3: Import the branch as a submodule
Now let's make the new branch available upstream:
git push --all
After that's been pushed, we can import the new branch as a submodule where we want it in the tree:
git checkout master git submodule add -b product-details-git ../reponame.git my/submodules/dir/product-details
Note that ../reponame.git
refers to the up-stream repository's name, and -b ...
defines the name of the branch we've created earlier. Git will check out your remote repository and point to the right branch automatically.
Don't forget to git commit
and you're done!
Updating from SVN
Updating the "external" from SVN is unfortunately a tedious three-step process :(. First, fetch changes from SVN:
git svn fetch product-details
Second, merge these changes into your local git branch and push the changes up-stream:
git checkout product-details-git git merge product-details-svn git push origin HEAD
And finally, update the submodule and "pin it" at the newer revision:
git checkout master cd my/submodules/dir/product-details git pull origin product-details-git cd .. git add product-details git commit -m 'updating product details'
Improvements?
This post is as much a set of instructions as it is a call for improvements. If you have an easier way to do this, or if you know how to speed up or simplify any of this, a comment to this post would be very much appreciated!
Partial 'svn dcommit' with git
Here at Mozilla, a bunch of webdevs use git svn instead of plain Subversion to interact with our svn repositories -- mostly because of in-place branching, better merging, and all these things that make a dev's life happier.
As you probably know when reading this article, you push all uncommitted changes to the remote svn repository by typing git svn dcommit
. This will take your last, say, five commits, push them to SVN, and mark them locally as committed. But what if you only want to dcommit
some of your changes?
(If you don't need explanations, jump straight to the summary).
Step 1 (optional): Reorder commits
I am not going to go into a lot of detail on interactive rebasing (git rebase -i
), but to start off, make sure your commits are ordered, so that the ones you do want to commit to SVN are before the ones you do not want to push up-stream for now. Example: If in the following history, you want to commit all changes but a023fea
, you want to rebase your commits so a023fea
is last:
In git rebase -i HEAD~4
, change...
pick 07c26c5 some de L10n
pick a023fea adding free-text messages to localizer dashboards
pick 8597f47 adding featured collections as l10n category
pick 19f3df3 making existing localizer pages work with amo2009 layout
... to...
pick 07c26c5 some de L10n
pick 8597f47 adding featured collections as l10n category
pick 19f3df3 making existing localizer pages work with amo2009 layout
pick a023fea adding free-text messages to localizer dashboards
Make sure to resolve any merging problems that might occur due to the reordering.
Step 2: Step in between commits
To only push the desired commits to svn, execute another git rebase -i
and mark the last desired commit for editing (by changing pick
to edit
):
pick 07c26c5 some de L10n
pick 8597f47 adding featured collections as l10n category
edit 19f3df3 making existing localizer pages work with amo2009 layout
pick a023fea adding free-text messages to localizer dashboards
When exiting the editor, git will drop you off after the marked commit, but before the one you don't want, as a quick look at git log
can tell you.
Step 3: dcommit desired changes
After making sure this is really what you want, just execute git svn dcommit
as usual and watch git push all desired changes to SVN, while omitting the rest.
Step 4: Fast-forward to HEAD
When the dcommit is done, remember we are still in the middle of a rebase, so just run git rebase --continue
to fast-forward to the HEAD of your branch. Again, a quick look at git log
can reassure you that only the changes you wanted to have been pushed to SVN.
Success!
Summary: Quick cheat sheet
Here's a quick cheat sheet for you (and me) to come back to in case you forget:
- Reorder commits (
git rebase -i HEAD~4
) so that the undesired ones are after the ones you want to push - In your commit history, jump right after the last wanted commit by marking it for
edit
ing ingit rebase -i
git svn dcommit
git rebase --continue
Adding Empty Directories to git-svn
Just a reminder, because I always forget it: When you use git-svn on an svn repository and your code base contains empty directories (say, for temporary files, or log files), they will be ignored by git unless they contain at least one file.
Paradox? Maybe. There's a good reason however: git ignores empty directories because it tracks (file) content, not a bunch of directories some of which happen to contain a file (the concept of tracking files might be the only thing git has remotely in common with good ol' CVS -- though git also does not deeply care about file names, only content).
The "common" way to handle this is by adding a .gitignore
file to the repository. This won't harm svn-only clients, but it'll make git-svn clients pick up the (almost) empty directory properly.
This is what you need to do.
mkdir empty_dir
echo '*' > empty_dir/.gitignore
echo '!.gitignore' >> empty_dir/.gitignore
git add empty_dir
git commit -m 'adding empty directory' empty_dir
The .gitignore
file tells git what file names not to track inside the directory in question. The asterisk means, ignore all files, but the second line makes sure the .gitignore
file itself is recognized and added to the repository.
- 1