A Survivor’s Guide to Git
Kevin Miller
Kevin Miller

A Survivor’s Guide to Git

Kevin Miller, iOS Engineer

Git is hard. Git is intimidating. There’s a giant learning curve. And as software engineers it’s essential.

Git is the industry standard for version control. It’s something that most of us don’t learn in our schools or coding boot camps.

I first heard about Git during my boot camp and went through a few tutorials, but it wasn’t until I got a job and started working with other developers that I really had to use it. In fact, I see new developers come in all the time and have difficulty learning Git. So, if you’re confused, you’re not alone.

I hated every minute of learning how to use Git. It was difficult to find resources for absolute beginners, and the few I did find weren’t well-written or especially clear. Often they assume a certain level of knowledge that you may not have (I didn’t). So I want to provide a resource for people preparing to enter their first technical job, or for engineers already in a job and having an “oh sh*t” moment like I did.

I am not a Git expert. But I do know the basics– learned through experience and input from fellow developers– that allow me to collaborate with my coworkers so I can get to my real specialty: writing code. I’m putting what I’ve learned here to hopefully help you get to what you’re good at, too.

What the heck is Git anyway?

Git is software you install on your computer. If you haven’t installed it, go do it.

What makes Git different from, say, iTunes or Minesweeper, (even more than its functionality) is that Git has no Graphical User Interface (or GUI). GUIs are what revolutionized computing and made Steve Jobs and Bill Gates rich. Before GUIs, you had to interact with programs via a command line. Since Git has no GUI, you interact with it via command line, too (at least sometimes – more on that later).

$ git checkout -b [branch name] origin/[branch name]

When you see something like this on a tutorial site, it’s a command line command, to be run in Terminal on a Mac, Command Prompt on Windows or Shell on Linux.

So here’s one of those things people assume you know: the $ stands for all the junk before the dollar sign in terminal. Here’s mine:

Kevins-MacBook-Pro-2:~ kevinmiller$ 

When you see $ in a tutorial, you just type the stuff to the right of it. A [] or {} or <> means you’re supposed sub in your own information. Here’s what that command looks like when typed into terminal:

Kevins-MacBook-Pro-2:~ kevinmiller$ git checkout -b master origin/master

We’ll talk more about commands later. On to the next logical question:

But what does it do?

Good question. Git its a tool for source control (sometimes called version control). It tracks changes in files and allows multiple people to collaborate on the same file or set of files. If you’ve ever had multiple Microsoft Word files named “Paper-Draft.docx” “Paper-Final.docx” “PaperFinal2.docx” “PaperFinalFinal.docx” “PaperTHISISTHERIGHTONE.docx” then you understand the need for source control. You can see how this could really get out of hand if you have five, fifty, or in the case of a large company, thousands of people working on the same document. Git is often used for software development but can be used to track changes in almost any set of files.

That sounds cool… How does it do that?

Git takes snapshots of your work, called commits. Going back to our Microsoft Word Nightmare Analogy(™), you can think of a commit as if you printed each version of your term paper and filed it in a filing cabinet. Git does this for every file in your repository. Every time a file changes, a new copy is made. Repository is just a fancy word for “all the files that Git is keeping track of for you”.

How do multiple people collaborate without stepping on each other’s toes? Most often, people will do work in their own branch.

According to Atlassian

A branch represents an independent line of development. Branches serve as an abstraction for the edit/stage/commit process. You can think of them as a way to request a brand new working directory, staging area, and project history.

That’s a little dense. Imagine you started a term paper with a partner. You are responsible for writing about one topic and your partner is responsible for writing another. You wrote the introduction paragraph together, but now each of you needs to work independently. Both of you will start with the same document “Paper-Intro.docx” but then each of you will get your own filing cabinet! You’ll be able to work and keep track of your own versions independently, but you both started at the same point. As you work, each time you get to a version you want to save (commit) you’ll print it off and file it in the filing cabinet. Your partner will do the same with their filing cabinet.

Many tutorials use a tree metaphor to describe branches; you could think of the original branch (called master) as the tree trunk. Every branch on the tree comes from the trunk, but it splits off on its own. However, I like to think of it as a highway – say you have a highway with one lane. This is your master branch. If you create another branch, the highway turns into a two-lane highway. When you make changes to the branches it’s like the lane branched off onto its own exit. You can drive off and do whatever you want on it, as it is separate from the highway. Eventually, it will merge back onto the highway.

