Git is really simple

Git has a few concepts, that look really scary and advanced, but are actually pretty simple under the hood. Lets demystify some of them.

Commits

This is a file that has:

  • The changes you made
  • A description you type in
  • A link back to the parent commit
  • Optionally, some metadata, like if you signed a commit with a private key.

And that is it.

Git creates a file named some string of hexadecimal gibberish like 341f547769f90be823bcda20924e86069f8490f5, and stores all the above in it (compressed, to save space).

When git builds a diff, it:

  • Rewinds all the way to the first commit by just repeatedly going up to the parent commit
  • Plays forward through that chain of parents to build all the files
  • Compares that state with another commit.

(Why isn't it super slow, you ask? Well, I lied. It actually takes regular snapshots and stores them, so it only ever has to go back a few commits, not all the way to the beginning of time)

Commitish

341f547769f90be823bcda20924e86069f8490f5 isn't easy to remember, or type, is it? Well, the reason for the long commit name is to make sure it is unique. But it is also very unlikely you'll get a commit that starts 341f54... ever again (1 in 16 million). A 'commitish' is just git helping out us lazy humans; it will look up the first few characters of a full commit name, like 341f54, and find the actual commit for you. Anywhere you could use the full commit name, you can try typing the first few characters, and it'll almost always just work. If it complains, type a few more characters.

 Tags

This is literally just a commit that you have named. Think of it as writing down a page number to remember it later ('p394 - turn to here'). It is not a branch, and it cannot be modified (but it can be deleted, and later another with the same name can be created). But it is very easy to create a branch from a tag, if you need to change something:

git checkout tags/v1.2.3 -b 1.2.3-hotfix  

This is why tags are often used for version releases; "1.2.3" cannot change, because it has already been rolled out, and you'd end up in a horrible mess. But you can make a branch with a fix, tag it version "1.2.4", then push that out.

 Branches

Branches are very similar to tags, except you can update them to point at new commits. They just point to a commit, and that commit has parents, just like before.

When comparing branches, git will go back until it finds a commit that exists in both branches (the common ancestor) and compare from there. That is why you get the 'tram rails' when looking at git's history, branches can only ever be related to eachother through a common ancestor.

⚠️ BEWARE — When working with branches, you are working locally. Until you 'pull' updates from the server, you may be out of date, so someone else might have updated a file you're working on. Pull often. Pull very often. It makes merge conflicts easier to deal with later.

 Log

The git log is just showing you when things happened, and the message the developer wrote.

There are multiple ways to view this info.

 Basic

> git log

commit 341f547769f90be823bcda20924e86069f8490f5  
Author: David Godfrey <reactiveraven@gmail.com>  
Date:   Mon Jul 9 09:12:38 2018 +0100

    Streamline grammar

commit a49fcde341b77123183f9c1a81a518c9a66b7ecc  
Author: David Godfrey <reactiveraven@gmail.com>  
Date:   Mon Jul 9 09:12:22 2018 +0100

    Added 'generate-csharp.sh'

commit 0685bd03028b1cc928f3a86623513a0d07014bef  
Author: David Godfrey <reactiveraven@gmail.com>  
Date:   Sat Jul 7 14:21:15 2018 +0100

    Added 'generate-swift.sh'

commit e4e790fff5c7dfd3e21f182a99107b63a6149fef  
Author: David Godfrey <reactiveraven@gmail.com>  
Date:   Sat Jul 7 14:14:09 2018 +0100

    Initial commit

Everything you need, but doesn't make branches clear, and it is pretty verbose when you want to scan through them quickly.

 Pretty

> git log --graph --pretty=format:'\''%h -%d %s (%cr) <%an>'\'' --abbrev-commit'

* 8a9fe1f - (origin/master, origin/HEAD, master) [enzyme-adapter-react-helper] [deps] update `airbnb-js-shims`, `install-peerdeps` (4 weeks ago) <Jordan Harband>
* cd28cee - Update issue templates (2 months ago) <Jordan Harband>
* 29ad36b - [enzyme-adapter-react-helper] [deps]  update `npm-run` to pickup security fix (3 months ago) <Jonathan Felchlin>
*   024586e - [enzyme-adapter-react-helper] [fix] Moving rimraf to dependencies (3 months ago) <Jordan Harband>
|\
| * da1db2a - Moving rimraf to dependencies (3 months ago) <Jonathan Felchlin>
|/
*   3d836ef - Merge pull request #1513 from jquense/context (4 months ago) <Brandon Dail>
|\
| * 5253067 - move back to normal range (4 months ago) <Jason Quense>
| * 9fb423e - Update package.json (4 months ago) <Jason Quense>
| * f3618c1 - Add real shallow tests (4 months ago) <Jason Quense>
| * 419780b - Add shallow tests (4 months ago) <Jason Quense>
| * 81167fb - use children to tree (4 months ago) <Jason Quense>
| * 9d01e42 - Update package.json (4 months ago) <Jason Quense>
| * e21b7b5 - Add support for react context element types, fixes #1509 (4 months ago) <Jason Quense>
|/
* 0fe0d73 - [new] `debug`: Implement verbose debug output (5 months ago) <Blaine Kasten>
* 8a730fa - [Dev Deps] update `eslint`, `eslint-plugin-react`, `prop-types` (4 months ago) <Jordan Harband>

Great for scanning through a history, and you can see the tram-lines from branches, but misses off everything but the first line of commit messages.

In my alias there are also variables that colour-code the output to make it easier to view, but I've stripped them out here. The full command is here.

(yeah, the 'pretty' one is quite long; but it shows how configurable git is. Make your own alias that suits you)

 Rebasing

Here be dragons 🐉

If you're finding anything in this guide useful, ask someone for help before rebasing. These can get really messy, and you can break things for other people.

Rebasing lets you change the parent of a commit. Which means it is a new commit. Which means you changed [a1]->[b1]->[c1] into [a1]->[b2]->[c2] - and I'm still merrily working away assuming [b1] and [c1] exist.

Best case scenario, there are duplicate commits that end up being empty, which is really confusing when trying to diagnose a problem later.

Worst case scenario, commits I did not write and that I do not necessarily understand suddenly start conflicting when I try to merge. Very Not Good.