Learn Git with a few sentences

Daniel Robinson
13 min readJun 14, 2016

--

From “”
To “This is the first sentence in my git repository”

Above is a change set (#1). As long as you give me an empty sentence, I can ‘apply’ the change set to get “This is the first sentence in my git repository”.

From “This is the first sentence in my git repository”
To “Let me tell you a bit about git”

Wow, with this second change set (#2) we can go from an empty sentence, all the way to “Let me tell you about git”.

Git is like lego. It takes change sets, and stacks them on top of one another, but the bricks have to fit! It’s no good starting with an empty sentence “” and trying to smash #2 on top of it. #2 will be like “bro, I only go from “This is the first sentence in my git repository”.

When you’re stacking lego piece by piece, and you create the new pieces as you go, it is a process of designing the new lego piece, adding it to your lego kit, and then committing it to your lego creation. Git works the same.

Each time you commit a new piece to your creation, it’s true you’re creating a new snapshot of your creation at that moment in time, but you’re also adding an instruction of how you got to that state from the previous one. You have an instruction booklet from the beginning of time when there was nothing to your latest and greatest creation.

All the change sets have to apply on top of each other.

There’s always that friend. Yeah, that one. They come along and are like “Hey dude, I saw your empty sentence and I wanted to change it with this change set:”

From “”
To “My sentence is so much cooler”
#3 (conflicted)

And you’re like, “Madam, that was so yesteryear. That piece ain’t going to fit onto the top of my sentence history at all. I’m currently at “Let me tell you about git”. They’re super disappointed, but you are the master, and you just ain’t going to take that treatment. In order to resolve this merge conflict, you tell them “look, how about you take all of my sentence history, and use that as a base, and then just change your change set to work on top of it. They’re like “sweeet, awesome, I’ll be back”.

A few moments later they come back with:

From “Let me tell you about git”
To “My sentence is so much cooler”
#3

And you’re like “That soooo fits. I’m going to bring that change across, and commit it to my master creation. Nice work friend!”.

You both stand back and marvel:

“” — #1 — #2 — #3

The cool thing is that after this change your friend has their own copy because they had used your instruction book to get to #2, and then created #3 on top of that. So they also have the same creation:

“” — #1 — #2 — #3

The problem is your friend is getting a bit like “After this, I’m soooo the master”. What are they thinking???? You’re a little less confident though. So you’re like “My dear friend, I ain’t gonna step aside, but let’s make a third copy of this creation, and let that be the master: don’t want no clash of ego’s here….”. That friend is freakishly efficient… “Tadaaaaa!” they say: “” — #1 — #2 — #3 the new, master copy.

For a time all is well. You come up with #4, add it to your copy, and you both agree to put it into the master copy. Then your friend comes up with #5, #6. This creation is coming along! You both agree to add it to master, and then you also fetch a copy of #5, #6 from master to your local copy. Just like that, your friend, master, and you are all up to speed together.

Next minute though, you have total writers block! Well, it’s not that you can’t come up with anything, it’s that you’ve got two new change set (/sentence) ideas, and you’re not sure which is better… doh! Ain’t ready to put them on your own main copy, what do? Hey, what if you don’t touch the main trunk you got going, but you just sort of store #7a and #7b on the side:

“” — #1 — #2 — #3 — #4 — #5 — #6–#7 (branch a)
“” — #1 — #2 — #3 — #4 — #5 — #6
“” — #1 — #2 — #3 — #4 — #5 — #6 — #7 (branch b)

Leave it alone for more decisive days. You’ve got two branch options, and you can decide any time. In the meantime, you kind of check out #7a for a while, then go back to #6, then swap over to #7b. Just sort of see what’s right. Finally, someone flips a coin for you, within seconds you’re like, yep git, get rid of branch a, we’re merging branch b. You bring across #7 from branch b. Get rid of the branch b, and you’re back to magical mainline in seconds flat, nice!

“” — #1 — #2 — #3 — #4 — #5 — #6 — #7

You go talk to your friend, and oh noesssss! They don’t agree! They too, really need time to decide about this #7th sentence. So you both decide to put a branch on the master copy of your creations. Master copy just sort of sit’s there with the two branches like so:

“” — #1 — #2 — #3 — #4 — #5 — #6
“” — #1 — #2 — #3 — #4 — #5 — #6 — #7 (branch b)

The problem is, in the mean time, other people come along and start putting great changes into the master copy master branch, and the master copy branch b. Pretty soon it’s like

… — #5 — #6 — #7new — #8mainline — #9mainline
… — #5 — #6 — #7 — #8 (branch b)

But then it’s decided that actually the original #7 had a great sentence and it should go in. What do you do? There’s two options really. You could just take a look at the entire difference between #9mainline and #7 + #8(branch b) and create a whole new single commit based on the difference and call that #10 and be done. But you lose your history — your history! So… you could also extract #7 & #8 from branch b, and just change #7 to make it fit on top of #9mainline. That way you get a slightly modified #10, and your #8 can can stay exactly the same but become #11. Visually now:

Take branch b:
… — #5 — #6 — #7 — #8 (branch b) & chop off #7 — #8

Then change #7 to fit on top of #9mainline (call it #10, and #8 -> #11). So you’ve got:

… — #5 — #6 — #7new — #8mainline — #9mainline & #10–#11

Sweet, just put em together and you’ve got history:

… — #5 — #6 — #7new — #8mainline — #9mainline — #10–#11

Well that was super cool! The whole idea is as long as the pieces fit on top of each other, you can have multiple different chains, you can chop the chains, and put them back together again. That’s how Git works. It gives you, and your friends a whole lot of lego blocks, and you can all play together! All possibilities can exist, but most of the time, you all agree to one. Won’t stop everybody from experimenting though. And if one branch get’s ahead of another, you always have that option of just taking a snapshot difference between the two, or changing your history just a little so it fits, but mostly retaining it otherwise in terms of the number of commits.

Git is magically flexible, it stores change sets, and let’s lot’s of different people remix them as they like, independently, but often with an overall agreed upon central master branch. It’s as simple as that!

You can extrapolate how complex it could get if hundreds of people are all working on the same project, but through some basic easily established workflows, git handles this sort of thing really well. This is basically the end of the article, but if you want to see how some of the general concepts above translate into actual usage, continue reading.

I mentored at a workshop recently, so I want to be really clear about a few things first:

  1. Git is not Github — keep reading.
  2. Git is a command line application. See Command-line.
  3. Git works on the file system. Lego blocks are files, with all the changes inside those files. You can turn any folder on your file system into a git repository. Of course if you start with an empty folder it makes more sense, unless you know that everything in that folder is going to be your #1 change set.

So to start a git repo you can do this:

$ mkdir gotTheGittersTryingGit
$ cd gotTheGittersTryingGit
$ git init .

This makes a new folder called “gotTheGittersTryingGit” and initializes it with a blank history book/makes it a git repo. If you were using git alone and never made a mistake, the entire rest of your git life could be as follows:

$ git status // 1 No changes - you didn't do anything yet!// 2 Make your changes!$ git status // 3 This should say "untracked changes"$ git add <filename> // 3 Repeat for all the changed files$ git commit -m "<Describe your change set>" // 4$ git status // 5 No changes. Proceed to 2.

The git statuses are optional, they just tell you what’s going on. One more time folks, the entire process of git on a main trunk is

  1. Start with no changes (since last commit)
  2. Make changes
  3. Add those changes
  4. Commit those changes when you really like everything you’ve added.
  5. Repeat.

This means all the changes you make through time are tracked by git.

We mentioned earlier though, you may want to keep your master mainline history more “definite” and put possible new features on new branches until they’re finalized. To do this you create branches.

$ git checkout -b <new-branch-name> // Creates and checks out a new branch// Make changes$ git add . // Adds all changes (instead of file by file)$ git commit -m "add my feature"$ git checkout master // Go back to master & 'hide' changes$ git checkout new-branch-name // Go back to version with changes!// Decide you want those changes in master branch.$ git checkout master$ git merge new-branch-name // Commit differences in single commit
OR
$ git rebase new-branch-name // Bring across all commits
$ git branch -d new-branch-name // Probably want to delete branch

There are a lot of workflows to Git. Probably the most important distinction to make is between the idea’s of merging and rebasing. Merging takes a direct differential between two snapshots, makes one commit from that, and adds this to the master. Rebasing brings across all the history. This can be more complex, because sometimes, the history won’t fit, and you’ll have to make changes to some of it. Git is pretty verbose about this, but you might need to make changes after a rebase to resolve conflicts, and continue doing so until all the history was applied. See more below on rebasing.

Finally, there is the idea of a ‘remote’ in git. A remote is another copy of the working code living at some other location. The most common example is a git repository on Github. With remotes, the idea is you can push and fetch changes. If you’ve never done this, pop over to github, create an account, and create a repository: instructions will show up on how to push your local changes to the remote repository.

$ git remote add origin remote repository URL$ git push origin master // push to origin from master branch

With a centralised master public copy of a git repository like those on Github, collaboration is powerfully enabled. You should feel no fear at all in being able to contribute back to a public (open source) git repository. One of the easiest workflows to do this (suppose you are editing some documentation for an open source project) is to use the “fork” button on github.

This button will create your own personal remote copy of the repo on github. You can then get that copy on your local computer doing a clone, then make updates and push back:

$ git clone <url-for-_your_-remote-repository>// Make changes$ git add <file> // repeat for all files or use git add -A$ git commit -m "Descriptive short summary of changes"$ git push origin master // origin is set by git clone

Now your personal remote ‘forked’ copy will have extra changes than the original open source copy. Now you can make a “pull” request on Github to have your changes merged across to the original central copy, and if accepted, you will take your claim to fame as an open source contributor!

Follow the instructions on Github, which are quite friendly!

You should know that it is also possible to manage multiple remotes in the same git project. This could mean that instead of git push origin master, you could do e.g. git push my-remote master, and git push my-friends-remote master. To add remotes it is

$ git remote add <name-of-remote> <remote repository URL>$ git remote -v // See all remotes

Rewriting History & Correcting Changes

Coming back to just your personal workflow, whether that be on a branch, or on your (mainline) master branch, sometimes you may want to change past commits, or make your history make more sense. The simplest case is you commit something and go “woops I missed something, or I want to change my commit message” no fear go here:

$ git commit -m "Spllin Mistake & Bad file added" // Bad commit// Make the changes you missed$ git add <file> // or git add . to add all changes$ git commit --amend // Commit changes as part of previous commit

Aside: VIM

As an aside, we haven’t talked about VIM. If you’ve been following through, and haven’t already, with git commit --amend you’ll almost definitely have VIM open. VIM is a command line text editor just like notepad or notepad++ or sublime or… <name your text editor> except that it runs in the command line. Unlike other text editors, inserting text, saving and quitting are non-trivial! I recommend google searching for a run down, but at the very least you can:

  • press ‘i’ for “INSERT” mode — now you can actually type text!!!
  • Esc to exit the mode you’re in — e.g. exit “INSERT” mode
  • type ‘:w’ to write the file changes to disk (save)
  • type ‘:q’ to quit (if there are no changes)
  • type ‘:wq’ to save and quit
  • type ‘:q!’ to quit without saving changes.

Git uses whatever text editor is configured — by default VIM — and waits for you to make some changes, save the file, and then quit the editor you made those changes in. In our git commit --amend case, you will see the “Spllin Mistake & Bad file added” message at the top of the file, and you can change this message, then esc-:wq to save and get out of there. Git will automatically know you’ve updated the commit message.

Back Again: Rewriting History

So, now you know how to fix up the last commit you made. (by the way just go ‘git show’ to see the last commit).

What about 5 commits ago? Ok, back to our friend rebasing. Rebasing is just the process of taking a bunch of commit’s, and applying them on top of another bunch of commits, but allowing you to fix conflicts or make changes along the way. We previously saw it pulling changes across from other branches, but you can also just slice your history in two, and re-apply the second section of history on top of the first.

“” — #1 — #2 — #3 SLICE HERE #4 — #5 — #6

You do that by either using the actual hash from the commit history

$ git rebase -i #3 // Will slice at commit hash #3

Or you can start at the most recent commit (HEAD) and count backwards

$ git rebase -i HEAD~3  // 3 means go back three commits
OR
$ git rebase -i HEAD^^^ // one ^ per commit to go back

This will launch a temporary VIM document, and give you a list of commits like this:

pick d62bfd4 #4
pick b4f9247 #5
pick d50151c #6

If you change that to

edit d62bfd4 #4
pick b4f9247 #5
pick d50151c #6

You will end up at #4 (d62bfd4 is the actual git generated hash), and you can use the git commit --amend trick we did above to change that commit. You can also just commit new changes in the middle, provided that won’t break the commit’s higher up in the chain. When you have fixed #4 / added a #4a, #4b, #4c etc, you can then say

$ git rebase --continue

This will continue onto #5, and then #6. If you had changed either to edit, it would stop along the way. You will notice other options in the git interactive rebase. There is a “combine” option called ‘squashing’. This could, for example make:

“” — #1 — #2 — #3 — #4
become
“” — #1a
where #1a is #1, #2, #3, #4, combined.

You would be offered to rewrite the commit message for the new combined commit. If you use ‘fixup’ it will discard the commit messages and just use the last commit message when squashing them all together.

Between git commit --amend and git rebase -i, you can slice, combine, change, fix, mutate, do just about whatever you like to your git history. What you must remember though, in collaborative projects, is that it’s not just your git history, and you should only ever rewrite history on one of your feature branches. Otherwise everyone can get out of sync (different commit hashes are bad) and mayhem may occur. On that note, if you’re ever in the middle of an interactive rebase, and it’s not going well, git rebase — abort is your super super good friend. You can start over.

This has been a flying introduction

Flying introductions are great in my mind, because once I have the big picture, I feel a lot more orientated to what I should play with. Play is important — another word for it is practice — depending on how much you want to make it fun. Some things you could challenge yourself to are:

a) watch what happens on disk when you switch between git branches

b) Try interactive rebasing (git rebase -i HEAD~<num>).

c) Put your project on github, and push changes to it.

d) Fork an open source project on github, make a local checkout, and push changes to your fork. If you spot an issue in that project’s issue tracker with a tag like “good first contribution” or “beginners could fix” perhaps you want to make meaningful changes and create a pull request back to the project. Seriously, hate is a low frequency thing on Github, appreciation is 99% what you’ll get. If anyone bullies you, contact me and we’ll talk it through!

Go get gittery with Git!!!!

--

--

Daniel Robinson

JS, React, Node, Laravel developer. Living in Nelson NZ, working remote for Polygon Research in Montreal.