Because you have a record of all the changes that you’ve made (if you chose to commit them) it’s easy to collaborate, go back to old versions, combine versions, etc. The possibilities are endless. A project will most likely have multiple branches, or multiple series of versions. As development progresses these branches will all be merged together to create the finished product.

Believe it or not, these are the basics of Git. And the best way to understand it is to use it. So, let’s make a cake! Well, we’ll make a shopping list for a cake. I highly recommend following along on your own computer. It will make more sense as you do it.

But how to Git?

First, we need to make a repository. Remember, this is just going to be a folder (and its subfolders) on your computer where your files live and where they are tracked by Git.

I made a folder called Shopping List on my computer. Do the same, somewhere you’ll be able to find it.

Ok, we have our folder. At this point, we have no list, and we have no Git. So let’s make our folder into a Git repository. On the command line, run the following command:

$ cd <path_to_folder>

See what I did there? I used that shorthand that we talked about earlier. This is what it actually looked like on my command line:

Kevins-MBP-2:gittutorial-ios kevinmiller$ cd /Users/kevinmiller/Developer/Shopping\ List

What did this do? It changed my current directory (cd) from my old one (my current work project) to the one where we want our Git repository. So, once we’re in it, run this command:

$ git init

This initializes a Git repository. If you navigate to that folder on your system, you’ll see a new folder called .git, which is where Git will keep track of your project, in this case, our shopping list. Note: if you don’t see the .git folder you may have to enable viewing hidden folders on your system. Usually, files and folders that start with “.” are hidden because they’re important and it’s easy to mess things up if you don’t know what you’re doing.

Ok, so now let’s make a shopping list. Let’s keep it simple and create a .txt file (with any text editor or with the command line if you’re fancy) called ShoppingList.txt. Let’s add three ingredients:

Eggs
Flour
Sugar

Now we have the basic ingredients for a cake. Might not be fancy, but could still be good.

Remember, we are making a simple shopping list for clarity and ease of understanding, but this is often code… Git works the same for both!

Save the file and type in:

$ git status

This is what you should see:

On branch master

No commits yet

Untracked files:
 (use "git add <file>..." to include in what will be committed)

	ShoppingList.txt
nothing added to commit but untracked files present (use "git add" to track)
Kevins-MBP-2:Shopping List kevinmiller$

So far, so good! If you see something other than this, abandon all hope and give up trying. Just kidding. You might be in the wrong directory, maybe Git hasn’t been installed, maybe you forgot to save the file.

Before we unpack what’s above, let’s talk about GUIs. Several good GUI wrappers exist for Git. I use SourceTree and highly recommend it. However, you can’t be afraid of the command line. Everything can be done from the command line, and at some point, you will have to use it. GUIs can do a lot, but not everything. It’s really a matter of personal preference. Ultimately once you know what you want to do with Git, how you do it (via the command line or a GUI) doesn’t matter.

Up above we asked about our status and got a message back from Git. It told us our branch (our history of snapshots, remember?), which is master. That makes sense. Master is the default name for the first branch, as it is the “master copy.”

Now it says “no commits”. That makes sense, too. We haven’t told it to make any commits yet.

This is where it gets interesting. It says:

Untracked files:
 (use "git add <file>..." to include in what will be committed)

	ShoppingList.txt

nothing added to commit but untracked files present (use "git add" to track)

Git noticed that we made a new file – and it’s the file we made. Cool! It’s also telling us that it’s not yet keeping track of this file for us. We want it to do that. Fortunately for us, it tells us how to do that with $ git add <file>… which we now know means $ git add ShoppingList.txt. Go ahead and do that.

Cool. Now what? Do $ git status again.

On branch master

No commits yet

Changes to be committed:
 (use "git rm --cached <file>..." to unstage)

	new file:  ShoppingList.txt

Ok! Now it’s saying we have some changes! Yes. We went from no file to a ShoppingList.txt file with 3 items in it. Git is telling us what is going to be in our next commit. But we still haven’t told it to commit. Let’s do that.

$ git commit -m "Created shopping list for cake"

What is that -m? That’s a shortcut to enter a commit message. Never commit without a commit message, but never commit with a generic commit message like “WIP.” You want to be concise but descriptive in your commit messages. It might seem like a bit of a pain, but think about it: The whole reason we are using Git is to keep track of the changes in our files. Clear commit messages are crucial if (when) you have to look back at your files’ history or look through someone else’s work. Consult this article for examples of best practices for writing commit messages.

