I’ll start with the obvious question; why a fork? Well, the short answer is; my patches are not being applied, the long answer is convoluted and would require long explanation of how Git development works, principles and guidelines, but more importantly the culture of the core developers, and I’m not going to get into that, maybe in the comments section if somebody is interested.
So what is git-fc? It is a friendly fork, and by that I mean that it’s a fork that won’t deviate from the mainline, it is more like a branch in Git terms. This branch will move forward close to Git’s mainline, and it could be merged at any point in time, if the maintainer wished to do so.
git-fc doesn’t include experimental code, or half-assed features, so you can expect the same level of stability as Git’s mainline. Also, it doesn’t remove any feature, or do any backwards incompatible changes, so you can replace git with git-fc and you wouldn’t notice the difference. The delta comes in the extra features that I’ll describe in detail below, that is all.
Who am I? I’ve contributed many patches to Git, mainly the git-remote-hg/bzr two-way bridges, but many many other things. Here’s a list of the top 10 contributors to Git since last year by number of patches:
% git shortlog --since='1 year ago' --no-merges -n -s | head -n 10
388 Junio C Hamano
308 Felipe Contreras
230 Jeff King
161 Nguyễn Thái Ngọc Duy
122 Michael Haggerty
103 Ramkumar Ramachandra
96 John Keeping
69 Eric Sunshine
59 Thomas Rast
51 René Scharfe
More info in ohloh.
As you see, I’ve done a lot of work for Git’s mainline, so chances are you have already benefited from my code one way or the other.
However, the most interesting patches are not merged. I wrote a summary of my 160 patches, explaining their status, so Git developers would prioritize them, but I think it’s fair to say they are just not going to apply them.
So, what do you get if you use git-fc?
Many people have suggested a shortcut for the non-particularly-intuitive “HEAD”, but none of these suggestions seemed very appealing, or feasible.
Because Git already has an ref@op revision syntax, where if you remove the ref, HEAD is implied, I thought @ could be thought as HEAD.
This change was welcome and accepted by the Git mainline, and it even was on track for v1.8.4 but it was dropped last minute because of some issues that are fixed now, and you probably will see it in v1.8.5. But why wait? 🙂
Nice ‘branch -v’
If you have configured the upstream tracking branch for your branches (I wrote a blog post about them), when you do ‘git branch -v’ you see something like this:
fc/branch/fast 177dcad [ahead 2] branch: reorganize verbose options
fc/stage abb6ad5 [ahead 14] completion: update 'git reset' ...
fc/transport/improv eb4d3c7 [ahead 10] transport-helper: don't update ...
While that provides useful information, it doesn’t show the upstream tracking branch, just says “ahead 2” but “ahead 2” compared to what?
If you do ‘git branch -vv’, then you see the answer:
fc/branch/fast 177dcad [master: ahead 2] branch: reorganize ...
fc/stage abb6ad5 [master: ahead 14] completion: update ...
fc/transport/improv eb4d3c7 [master: ahead 10] transport-helper: don't ...
Unfortunately both options take a lot of time (relative to most Git commands which are instantaneous), because computing the “ahead 2” takes a lot of time. So I decided to switch things around, so ‘git branch -v’ gives you:
fc/branch/fast 177dcad [master] branch: reorganize verbose options
fc/stage abb6ad5 [master] completion: update 'git reset' new ...
fc/transport/improv eb4d3c7 [master] transport-helper: don't update refs ...
And it does so instantaneously.
Many (if not all) version control system tools have shortcuts for their most common operations; hg ci, svn co, cvs st. But not Git. You can configure your own aliases manually, but you might have some trouble if you use somebody else’s machine.
Adding default aliases is trivial, it helps everyone, and it doesn’t hurt anyone, yet the patch to do so was rejected.
For now, there are only four aliases, but more can be added later if they are requested.
co = checkout
ci = commit
rb = rebase
st = status
If you have already these aliases, or mapped to something else, your aliases would take precedence over the default ones, so you won’t have any problems.
Streamlined remote helpers
I have spent a lot of time working on git-remote-hg and git-remote-bzr, and although they are relatively new, they have proven to be quite stable and solid, yet they are only part of the “contrib” area side by side with much simpler and way less solid scripts.
In order these in Git mainline you might need a bit of tinkering, and it’s not straight-forward to package them for distributions.
With git-fc they are installed by default, and in the right way, making things easier for distributions.
Improvements to the transport helper
The two way bridges between Git and Mercurial/Bazaar already work quite well, but they lack some features, specifically you cannot do –force, or –dry-run, or use an old:new refspec. If you are not familiar with the old:new refspec; you can do ‘git push master:my-master’, which would push your ‘master’ branch, as if it was named ‘my-master’ in the remote repository.
This is extremely useful if you are really serious about using Git as a transparent client to access a Mercurial repository.
New core.mode configuration
Git is already preparing users for the v2.0 release which would bring minor backward compatibility breakage, but some people would rather get rid of the warnings which are going to stay probably for many releases more and just move to the new behavior already.
Testing Git v2.0 behavior today would not only help git-fc, but also the Git mainline, and you can do that by setting core.mode = next, so if you do this and provide feedback about any issues, that would be greatly appreciated. Unfortunately you cannot test the v2.0 behavior in Git mainline because they rejected the patches, but you can in git-fc.
Please note that the v2.0 behavior might change in the future, before v2.0 is released, so if you enable this mode you need to be aware of that. Chances are you are not going to notice any difference anyway.
In addition to the “next” (v2.0) mode, there’s the “progress” mode. This mode enables “next” plus other configurations that have been proposed to change by default in v2.0, but hasn’t yet been agreed.
In particular, you get these:
merge.defaulttoupstream = true
branch.autosetupmerge = always
mergetool.prompt = false
There might be more in the future, and suggestions are welcome.
It is recommended that you setup this mode for git-fc:
git config --global core.mode progress
Non-ff pulls rejected by default
Even in the Git project everybody has agreed this is the way to go in order to avoid the typical Git newbie making the mistake of doing a merge, when perhaps (s)he wanted to do git reset, or git rebase. With this change git complains that that a non-fast-forward branch is being pulled, so the user has to decide what to do.
The user would have to do either ‘
git pull --merge‘ or ‘
git pull --rebase‘, the former being what Git mainline currently does.
The user can of course choose the old behavior, which is easy to configure:
git config --global pull.mode merge
Official staging area
Everybody already uses the term “staging area” already, and Git developers also agreed it the best term to what is officially referred to as “the index”. So git-fc has new options for all commands that modify the staging area (e.g. git grep –staged, git rm –staged), and also adds a new git stage command that makes it easier to work with the staging area.
'git stage' [options] [--] [...]
'git stage add' [options] [--] [...]
'git stage reset' [-q|--patch] [--] [...]
'git stage diff' [options]  [--] [...]
'git stage rm' [options] [--] [...]
'git stage apply' [options] [--] [...]
'git stage edit'
Without any command, git stage adds files to the stage, same as git add, same as in Git mainline.
New fetch.default configuration
When you have configured the upstream tracking branch for all your branches, you will probably have tracking branches that point to a local branch, for example feature-a pointing to master, in which case you would get something like:
% git fetch
* branch master -> FETCH_HEAD
Which makes absolutely no sense, since the ‘.’ repository is not even documented, and FETCH_HEAD is a marginally known concept. In this case git fetch is basically doing nothing from the user’s point of view.
So the user can configure
fetch.default = simple to get a simple sensible default; ‘
git fetch‘ will always use origin by default, which is not ideal for everyone, but it’s better than the current alternative.
If you use the “progress” mode, this option is also enabled.
Publish tracking branch
Git mainline doesn’t have the greatest support for triangular workflows, a good solution for that is to introduce a second “upstream” tracking branch which is for the reverse; the branch you normally push to.
Say you clone a repository (libgit2) in GitHub, then create a branch (feature-a) and push it to your personal repository, you would want to track two branches (origin/master), and (mine/feature-a), but Git mainline only provides support for a single upstream tracking branch.
If you setup your upstream tracking branch to origin/master, then you can just do git rebase without arguments and git will pick the right branch (origin/master) to rebase to. However, git push by default will also try to push to origin/master, which is not what you want. Plus git branch -v will show how ahead/behind your branch is compared to origin/master, not mine/feature-a.
If you set up your upstream to mine/feature-a, then git push will work, but git rebase won’t.
With this option, git rebase uses the upstream branch, and git push uses the publish branch.
Setting the publish tracking branch is easy:
git push --set-publish mine feature-a
git branch --set-publish mine/feature-a
git branch -v will show it as well:
fc/branch/fast 177dcad [master, gh/fc/branch/fast] branch: ...
fc/stage abb6ad5 [master, gh/fc/stage] completion: ...
fc/transport/improv eb4d3c7 [master, gh/fc/transport/improv] ...
Support for Ruby
By far the most complex and interesting feature, but unfortunately also the one that is not yet 100% complete.
There is partial optional support for Ruby. Git already has tooling so any language can use it’s plumbing and achieve plenty of tasks:
IO.popen(%w[git for-each-ref]) do |io|
io.each do |line|
sha1, kind, name = line.split()
However, this a) requires a process fork, and b) requires I/O communication to get the desired data. While this is not a big deal on many systems, it is in Windows systems where forks are slow, and many Git core programs don’t work as well as they do in Linux.
Git has a goal to replace all the core scripts with native C versions, but it’s a goal only in name that is not actually pursued. In addition, that still leaves out any third party tools since Git doesn’t provide a shared libgit library, which is why an independent libgit2 was needed in the first place.
Ruby bindings solve these problems:
for_each_ref() do |name, sha1, flags|
The command ‘
git ruby‘ can use this script by providing the bindings for many Git’s internal C functions (though not all), which makes it easier to write Ruby programs that take full advantage of Git without any need of forks, or I/O communication.
As you might guess, I’ve spent a lot of time working on all these features, plus all the ones that are already merged in Git’s mainline. Hopefully they are useful to some people.
It’s easy to compile and install:
By default git will be installed in your home directory, but you can also do what I do: ‘
make prefix=/opt/git install‘, and add ‘/opt/git/bin’ to your $PATH. All you need is a few development packages; zlib, curl, expat, openssl.
The code is in Github, the home page is in Google code, and the mailing list in Google groups. All comments and patches are welcome.
You can find future comments and releases in this blog, under the git-fc tag.