What is this?
This may shock you given the name of this blog entry, but I really hate Git. It’s become a source of entertainment to my coworkers, and frankly to me as well because if you’re going to hate something then it should at LEAST be a source of amusement.
One day they said hey, at least you have figured out how to work with Git, maybe you should write up what you’ve learned! Maybe it’ll be useful!
I don’t think this is what they intended. But I’m really not sorry. So here is my guide to working with Git. This assumes you have Git working and are banging your head against something while you try to use it.
Start by editing your ~/.gitconfig file. Bring over the alias section from mine, and also bring over the two sections on mergetool and diff tool (see the Perforce tool below on what those are.)
Well then!
Welcome to Git! Let me start by saying I'm very, very sorry. It does get a little better once you're used to it. Not much better. But a little.
I'll put this link first. This is a word document with the commands I need to refer to the most. It's more geared to being a single page than a reference though.
And here is my .gitconfig file, located in my home directory, for reference:
Note that I’m using either a Mac or a Linux box so everything is filtered through that. I’ve happily kept Git off my Windows boxes so far.
Your Mindset
Git hates you. Git is really looking forward to destroying your code. Git is hoping you'll let it destroy other people's code in the process. You HAVE to keep that in mind when you work with Git. Just thinking you can use Git to casually check in code will result in you spending the rest of your day trying to figure out what happened and how to fix it. And it's quite possible that you won't be able to.
I can't stress this enough. My mental model of using Git has become that of lion taming. You stand there with your chair and your whip and you pay careful attention to everything. The moment you relax, the lion jumps on you and destroys you. This is Git. This is what sets Git aside from everything else. If you're used to trusting your source code repository not to destroy code, that's the very first mindset change you have to make. You can NOT trust Git. EVER.
The internet has many horror stories about Git. The one that stuck with me is the guy that found the command on stack overflow of "git push --mirror". Seem innocuous, it just pushes up code, right? But it just about wiped out his central repository and did so irrevocably. There's nothing in the command to make you THINK it'll do something like that - but it turns out that command will wipe any branches on the central repository that you don't have. And it does it gleefully and permanently. So ... the lesson? Googling for what to do is frequently the only way to work with Git, but check what you find carefully and think about it before you take any advice. And think about where you will restore your code from if Git destroys your repository when you run the command.
Welcome to Git!
Git Concepts As I Translate Them
Origin
First, terminology. You have to get used to the word "origin". To me, since I wrote the code, the origin is me. To Git, the origin is the source repository - which to me is the destination. I'd say that therefore this is a TERRIBLE choice of terminology - avoiding ambivalence like this is about the very first step in usability. So let this be the first lesson - Git has not taken that very first step into usability. Get used to that.
I originally renamed "origin" as "mtn" (as I work for MTN) to avoid ambivalence and it helped - but there is a pitfall there. You will not be able to use Git without constantly googling to find out what to do with whatever arcane error it's throwing at you. And everyone has gotten used to "origin" so all the code out there uses that, meaning you'll have to get used to it anyway. So "origin" means ... well, unfortunately, the only unambivalent word to me for Git’s "origin" is "destination". "Origin" is where you want your code to end up, origin is the final destination you push your code to. The Git definition, however, is that it is the original repository. It's just something to get used to.
Branching - The Only Cool Thing About Git.
The original title was "Branching: Git Invents New Ways to Permanently Destroy Your Code Changes". Branching is a great idea, horribly hobbled by the fact that it's in Git.
The basic idea behind branching is that if you want to start making changes to your code base but not put them into the main code repository, you can create a branch. Then you can do work off to the side in that branch, and merge it back to your main repository when it's ready. The idea is cool. The execution, unfortunately, scares me. The main thing to remember is that section above, Git HATES you. So if you branch, Git just sits around hoping you'll type the wrong thing so that it can delete your local branch and destroy your code changes. But there are times you can't avoid it.
I originally tried creating a branch called dev, and doing all development work in it until I learned just how confused this can make Git. (I was hitting regular problems with Git being unable to merge code, even though I’m the only developer and all changes were linear.) So I stopped branching and just live in the master branch, and a lot of issues went away. I’m told this becomes problematic once you have multiple developers, all I can say is that if Git can’t merge my changes when I’m the only developer doing linear changes I live in fear of the day someone else checks in code.
But the idea is great, and other than trying to use Git to implement branches, it’s pretty sweet. I have actually started using branches more as we release code – the released code gets it’s own branch – so I will warn you of the new gotcha I did find where Git gives you no clue what branch you are in and will get hopelessly snarled if you forget, which you will eventually.
Workflow - A New Height in Unusability
The basic idea of workflow that I've always had is that you check code in. Not complex. This is where Git really spent effort making itself convoluted. I found some graphs comparing Git to Subversion which illustrate this wonderfully on Steve Bennett’s blog. Here's the subversion workflow (Perforce and most other tools have a similar workflow):
Not bad. You checkout code to get the latest code from the central repository. And you checkin (or commit) code to push it back up. And then there is the Git workflow:
Realize they effectively do the same things. The difference is that Git hates you.
To cope with this, I stick to the same golden path workflow as strictly as I can. If I step outside of this golden path, I consider myself in a minefield. And even in this workflow? Remember that lion taming module. If you get complacent, something will get corrupted. I guarantee it.
We'll talk about that in a minute but first - there are some tools to install to make things easier.
Tools
There are two toolsets I've been using to make Git suck a little bit less - Perforce and SourceTree. I'd consider SourceTree optional, though extremely helpful. Perforce I consider mandatory.
Perforce for Merging
I found this on the net. It summarizes my experience merging in Git’s default tools.
There are some areas in Git that are so unusable that I just flat out gave up. Merging is one. I tried, but this is where Git really took unusability to new heights. I recommend trying to do a merge with Git to entertain yourself one day. Just don't expect to come out of it with a merged file. I think they included merging as a practical joke.
Git does have hooks that let you use other tools though, fortunately. I played with some of them, and Perforce won hands down. So I recommend wiring Perforce in for merging. Perforce is another source control system. But unlike Git, Perforce likes you. They like you so much that they made some of their tools free, and they really save the day here.
So download and install them!
http://www.perforce.com/product/components/perforce-visual-merge-and-diff-tools
Then in your .gitconfig file make sure you have the following sections, with the path for "cmd" modified as needed. See my gitconfig (attached above) for reference. This will use the Perforce tools in both the git commandline and in SourceTree.
[mergetool
"p4merge"
]
cmd = /Applications/p4merge.app/Contents/Resources/launchp4merge
"\"$PWD/$BASE\""
"\"$PWD/$REMOTE\""
"\"$PWD/$LOCAL\""
"\"$PWD/$MERGED\""
keepTemporaries =
false
trustExitCode =
false
keepBackup =
false
[difftool
"p4merge"
]
cmd = /Applications/p4merge.app/Contents/Resources/launchp4merge \"$LOCAL\" \"$REMOTE\"
keepTemporaries =
false
trustExitCode =
false
keepBackup =
false
[difftool
"sourcetree"
]
cmd = /Applications/p4merge.app/Contents/MacOS/p4merge \"$LOCAL\" \"$REMOTE\"
path =
[mergetool
"sourcetree"
]
cmd = /Applications/p4merge.app/Contents/MacOS/p4merge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"
trustExitCode =
true
[merge]
tool = p4merge
[diff]
tool = p4merge
Then when you need to merge files just type the following:
git mergetool
and you'll get Perforce's merge tool instead of Git's merge. Note that the download page has a video on how to use these tools. In essence the top has three columns, the original file, the file with your changes, and the file with the other changes you're trying to merge in. The bottom has all the changes merged using P4's best guess. Just look for the button at the top to go to the next conflict, and edit the code at the bottom to be as desired.
SourceTree
I looked for tools to give Git a front-end that would give it similar usability to the other tools on the market. Frankly I failed. Git is never going to be in the same league as the other tools when it comes to usability, nor does it have any interest in this. But SourceTree helps a LOT.
It's a free tool that you can download it for Mac or Windows here:
It is on the Mac App Store, but they aren't updating that version anymore, so I recommend downloading it directly.
I tend to do my most basic workflow in SourceTree, then break out to the command line for anything out of the norm.
In SourceTree, click "File -> New" and paste in the repository URL from Github. It will clone that repository for you. Add a bookmark as well.
When you open a repository in SourceTree, it shows you all changed files in a bottom window pane. Highlight a file (or all files) and click the Add button. It moves them to the upper pane - and note that the right side shows you a diff. So that's pretty nice.
Once you have the top pane the way you want it, click "commit". This commits to the local git repository, and does NOT go off your machine. But look at the bottom of the window that pops up and you'll see a checkbox that says "push commit immediately to " and a dropdown box. Mine defaults to "origin" and I haven't been able to change that to my personal repository, but so I just check the box and change it to my local repository. Put your comment in the top, hit "commit", and it's off!
If you hit "Repository -> settings" you can see the repositories you have set up and change them.
Now look at the left of the screen. Note that you can see repositories and branches. It will default to the active branch, so normally it just does what you'd expect. And normally I just keep it on "Working Copy" which shows me what I want to see. Highlight any other branch, and you can see history of all the branches that have happened.
SourceTree is also the best place to view checkin history. Git and Github are TERRIBLE at this, so this was huge to me. In SourceTree, find the remote branch you're interested in on the left and click it. You'll see a list of all commits in the upper right pane - highlight one, and all the code changes show up below it. If you right click a file, you can choose "log select" and it'll show you the history of that file. No, I have NO idea what they were thinking when they labelled that "log select". My only guess is that someone at SourceTree hates you in subtle ways. They ARE a Git front end, so there has to be usability issues somewhere. If not they’d be thrown out of the Git community I suspect.
Github does have a history button now that's good for checking the history of a single file. Pull up the file in Github and you'll see the history button.
If you get more advanced, select a branch in SourceTree. View the history. Right-click a commit in the history picture and you'll have access to tools that will corrupt your Git repository in more ways than I care to document. If you do a LOT of reading they'll let you do cool things, but for now just assume each one of them will corrupt your repository in different and entertaining ways.
IntelliJ
IntelliJ has a lot of Git support built in, and can actually issue a lot of Git commands. Frankly I just don't trust Git enough to do this. Remember that lion taming mental picture? Somehow to me this is just jumping into the lion cage with blinders on so that you can’t see everything and then hoping it’ll all work out. I don't trust that this will not result in my being eaten by the lions. So while I tinkered with the Git plugin I don't use it.
BUT!
When you're looking at a single file, hit control-V. A CVS popup will appear. If you hit "local history" you can see some history of the file. This has been pretty handy. It's not a full history - I think it's just a history of the branch - but it's still quite useful!
The Golden Path
Fetching Code
Don't write any code without making sure what branch you're in. Unless you aren't using branches, which means you can skip over several potential problem areas.
git branch
If you aren't in the branch you think you are, then change branches:
git checkout {branchname}
Then get the latest code from origin. That doesn't actually do anything with the code though, so you have to then tell git to actually put the code in your local branch.
git fetch origin
git mf origin/master
Git will ONLY apply those changes to the branch you're in, though. So you have to rerun the "git mf origin/master" in each branch when you get there. Frankly I just scripted the whole thing. Then I added some cleanup code to it. I put these lines in my alias file:
alias git=hub
alias gitprune=
'echo before;git branch -a;git fetch --prune;git fetch --prune origin; git fetch --prune evanthx;echo after;git branch -a'
alias gitmf=
'git mf origin/master'
alias gitfetch=
'gitprune; git fetch origin; gitmf'
Now just type gitfetch. The prune commands delete the old branches that you THOUGHT you deleted and that git TOLD you it deleted, but git lies constantly. So I just stuck those in my aliases because frankly, when I told git I wanted to delete a branch I actually wanted the branch deleted. This way it will eventually get git to do what it already lied to me about doing.
Note that using this relies on aliases being defined in your .gitconfig. I thought about rewriting all my scripts for this without aliases, but frankly you'll be happier with them.
Also feel free to rename the "gitfetch" command to something really snarky so that you can stick it to the man every time you fetch code.
Pushing Code
Now we have all the tools we actually need to use Git and hide a lot of the horribleness. I'll be honest. This workflow is still just insane. But you eventually get used to it. Git is one of those things where you really just have to hold on until Stockholm Syndrome sets in and then you finally start explaining to people why the workflow below really makes sense! No, really! It does!
Create a branch and get into it (IF you are using branches.)
Make some code changes.
Go into SourceTree. Highlight each changed file and click "add".
Once all files are in (and you've checked the diffs on the right) click commit.
Check the commit box to go to your personal repository.
Go to Github, go to the central repository and issue a pull request.
Once the pull has been merged, go to the commandline and type "gitfetch" (which is an alias defined above.)
That fetches your commands and merges them in the nice way. It'll fail a lot. When it does, my default is to type "git pull" which tries to merge the changes a different way. It usually will work. When it doesn't, see the next section.
(Fun note : Compare this to other products. The work flow in Perforce? Add files, commit them. DONE! This is the difference between a tool that loves you and a tool that hates you.)
Git Commands I Use
Even with your best attempts to stay on the golden path, Git will regularly get screwed up. You might consider this surprising, but then just remember to yourself how much Git hates you on a deep, personal level. The real problem as far as I can tell is that when you use branches you have to sort of circularly update back to yourself. If you push something to the central repository, you also have to bring it back down to merge back in with your source code - in other words, you have to commit the code with SourceTree, then pull it back down with the Gitfetch code I have in my alias file. This just opens the doors to Git confusing itself, and it does.
So when it does, what do you do?
Push Your Repository
The first common issue I have is when you have code the way you want it. You need to check it in, but Git is failing on something and won't let you. You can force a push, telling git to shut up and just take the changes.
git push -f origin master
Note "origin" in there. That goes to the central repository, not your local one. So you might consider that. If you put the wrong repository in there, git will actually laugh while it destroys code. And if you do it right, you'll need to tell the other developers in that repository so they don't get confused. It can cause problems otherwise...
The fact that I found this image on the net as is at least lets me know I’m not alone in my pain.
Get Git's Repository
The next common issue is when what is in Git is correct and I want it, but Git can't get it for me. Git reset will do this. To force Git to put it's changes over top of the local repository:
git reset --hard origin/master
By the way, note that for a push there is a space between origin and master. For reset, there is a slash between them. You'd think that sort of thing would be consistent from command to command, but consistency would be a step along the path to usability. Git therefore has ensured a total and complete lack of consistency. It hates you.
One other way to do this that I've used with great success is to just delete your repository and reclone it. That works very well - as long as you aren't using branches. It'll delete any branches you may be using, so that's really not something to do casually. But on build machines, for example, where you just want to check out files and never check in? I've had Git get so snarled up trying to do that that I just gave up and killed and recloned the repository. I have yet to figure out HOW Git got that snarled up. I think it did it on purpose.
Undo A Checkin
So say you accidentally checked code in to the wrong repository and want to undo it. In most other source controls, you'd just check out at the desired spot, then do a new checkin. You can do that in Git as well, but since Git is pretty much geared to lie to you about history, you might as well take advantage of that by combining the two above options.
First, you need the sha-1 of the commit you want to get back to. You can find it in SourceTree, right click it, and choose "Copy Sha-1 to clipboard". You can also find it in GitHub, and it has a button that will let you copy it to the clipboard as well.
Then issue these two commands to set your local branch to that point in history, then to force that up to GitHub. That will erase all branches AFTER the sha-1 you chose - meaning again, do this casually and Git will laugh at you while it wipes code.
git reset --hard {sha-
1
}
git push -f origin master
Scripting git commands
You may or may not ever script Git commands. It seems like a great idea! It's all command line driven, so just throw things into a script and you're set!
This was my original thinking, so I did some build scripts and a few other things. And I was getting really weird errors. And I finally figured out - Git fails a lot. If you think you can just check out code - even on a build machine that never does anything but get the latest iteration of code - then you really haven't gotten the basic message about Git. You got complacent.
The basic issue is that Git fails. And it hates you, so it'll look for great times to fail when you won't notice, like when you scripted it. So here's a handy command that I use in zshell scripts:
read -p
"Press [Enter] key to continue..."
All it does is stop the script until you press the enter key. Put that after EVERY Git command. Then when you run your script, you can actually review the output of the Git commands and see what they say. This was another mindset change for me, I'm used to just writing a script carefully and then it works - but you just can't trust Git to work. Because whenever you just trust Git to work without carefully checking ...