Congratulations! You made your first commit. Let’s make more.

That cake is a little boring. Let’s add some toppings and organize it into sections. We also want to change flour to butter since we already have flour at home. (Can you imagine this list below as code? Each “recipe” could be a function.)

--- Cake ---
Eggs
Butter
Sugar

--- Toppings ---
Strawberries
Raspberries
Chocolate

Ok, let’s do the same thing as before:

$ git status
On branch master
Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git checkout -- <file>..." to discard changes in working directory)

	modified:  ShoppingList.txt

no changes added to commit (use "git add" and/or "git commit -a")

So now we have something a little different. “Changes not staged for commit…” what does that mean? It means we’ve made some changes, but they haven’t been committed or “saved” yet.

A brief aside about how Git works: I mentioned earlier that Git keeps snapshots, or commits, of your entire project. If you make copies of each file and have thousands of commits and thousands of files in your project (my current project has 2,446), it would get really big really fast. So how does Git handle this? It only makes a new copy of the file if there were changes. Otherwise, it references the old copy of the file. Here it’s comparing the last copy of the file that it has against this new one and saying “Hey! These are different, what do you want to do?”

Let’s see what those changes are…

diff --git a/ShoppingList.txt b/ShoppingList.txt
index b61f001..774565b 100644
--- a/ShoppingList.txt
+++ b/ShoppingList.txt
@@ -1,3 +1,9 @@
+--- Cake ---
 Eggs
 -Flour
+Butter
 Sugar
+
+--- Toppings ---
+Strawberries
+Raspberries
+Chocolate

This lets us see the differences between our last commit and what we are committing now. It shows the removed lines with - and lines we’ve added with + Don’t worry about the stuff at the top for now. It’s important, but not critical to your general understanding of Git as a concept.

Now we have to decide what changes we made are going to be included in the next commit. This is called staging. Staging is where you choose the changes you want to commit, or “save.” Staging does not actually “save” anything, you must commit to do that. If you had made changes to multiple files at once, you would pick and choose which changes you wanted to stage here. There are many instances when you might not want to include all your changes, but for now, we want to include all of them.

$ git add ShoppingList.txt
$ git commit -m "Added ingredients for fruit torte, removed flour"

Awesome! Now the shopping list looks pretty good. But before we go shopping, Paul Hollywood and Mary Berry want to add some items to the shopping list. Let’s create new branches for them so they can add items to the list.

(Play along here… we’re going to do all of this ourselves, but imagine you’re really collaborating with coworkers who need to work on the same file.)

Let’s create a branch for Paul and a branch for Mary:

$ git branch pauls-branch
$ git branch marys-branch

All right! Now we’ve created branches for Paul and Mary to work on separately. Though it’s not completely accurate, you can think of creating a branch as creating a new copy of the project. Paul and Mary will then make changes on their own copies rather than on the master copy (branch). They (we) are going to make changes. First, do this:

$ git branch

This is what you should see:

marys-branch
* master
pauls-branch

We have three branches: the two that we just made and the original one, master. Continuing our highway metaphor from the beginning of the article, we now have a three-lane highway. One of those lanes is about to take an exit.

So, let’s check out a branch and start making a shopping list:

$ git checkout pauls-branch

If you want, run $ git branch again and you’ll see the star is now on pauls-branch. Checking out a branch makes it your active working copy. Any changes you make to your files will be committed to that branch.

Ok, time to add some stuff to Paul’s shopping list. Open up your shopping list and make the following changes:

--- Cake ---
Eggs
Unsalted Butter
Sugar

--- Toppings ---
Blackberries
Raspberries
Chocolate

Paul is pretentious and likes unsalted butter, so he changed butter. He also changed strawberries to blackberries. Save your file and let’s stage and commit these changes.

$ git add -A

What’s this? It’s just a shortcut for staging all the files that have been modified. In Git there are multiple ways to do the same thing.

$ git commit -m "Added ingredients for summer fruit genoise"

We only want one shopping list, so let’s merge this into master. Checkout master first (remember, this is the main highway).

$ git checkout master

When you checkout a different branch, Git actually swaps the files out automatically in your directory. If you don’t see changes in your text file close and reopen it.

