Even if you've just arrived into the Gitworld, you've already noticed that things are really fast and flexible. Some people claim that the most important thing about the distributed nature of Git is that you can work on an airplane, but I claim that's totally bogus. The coolest thing about the distributed nature of Git is that you can fool around all you want, and try a million things, and even mess up your repo as much as you want... and still recover. Here's how, with a screencast at the bottom.
With CVS or Subversion or any number of other VCS's, when you commit or branch, you have the potential of causing yourself and others pain forever. And there's no way around it. You just have to do your best and hope it comes out OK. But with Git, you can try anything and rehearse it in advance, in a total no-consequence environment. Here are some suggestions of where to start.
Setting up a git-play area
First, let's pretend that we're a committer of Examples project. To do this, we don't have to have any privileges. Let's just make a copy of the Examples repo that we do have privileges on. We'll do this by creating a copy of the repository on Drupal.org in a throw-away directory:
git clone --mirror git://git.drupal.org/project/examples.git
git clone examples.git --branch master
Now we have a repo where we can do anything we want, experiment with anything, and it can never get back to the server, even though we have full push access. There is no way you can do anything wrong using this setup - you're able to commit, but you're committing to a local clone of Examples project.
Note that you could also just
cp -r a local repo to /tmp or some similar junk play area. You just have to be careful in that case because if you had commit access in the original repo, you do also in the copy. The reason I did the mirror clone above was to give us commit access to a bogus local repository with no consequences.
git reset --hard <commit>
Sometimes you just want to give up your work (or redo it, or just replay it from the authoritative repository. Then
git reset --hard is what you want. You can throw away one or several commits, blow away changes you have staged, etc. If the commits in question are still on another branch (or on your branch in the remote repository), then you're not even doing anything destructive, but just fiddling with your local.
So first, let's experiment with what happens when we use the destructive
git reset --hard, which sets the repository back to the commit we name. Let's set it back 3 commits:
git reset --hard HEAD~3
We have just destroyed 3 commits! But did we do any damage? Nope.
pulls it right back into our local repository. All we actually did was to remote the memory of those 3 commits from our local branch.
Or let's say that those commits were not in the remote repo. We can still do all this with no risk:
git checkout master
git checkout -b play_branch
git reset --hard HEAD~3
# Recover the commits from our original branch
git merge master
Resetting a commit so we can fix it up a bit
Let's say that all these commits are my own and they haven't been released into the wild yet. I'm going to rework the top commit just a bit because I didn't really like it. I essentially throw away the commit, but keep the files in my work tree.
git reset HEAD^
will undo the top commit, but leave the results of it in the working tree.
Amending a commit
Sometimes I have either messed up the commit message, forgotten to stage a file or a file deletion, or something of the like. It's just good to have another chance at the commit.
I can stage some additional stuff I want to commit just by doing a
git add and then
git commit --amend
and the newly staged stuff gets added to the top commit, and I have the chance to change the commit message as well.
Yes, this is rewriting history, and it must be done only on commits that have not already been released into the wild.
Combining (squashing) 10 commits into one
Since we're playing let's combine 10 commits into one. This uses the rather exotic "rebase" command to do the exotic "squash" operation. But even though these sound forbidding, it's just a powerful way to combine many commits into one:
git rebase -i HEAD~10
We get the chance to turn the last 10 commits into any number of commits, or squash them into 1. To turn it all into one commit, change lines 2-10 from "pick" into an "s" (for "squash").
Let's make a new branch, go back in history by 10 commits, and then cherry pick some of the original commits that were on this branch back onto it. It's easy.
git log # Take a look at the commits
git checkout -b my_fiddle_branch # Make a play branch
git reset --hard HEAD~10 # Kill off the top 10 commits
git cherry-pick master^ # Take the next-to-top commit on master and apply it on this branch