Introduction to Git and GitHub for Python Developers

Introduction to Git and GitHub for Python Developers

by Jim Anderson basics tools

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Introduction to Git and GitHub for Python Developers

Have you ever worked on a Python project that stopped working after you made a change here or a PEP-8 cleanup there, and you weren’t quite sure how to get it back? Version control systems can help you solve that problem and other related ones. Git is one of the most popular version control systems today.

In this tutorial, I’ll walk you through what Git is, how to use it for your personal projects, and how to use it in conjunction with GitHub to work with other people on larger projects. We’ll look at how to create a repo, how to add both new and modified files, and how to navigate through your project’s history so you can “get back” to when your project was working.

This article assumes you already have Git installed on your system. If you don’t, the excellent Pro Git book has a section on how to do that.

What Is Git?

Git is a distributed version control system (DVCS). Let’s break that down a bit and look at what it means.

Version Control

A version control system (VCS) is a set of tools that track the history of a set of files. This means that you can tell your VCS (Git, in our case) to save the state of your files at any point. Then, you may continue to edit the files and store that state as well. Saving the state is similar to creating a backup copy of your working directory. When using Git, we refer to this saving of state as making a commit.

When you make a commit in Git, you add a commit message that explains at a high level what changes you made in this commit. Git can show you the history of all of the commits and their commit messages. This provides a useful history of what work you have done and can really help pinpoint when a bug crept into the system.

In addition to showing you the log of changes you’ve made, Git also allows you to compare files between different commits. As I mentioned earlier, Git will also allow you to return any file (or all files) to an earlier commit with little effort.

Distributed Version Control

OK, so that’s a version control system. What’s the distributed part? It’s probably easiest to answer that question by starting with a little history. Early version control systems worked by storing all of those commits locally on your hard drive. This collection of commits is called a repository. This solved the “I need to get back to where I was” problem but didn’t scale well for a team working on the same codebase.

As larger groups started working (and networking became more common), VCSs changed to store the repository on a central server that was shared by many developers. While this solved many problems, it also created new ones, like file locking.

Following the lead of a few other products, Git broke with that model. Git does not have a central server that has the definitive version of the repository. All users have a full copy of the repository. This means that getting all of the developers back on the same page can sometimes be tricky, but it also means that developers can work offline most of the time, only connecting to other repositories when they need to share their work.

That last paragraph can seem a little confusing at first, because there are a lot of developers who use GitHub as a central repository from which everyone must pull. This is true, but Git doesn’t impose this. It’s just convenient in some circumstances to have a central place to share the code. The full repository is still stored on all local repos even when you use GitHub.

Basic Usage

Now that we’ve talked about what Git is in general, let’s run through an example and see it in action. We’ll start by working with Git just on our local machine. Once we get the hang of that, we’ll add GitHub and explain how you can interact with it.

Creating a New Repo

To work with Git, you first need to tell it who you are. You can set your username with the git config command:

Shell
$ git config --global user.name "your name goes here"

Once that is set up, you will need a repo to work in. Creating a repo is simple. Use the git init command in a directory:

Shell
$ mkdir example
$ cd example
$ git init
Initialized empty Git repository in /home/jima/tmp/example/.git/

Once you have a repo, you can ask Git about it. The Git command you’ll use most frequently is git status. Try that now:

Shell
$ git status
On branch master

Initial commit

nothing to commit (create/copy files and use "git add" to track)

This shows you a couple of bits of information: which branch you’re on, master (we’ll talk about branches later), and that you have nothing to commit. This last part means that there are no files in this directory that Git doesn’t know about. That’s good, as we just created the directory.

Adding a New File

Now create a file that Git doesn’t know about. With your favorite editor, create the file hello.py, which has just a print statement in it.

# hello.py
print('hello Git!')

If you run git status again, you’ll see a different result:

Shell
$ git status
On branch master

Initial commit

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

   hello.py

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

Now Git sees the new file and tells you that it’s untracked. That’s just Git’s way of saying that the file is not part of the repo and is not under version control. We can fix that by adding the file to Git. Use the git add command to make that happen:

Shell
$ git add hello.py
$ git status
On branch master

Initial commit

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

   new file:   hello.py

Now Git knows about hello.py and lists it under changes to be committed. Adding the file to Git moves it into the staging area (discussed below) and means we can commit it to the repo.

Committing Changes

When you commit changes, you are telling Git to make a snapshot of this state in the repo. Do that now by using the git commit command. The -m option tells Git to use the commit message that follows. If you don’t use -m, Git will bring up an editor for you to create the commit message. In general, you want your commit messages to reflect what has changed in the commit:

Shell
$ git commit -m "creating hello.py"
[master (root-commit) 25b09b9] creating hello.py
 1 file changed, 3 insertions(+)
 create mode 100755 hello.py

$ git status
On branch master
nothing to commit, working directory clean

You can see that the commit command returned a bunch of information, most of which isn’t that useful, but it does tell you that only 1 file changed (which makes sense as we added one file). It also tells you the SHA of the commit (25b09b9). We’ll have an aside about SHA a bit later.

Running the git status command again shows that we have a clean working directory, meaning that all changes are committed to Git.

At this point, we need to stop our tutorial and have a quick chat about the staging area.

Aside: The Staging Area

Unlike many version control systems, Git has a staging area (often referred to as the index). The staging area is how Git keeps track of the changes you want to be in your next commit. When we ran git add above, we told Git that we wanted to move the new file hello.py to the staging area. This change was reflected in git status. The file went from the untracked section to the to be committed section of the output.

Note that the staging area reflects the exact contents of the file when you ran git add. If you modify it again, the file will appear both in the staged and unstaged portions of the status output.

At any point of working with a file in Git (assuming it’s already been committed once), there can be three versions of the file you can work with:

  • the version on your hard drive that you are editing
  • a different version that Git has stored in your staging area
  • the latest version checked in to the repo

All three of these can be different versions of the file. Moving changes to the staging area and then committing them brings all of these versions back into sync.

When I started with Git, I found the staging area a little confusing and a bit annoying. It seemed to add extra steps to the process without adding any benefits. When you’re first learning Git, that’s actually true. After a while, there will be situations where you come to really appreciate having that functionality. Unfortunately, those situations are beyond the scope of this tutorial.

If you’re interested in more detailed info about the staging area, I can recommend this blog post.

.gitignore

The status command is very handy, and you’ll find yourself using it often. But sometimes you’ll find that there are a bunch of files that show up in the untracked section and that you want Git to just not see. That’s where the .gitignore file comes in.

Let’s walk through an example. Create a new Python file in the same directory called myname.py.

Python
# myname.py
def get_name():
    return "Jim"

Then modify your hello.py to include myname and call its function:

Python
# hello.py
import myname

name = myname.get_name()
print("hello {}".format(name))

When you import a local module, Python will compile it to bytecode for you and leave that file on your filesystem. In Python 2, it will leave a file called myname.pyc, but we’ll assume you’re running Python 3. In that case it will create a __pycache__ directory and store a pyc file there. That is what’s shown below:

Shell
$ ls
hello.py  myname.py
$ ./hello.py
hello Jim!
$ ls
hello.py  myname.py  __pycache__

Now if you run git status, you’ll see that directory in the untracked section. Also note that your new myname.py file is untracked, while the changes you made to hello.py are in a new section called “Changes not staged for commit”. This just means that those changes have not yet been added to the staging area. Let’s try it out:

Shell