Now we are going to merge Paul’s branch back into our original shopping list:

$ git merge pauls-branch

Not so bad, right? Now Paul’s changes should be reflected in our list! Open it up and check it out. Let’s checkout Mary’s branch and add some things to it.

$ git checkout marys-branch

Open up the shopping list. You’ll see that it’s back to the state that we branched off of. Let’s make Mary’s changes. (Change strawberries to blueberries.)

--- Cake ---
Eggs
Butter
Sugar

--- Toppings ---
Blueberries
Raspberries
Chocolate

Ok. Save the file, then stage and commit the changes.

$ git add -A
$ git commit -m "Added ingredients for blueberry cake"

Ok! Let’s merge this into the master branch.

$ git checkout master
$ git merge marys-branch

Uh oh! Something went wrong!

CONFLICT (content): Merge conflict in ShoppingList.txt
Automatic merge failed; fix conflicts and then commit the result.

So what’s happening here? As you may have guessed, this was intentional. This is called a merge conflict, and as in life conflicts happen all the time.

When I first started, hearing the words “merge conflict” filled me with dread. Let’s try to understand it so they’re less scary. What causes a merge conflict? According to Github help, “Merge conflicts happen when you merge branches that have competing commits.” Let’s unpack that a little. Paul and Mary both branched off at the same time, which means that they both started with the same version of the shopping list. Paul made some changes, and Mary made some changes. We merged Paul’s changes into our master shopping list, and now we’re trying to merge Mary’s version in.

Even though this wasn’t the original file we started with, Git is still going try to make the changes. It knows that on line 6 we are trying to change strawberries -&gt; blueberries but we have blackberries instead of strawberries because Paul changed it. We have “competing commits.” Git doesn’t know what to do here and needs human guidance. That’s where we come in.

Open up your ShoppingList.txt file. You’ll see some new stuff in there:

--- Cake ---
Eggs
Unsalted Butter
Sugar

--- Toppings ---
<<<<<<< HEAD
Blackberries
=======
Blueberries
>>>>>>> marys-branch
Raspberries
Chocolate

<<<<<<< and ======= are called conflict markers. They show you the conflicts between the two files that you need to resolve. The HEAD is your working branch, or the branch you’re trying to merge into. In this case, it’s saying the HEAD says raspberries. Mary’s branch says blueberries, what should I do?

If we wanted to say, “No, Paul, blackberries are too expensive right now,” we would delete blackberries. However, we’ll go ahead and keep both. This is why Git didn’t just change blackberries -> blueberries; we might want both – which we do. Delete the conflict markers, edit the file, and then save.

Now you may be asking yourself, why wasn’t there a conflict with butter? Mary’s branch had regular butter and Paul’s had unsalted butter? Well, Mary didn’t try to change butter, so Git chose Paul’s change, which was already in master.

--- Cake ---
Eggs
Unsalted Butter
Sugar

--- Toppings ---
Blackberries
Blueberries
Raspberries
Chocolate

Great! We’ve resolved the conflict. However, we still have to complete the merge. Remember, above it said to fix the conflicts and then commit the result.

$ git add -A
$ git commit -m "Merged marys list"

And you’ve just resolved your first merge conflict! Merge conflicts in the real world will be more complex than this simple example, but the basic concept is the same.

When it all goes well, the Git workflow is pretty straightforward:

  1. Checkout a branch
  2. Make changes
  3. Stage changes
  4. Commit Changes
  5. Go back to 1

Remote vs. Local

Everything we’ve done in this tutorial has been local, which means our computers are keeping track of all these files on our machine and they don’t exist anywhere else. This is not very useful if you’re collaborating with coworkers and want to do very reasonable things like use different computers. So what do you do? Track these changes online using a remote repository. Often this will be hosted on a website like BitBucket or GitHub which means that your code base (in our case, our shopping list) is going to be stored somewhere else, on someone else’s computer.

So if the code is stored elsewhere, how do we make changes to it? First,we need to clone that repository.

Cloning instructions vary based on where the repository is hosted, the type of authentication, etc, so I’ll leave this to you to figure out. What is cloning? As the name implies, cloning a repository makes an exact copy on your computer. Once you have this copy you can do work on it – making changes like we’ve discussed above.

