Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.

Git Basics

A basic overview of Git accompanied by some tips, tricks, and opinions on workflow for using it each day. This in no way attempts to be a complete guide, just a simple set of heuristics to help out beginners new to Git.

I personally learned Git best by watching Scott Chacon's videos, reading through Git's documentation, and browsing through Chacon's Pro Git book. Remember, Stack Overflow is your friend, as well.


Also, note that the Git basics in this Skillshare will be given at a somewhat simplified level. Some of what is said might be slightly incorrect, but the intent is not to be 100% textbook accurate in descriptions and explanations. Ease-of-use and rememberance is my main goal, and painting bike sheds is furthest from what I'm trying to accomplish.

What is Git?

This question has been covered numerous times all over the internet, but I think of Git as a way of saving files at specific points in time. This way, in case you mess anything up, you can go back to a specific point and work from there.

Git also allows you to experiment freely with projects. If you had a car project and wanted to add wings to it, you could do so by setting up a wings branch. And, if anything went wrong, you could just rewind time to a commit where the car previously worked perfectly.


Git can easily be installed from the official website.

Git Started

If you've never created a Git repository before, then let's start from the command line (your computer's Terminal on Mac OS X).

First, we'll create a new project.

$ mkdir letters
$ cd letters

And we'll initialize this as a Git repository.

$ git init

Now it's time to add some files. We'll do so using the touch command and Unix bracket completion.

$ touch
$ touch {a,b,c}.txt

You should now have four blank files in your repo. Let's go ahead and create our first commit.

$ git add --all
$ git commit -m "First commit"


Github advocates a simple commit message style:

Capitalized, short summary (less than 50 chars)

And, Tim Pope advocates using present tense rather than past tense, too.

Write your commit message in the present tense: "Fix bug" and not "Fixed
bug." This convention matches up with commit messages generated by
commands like git merge and git revert.


Our letters project is pretty cool so far, but what if we need to go off on a tangent? Maybe three letters doesn't feel like enough, but we don't want to screw up our current progress.

This is where branches come in handy. Let's create a new branch and add a new letter file.

$ git checkout -b more

So, we talked about branches, but what's with all this checkout -b business? Well, using the b flag with the checkout command lets you create new branches.

This seems weird, though. Shouldn't we use the branch command to add new branches? Well, behind the scenes, it actually is being used. Here's the long way of creating a new branch:

$ git branch -l more
$ git checkout more

Either way, you're going to have to checkout the new branch, so it's just easier doing it all in one command.

Now, let's add a couple more files.

$ touch {d,e}.txt
$ touch

Let's check the status of our currently staged files.

$ git status

It should show three untracked files. And, while status is a nice command, it uses up a lot of space. There's an easier way to see the information you're after:

$ git status -sb

The s flag creates short output, and the b flag gives us information on the current branch we're in.

Currently, I like both the d.txt and e.txt files we've created, but I'm unsure about committing the file (since it ends with a Markdown extension). Let's go ahead and add just the .txt files.

$ git add *.txt

And, then we should double check that the file wasn't added.

$ git status -sb

In fact, it's good practice to ALWAYS check the status before making a commit. And, since our double checking went okay, we can now make our second commit.

$ git commit -m "Add txt files"

We don't want to lose our progress by making the file, though. So, let's go ahead and create another branch (in fact, you should think of branches as completely free -- make as many as you'd like).

$ git checkout -b markdown

Now that we're in the markdown branch, let's add the file to staging.

$ git add

And, lastly, we'll check our status before committing.

$ git status -sb
$ git commit -m "Add first markdown file"

Switching Branches

We can now list out all our branches.

$ git branch

In order to switch, we need to use the checkout command.

$ git checkout more

Now, we can list the files in this current branch.

$ ls -l

This commit looks good (notice that the file no longer exists in this branch), so let's go ahead and merge it with the master branch.

$ git checkout master
$ git merge more

Awesome! Now your branches have been merged, and the new files should appear. This should be the preferred way of developing new features -- I think of the master branch as always being golden, and new features should always be developed in separate branches.

We really don't need the more branch anymore, so let's go ahead and delete that.

$ git branch -d more

And, when we list out our branches, only two should appear now.

$ git branch


The clone command allows you to work on projects that are already up on the web (you'll mostly be using this with projects already up on Github).

We don't necessarily need to use this command right now, but I'll go ahead and leave a quick example.

$ git clone

This would clone the work currently done in the Code for America Skillshares repo into a directory named skillshares. If we didn't want that name, and instead wanted a name like Fred, we could have run the following command:

$ git clone Fred

Alternatively, you could always clone and then mv the directory.

$ git clone
$ mv skillshares Fred


Another useful command is git stash. This lets you "save" changes, without really saving them.

For instance, let's create a new file.

$ touch f.txt

And, let's add it to our staging area.

$ git add --all

Then, we remember that we don't really need that file right now, but we don't want to go ahead and create a new branch. It'd be nice to just put it somewhere before we're ready to continue.

This is where stash comes in handy.

$ git stash

Now we're back to the last commit, and the f.txt file is nowhere to be seen. But, we decide we probably want it back -- this is where stash pop can be used.

$ git stash pop

And boom, we're back to having the f.txt file added to the current staging area. You can double check this by using the status command that we used earlier.

$ git status -sb

Lastly, I just want to note, while stash is a pretty useful command, it's also a little dangerous if you mess up your workflow with it. As for day-to-day working heuristics, I'd advocate creating a one-off branch rather than relying on stash.

Remember: branches are completely free and easy-to-use, so rely on them when working on commits and ideas that might never make it back to the master branch.


Inevitably, you'll have to undo a previous commit. If it you haven't already pushed to a remote (such as Github, Heroku, etc.) then reset is probably the command you're looking for -- otherwise, you should use revert.

With reset, you can delete a commit and keep the changes with the --soft command. Alternatively, you can use the --hard flag to delete a commit and delete the changes, as well.

Let's go ahead and commit the f.txt file, then play around with the reset and revert commands.

$ git status -sb
$ git commit -m "Add f.txt file"

Now that we've made our commit, let's undo it.

$ git reset HEAD^

The ^ character is shorthand for "previous commit" -- if you want a more technical explanation, I'll be happy to explain it further. But, now let's check out status:

$ git status -sb

Notice the question marks next to f.txt? This means the file is untracked, but the previous commit has been deleted. We've implicitly used the --soft flag (our commit was deleted, but the files stayed the same).

Let's go ahead and commit the file again.

$ git add --all
$ git commit -m "Add f.txt file"

Now, since we haven't pushed this code to a remote (such as Github or Heroku), and we really don't want the f.txt file, let's go ahead and do a reset with the --hard flag.

$ git reset --hard HEAD~1

Notice that we used the HEAD~1 keyword rather than HEAD^ (if we wanted to go even further back, we could use a bigger number). Also, by using the --hard flag, our commit and f.txt file should no longer exist. We can check this by checking the status and commit log.

$ git status -sb
$ git log

Undo a reset

Is it possible to undo a reset? Yes. Yes, it is.

In order to do this, we'll need to use the reflog command to list out the previous states our HEAD has been in.

$ git reflog

Your previous commit "Add f.txt" file should be at HEAD@{1}. If it is, run the following to undo your undo:

$ git reset --hard HEAD@{1}

Notice the subtle irony of using a reset --hard command to undo a reset --hard command.


While the reset command is awesome for erasing up to a certain point in time, it comes with two shortfalls: you should not use it for commits that have already been pushed to a remote (this will cause you pain -- you've been forewarned), and it can't let you undo certain commits and leave others unchanged.

This is where revert comes in. With the revert command, you can cherry pick commits that have already been pushed up to a remote (and it won't explode in your face for doing so).

With that in mind, let's get our current log to double check the f.txt file has indeed been committed.

$ git log

For an easier to read log of past commits, add the --oneline flag.

$ git log --oneline

And, if you're feeling festive, add the --graph and --decorate flags, as well.

$ git log --oneline --graph --decorate

Notice the SHA hashes next to our commit messages whenever we run the git log --oneline command? We can use those to revert certain commits. So, copy or keep in mind one of those SHA hashes (for the sake of this README, mine will be abc123) and get ready to revert it.

$ git revert abc123

That commit has now been undone (though it stays in our history), and a revert commit has also been added. You'll have to save a revert message in your text editor, as well.

Let's take this to a meta-level now, and revert that revert (I'll use the SHA hash xyz789 for the sake of this README). Also, let's specify that we don't want to save the commit message -- that should just happen automatically.

$ git log --oneline
$ git revert --no-edit xyz789

And, with that, you're now a Git ninja at undoing commits with both reset and revert.


Sometimes when commiting you need to modify the last commit message. This is where the --amend command comes in handy.

Let's say you are ready to commit your code and run:

$ git commit -m "I <# Code for America"

But you really meant to say "I heart Code for America":

$ git commit --amend

Then correct your commit message.


Remotes are an awesome feature of Git (and you'll use them quite often with services like Github and Heroku). It's hard to believe that the distributed workflow allowed by Git wasn't possible before a couple years ago.

If you think of Git as a way to save your code and progress at certain points in time, then remotes are just different locations for that code -- you've probably already internalized this by using Github.

You can add remotes with the remote command. Most of the time you won't actually be typing this out (for instance, I always just copy and paste from Github when creating a new repository).

But, with that said, here's a quick example of creating a remote named origin. Why the origin name? It's just a heuristic and best practice to name the remote you'll be pushing to most frequently as origin. For all Git cares, you could name it poop and everything would work as expected.

$ git remote add origin

Now that the remote has been added to our Git repository, we can push code up to it.

$ git push origin master

Basically, what this command says is push the master branch of my project up to the origin remote.

We could easily push a different branch up to that remote, too -- which is actually how you get multiple branches listed on Github.

$ git push origin markdown

Also, once you've pushed a branch to a remote, you'll now be able to simply write out git push origin without naming the branch.


Sometimes, you might have to force a conflicting push to a remote. This is almost never wise, and you should consult the documentation before deciding to do so. But, in the case that you do actually need to, the --force command comes in handy.

$ git push --force origin


If you've ever used remotes with other individuals before, you've probably used the pull command. The pull command performs a fetch and merge in one go (you basically use it to grab changes that have been performed by a push to a remote).

$ git pull

You might encounter conflicts when using the pull command. This basically means more than two people have modified a file, and Git is unsure about which changes should stay and which should be deleted.

If you have a particulary nasty merge conflict, it's probably best to consult the Git documentation (it's basically a problem you're going to have to work through).

Tips and Tricks

I feel the best way to round out this Skillshare is with a few tips and tricks -- especially when it comes to ignoring certain file types and setting up easy-to-use Git aliases.


Git is awesome at keeping track of files and changes, but sometimes you want to blatantly ignore certain filetypes (.DS_Store, .pyc, etc.) across all projects.

If you don't already have a global .gitignore and .gitconfig file set up, then we need to go ahead and do that.

$ cd

You should now be in your home directory. To get the name of this location, we'll pbcopy the output from the following command:

$ echo $PWD | pbcopy

Let's check to make sure that a .gitconfig file exists.

$ cat .gitconfig

If you get an error, then we need to create a .gitconfig file to look like the following (though yours will be slightly different):

  name = Zach Williams
  email =
  editor = vim
  excludesfile = /Users/zachwill/.gitignore_global
  quotepath = false
  diff = auto
  status = auto
  user = zachwill

Notice the excludesfile line? This points to my global .gitignore that lists filetypes and directories Git should always ignore. Github has an awesome repo of all kinds of .gitignore files, and I keep my personal one online, too.

You should adjust the excludesfile to point to your own global .gitignore, and you've already copied the path with the echo $PWD | pbcopy command (so just paste and append the name of your global .gitignore file).

Also, .gitignore files can be added on a per project basis, too. For instance, the .gitignore file in this repo will ignore any files with the .zach extension.


I've saved the best news for last. Customized Git commands tend to get a bit on the verbose side, which is why Git aliases are so badass. For example, instead of always having to type out git add --all, we can add an alias to have git aa accomplish the same task.

All aliases should be saved to your .gitconfig file. For an example of aliases I regularly use, check out my .gitconfig file.