Previously,
I wrote about Git usage for single-user single-location projects. However,
where Git really shines is in managing a project when changes are made on
multiple machines (whether by one person or by multiple people).
Unlike centralized version control systems and file synchronization
software, Git and other distributed version control systems actually have
good support for disconnected operation:
- You can perform your commits locally and without talking to a central
server. You can push all your changes to another location whenever you get
a chance. (CVS and SVN don't support making commits in isolation, so people
who work offline end up submitting huge patches.)
- When you (or you and other people) perform independent changes in
parallel on different machines, Git knows how to gracefully merge those
changes the next time you synchronize.
However, I've found Git to be a lot easier to get started with than other
VCS's with the same features. All you need is a git init to start
managing a simple project in Git: like RCS (but unlike CVS and SVN), there's
no need to create a separate repository and make a checkout of it. And yet,
when your project matures, Git will happily (and with just a couple of
additional commands) move that project to multiple machines or share it over
the internet so you can take advantage of its distributed features.
One of my projects is used solely to manage my dotfiles. (I'm constantly
tweaking my .emacs, .bashrc, etc.) I use Git to keep my
dotfiles synchronized on all the computers I use. I'll discuss the basics of
distributed operation for a generic project before talking about some of the
wrinkles associated with using Git to manage your dotfiles.
Suppose my Git repository is in ~/testproj on the
machine bigphil. To start working on that project on another
machine (the equivalent of "svn checkout"), do:
git clone ssh://bigphil/~/testproj
You can also clone a repository from elsewhere on a local disk, or over
HTTP:
git clone ~/testproj
git clone http://example.com/git/testproj
You now have the complete version history of the project, and Git can work
completely independently of the original repository, if you'd like. You can
add files, make your own commits, etc. on your new repository locally.
However, typically you will want to continue incorporating commits that are
made to the source repository. You can do this with:
git pull
(When you cloned the repository, Git remembers the original
location; git pull will retrieve updates from the same location.)
This is the equivalent of "svn update".
Merging
When you pull from a remote repository and there have been changes on both
your local copy and the remote copy since you last synchronized, Git needs to
merge those changes. Usually, it will do this automatically and make a new
commit which incorporates the changes from both the local and the remote
repositories.
After a merge has occurred, if you look at the project history
with gitk --all, you'll see a place where two history lines diverged
(representing development in the local and remote repositories) and then were
merged back together. (However, the output of git log flattens these
nonlinearities.)
If the local and remote changes made modifications to the same pieces of
code, Git may have trouble performing a merge. In this case, it will do its
best, but it will leave conflict markers in the code and not commit the final
result. You should fix up the conflicts and then commit the merge
with git commit -a.
Pushing, and bare repositories
To take your local modifications and push them back to the original
repository from which you made your clone, do this:
git push
To push to a different repository:
git push path/to/other/repo
However, if you push your local modifications to a regular repository, a
person who is using that repository to do work may get confused because the
state of the repository is changing right under him. So typically it's better
to push to a bare repository, which is a repository without a
working copy (essentially, what's in the .git subdirectory of a
regular repository).
To initialize a bare repository:
git --bare init
Then use git push PATH to push to it. Bare repositories look
different at the file level, but cloning from and pushing to them is otherwise
the same.
Managing dotfiles with Git
To manage my dotfiles, I've made my home directory the root of a Git repo. I
only add the files I'm interested in managing
(.emacs, .bashrc, etc.), and Git ignores the rest of them.
I push changes from that repo into a bare repository on one of my machines,
and pull from that repository to get the latest versions.
The only complication is when I wish to bring my dotfiles to a new computer.
Git does not allow you to clone a repo into an existing directory (as I would
wish to do to clone my dotfiles into my home directory). However, things will
work if you clone to a new directory, and then copy the contents of that
directory (the .git subdirectory, and all the files of interest)
back to your home directory:
git clone ssh://bigphil/~/projects/dotfiles
mv dotfiles/.[a-zA-Z]* ~
(Note that dotfiles/* doesn't work because * doesn't usually select dotfiles.)