Intro to Version Control - Git

Have you ever made a change in a project and later wanted to undo it? Or you want to know “what has changed since a point in time?” Version control helps with both of these and more.

When I first started programming, I would zip up my entire project and have dated archives. While it did help with the “undo large changes” part, it didn’t solve the “what is changing between the versions” problem. A short time later, I was introduced to version control.

There are many different types of version control, Git, Subversion (svn), and Mercurial, to name a few. Let’s talk about Git. (It’s the same “git” from sites like github.com and gitlab.com)

Installing git

Git is available for Windows, macOS, and Linux.

NOTE: There are graphical tools for git like Git GUI. However, it’s worth the effort to learn git on the command line.

Windows: If you haven’t installed the Windows Subsystem for Linux (WSL), the simplest option I’ve found so far is using Git BASH which is installed by Git for Windows.

macOS: git is installed as part of the Command Line Tools for Xcode. These can either be installed by installing Xcode or by downloading the command line tools package directly.

Linux: There’s a good chance that git is preinstalled on your machine. If not, it can likely be installed via your preferred package manager.

New Terminology

  • Repository: Often shortened to “repo”. Contains your project in its current form and all its history.

  • Commit: Is used as both a concept and an action. A commit is a set of changes made in your project, similar to a point in time for your project. When you commit changes, you are adding them to the history of your project.

  • Branch: Made up of a series of commits. It’s kind of like a timeline within your repository.

┌─ Repository ────────────────────────┐
│                                     │
│          ┌── Branch                 │
│          │                          │
│          ▼                          │
│ main: ●─────●─────●─────●─────●───▸ │
│       ▲                 ▲     ▲     │
│       │                 │     │     │
│       └──── Commits ────┴─────┘     │
│                                     │
└─────────────────────────────────────┘

Create a New Repo

Time to create your first repo! While a repo can be created in any existing folder, it may be best to start with a new directory. That way, if anything goes wrong, nothing important is lost.

Feel free to make a new directory on your computer in your favorite manner. On the command line, I’ll run something like:

mkdir test-repo
cd test-repo

(test-repo is the name of the new directory. The mkdir command makes the directory and cd changes to the directory.)

Once your new directory is created and your command line is working in that directory, run:

git init

After running that command you should see something like:

$ git init
Initialized empty Git repository in ~/test-repo/.git/

You now have a new repo. By default, your branch will be either master or main (master was the default for many years. In 2022, the default was changed to main).

To see the status of your repo at any time, run git status. This will output something like:

$ git status
On branch main

No commits yet

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

Add Files

To add files to your repo, first, create a text file. This can be done in any way you like. A quick command line way is:

echo "hello world" > file.txt

What’s the status of the repo now? Running git status will report something like:

$ git status
On branch master

No commits yet

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

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

Notice the new “Untracked files” section that lists the “file.txt” file just created. When a file is “untracked” it means it’s in the same directory as your repo, but the repo isn’t watching it for changes. Since we want the repo to watch the file for changes, we need to add it to the repo.

To add a file to the repo, we can use the git add command to add files to the repo. This command requires a list of files. To add file.txt we need to run:

git add file.txt

The git add command most likely will not have any output, but if we then run git status we can see the change. Altogether, this looks something like this:

$ git add file.txt
$ git status
On branch master

No commits yet

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

Notice our file.txt is now under “Changes to be committed”! Next up, let’s commit them.

Committing changes

When we commit changes, we add them to the history of our project. Commits allow us to see what’s changed in our project over time. Note that commits are not created automatically. It’s up to you the developer to create the commit. Typically, a single logical set of work makes a good commit.

A commit needs a message which describes the set of changes it contains. This message can be whatever you want. I usually write the message so it fits in the sentence “This commit will ${commit_message}”. For example, “This commit will create starting file.txt” thus a commit message of “create starting file.txt”.