Once you’ve made changes to your copy, they only exist locally on your computer. How do you send them to the remote repository? There are commands for that. These are by no means exhaustive, and there are lots of different circumstances requiring different types of pushes and pulls, but this will give you a basic understanding of the concept.

To push is to take changes that you have made and push them up to the remote (the place your codebase is stored). Remember when Mary made changes to the cake recipe? After that she would need to push her changes to the remote, like so:

$git push origin marys-branch

If the branch did not already exist, she would have to use:

git push -u origin marys-branch

The -u is short for –set-upstream which directs Git to create an “upstream” or remote branch. Mary Berry, of course, knows this since she is a Git expert.

To pull is to pull any changes someone else has made to your local computer. Imagine that Paul wants to look at the new changes to the master shopping list, which has been updated since he cloned the repository:

$ git pull master gets all the changes that were made since and updates the local branch.

In my opinion, pull request is a very confusing name. Let’s say that Mary is in charge of the shopping list code repository. Paul has branched off of master, has made some changes, and has pushed those changes up to the remote. He now wants those changes to be merged into master. Well, since Mary is in charge and needs to approve those changes, he submits something called a pull request or PR for short.  He is requesting that Mary pull his branch into the master branch. See? I think that’s a stretch. He’s really requesting that Mary merge his branch into master.

A pull request is made via a service like BitBucket or Github. In addition to hosting your repository, they provide a place for colleagues to submit and review each other’s pull requests(called code review). Mary will just log onto Github to view Paul’s PR.  She’ll see the changes he is requesting to make and have an opportunity to comment and request changes before approving or declining.

Git: the Final Frontier

In the real world, your project will be more complex than this. You’ll have longer files, more files, and more branches (hopefully not too many). Typically there will be a master branch, a develop branch and multiple feature branches. The master branch usually represents the code that is in production (for example a live website or the app in the iTunes Store). Develop represents code that is in progress. Developers generally will make new branches off of develop for each feature they’re working on and then merge them back into develop when complete. When a new version has been tested, is stable, and is ready to be released, develop will be merged into master.

Believe it or not, the workflow for Git is pretty much the same between the simple shopping list above and the real world. Let’s look at the flow of work that we did and compare it to a real-world workflow.

Work needs to be done. In our case, we needed to add ingredients for a cake. A real-world example could be adding a method in your data service object to connect to an API

Someone starts work.  Above, Paul and Mary made branches to add their ingredients.  In the real world, you might branch off of develop and make a branch called “feature/integrate-all-recipes-API”

Changes are made. Often this means code is written. For example, Paul added ingredients to the crackers recipe. In the real world it could be something like this function to get recipes from an API:

 func getRecipesWith(ingredients: [String],
                        success: @escaping (([Recipe]) -> Void),
                        failure: @escaping ((APIError) -> Void)) {
        let endpoint = "\(DataStore.ingredientFilterEndpoint)"
        requestManager.request(withEndpoint: endpoint,
                               method: .GET,
                               params: [DataStore.ingredientsKey: ingredients.commaDelimitedComponents],
                               success: { responseObject in
                                guard
                                    let json = responseObject as? JSON,
                                    let recipeList = RecipeList(json: json),
                                    let recipes = recipeList.recipes else {
                                        failure(APIError(failingURL: endpoint,
                                                         message: "Cannot parse Recipe from JSON"))
                                        return
                                }
                                success(recipes)
        }, failure: { error in
            if let error = error {
                failure(error)
            }
        })
    }

Changes are staged. We staged all of the changes in this tutorial, but if we had written something we didn’t want committed– for example, a test function like:

func generateFakeRecipe() -> Recipe

We might choose not to stage this.

Changes are committed. We make a commit of the changes we want to keep. One of Paul’s was “Added fruit for summer genoise”, a real world one might be “Added method in data store to retrieve recipe array filtered by ingredient”

Changes are pushed. When work is complete, the branch is pushed to remote.

Changes are reviewed. Peers review and comment/request changes.

Changes are merged. The branch is merged in!

Is this all there is to know about Git? Far from it. You will probably have to do more research on your own before you feel comfortable. You will definitely have to practice, screw up, and practice some more. Try not to be too scared– almost everything can be undone. Git is very safe. Don’t be afraid to ask for help; most developers remember what it was like to learn Git.

I hope you were able to get from this article something that I didn’t have: a consolidated place to learn the very basics of Git in an easy to understand way without making any assumptions. Good luck and happy coding!