To create a commit we use the git commit command. This command takes the commit message using the -m flag. All together this will look like this:

git commit -m "create starting file.txt"

Running the command looks something like this:

$ git commit -m "create starting file.txt"
[main (root-commit) b82591d] create starting file.txt
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

Running git status will show that our file is no longer untracked or ready to be committed. Since there is now one commit, the “No commits yet” message is also gone.

$ git status
On branch main
nothing to commit, working tree clean

Since git is about tracking changes and since the repo contains all the changes in the folder, there is nothing special for the git status command to report. Thus the “nothing to commit, working tree clean” message.

Looking back in time

Before we look at the history of the repo, let’s create a few more changes. Go ahead and create a few more files, make changes to the existing one, and add a few commits. This is what I did:

$ echo "this is more text" > another-file.txt
$ git add another-file.txt
$ git commit -m "add another file"
[master 9eea9ed] add another file
 1 file changed, 1 insertion(+)
 create mode 100644 another-file.txt
$ echo "another line" >> file.txt # The ">>" here appends the content to file.txt
$ git add file.txt
$ git commit -m "add more text to file.txt"
[master e5f06c7] add more text to file.txt
 1 file changed, 1 insertion(+)
$ echo "new text" > another-file.txt
$ git add another-file.txt
$ git commit -m "replace the text in another-file.txt"
[master 90451bc] replace the text in another-file.txt
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git status
On branch master
nothing to commit, working tree clean

The git log command allows us to see what has changed in our project over time. Running it provides a reverse order history of the commits:

$ git log
commit 90451bce664543b4a2a306fe04561b190c717cb3 (HEAD -> master)
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:55:44 2022 -0400

    replace the text in another-file.txt

commit e5f06c79b7fe532fb35501e1f8261673f316f732
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:55:01 2022 -0400

    add more text to file.txt

commit 9eea9edfce1b4ac16640e74af119a7bc1d385e3f
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:54:15 2022 -0400

    add another file

commit b82591d9355573562bfbcba90ccd2b8ffe62583e
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:46:11 2022 -0400

    create starting file.txt

To see how what changes have been made, the -p flag (the “p” is for “patch”). Running git log -p results in:

commit 90451bce664543b4a2a306fe04561b190c717cb3
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:55:44 2022 -0400

    replace the text in another-file.txt

diff --git a/another-file.txt b/another-file.txt
index 23446b3..eee417f 100644
--- a/another-file.txt
+++ b/another-file.txt
@@ -1 +1 @@
-this is more text
+new text

commit e5f06c79b7fe532fb35501e1f8261673f316f732
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:55:01 2022 -0400

    add more text to file.txt

diff --git a/file.txt b/file.txt
index 3b18e51..fdff486 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1,2 @@
 hello world
+another line

commit 9eea9edfce1b4ac16640e74af119a7bc1d385e3f
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:54:15 2022 -0400

    add another file

diff --git a/another-file.txt b/another-file.txt
new file mode 100644
index 0000000..23446b3
--- /dev/null
+++ b/another-file.txt
@@ -0,0 +1 @@
+this is more text

commit b82591d9355573562bfbcba90ccd2b8ffe62583e
Author: Ethan Smith <examplegit@onebytegone.com>
Date:   Sat Sep 10 14:46:11 2022 -0400

    create starting file.txt

diff --git a/file.txt b/file.txt
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/file.txt
@@ -0,0 +1 @@
+hello world

Each commit now shows what changed in that commit, a diff (short for difference). The “+” plus sign indicates lines that have been added. The “-” minus sign indicates lines that have been removed. If a line was replaced, the old line will be marked as being removed and the new line will be marked as being added.

Conclusion

Congrats! You’ve taken the first step on the journey of version control. There is much more to discover and many useful ways of using the tool.

If you have questions on git feel free to ping me on Twitter. Don’t always have an answer, but am happy to try.

As a parting question, what other things could you put in version control? Is it limited to just code?