Why Should I Know Git as a Network Engineer?

Version control systems, primarily Git, are becoming more and more prevalent outside of the realm of software development. The increase in DevOps, network automation, and infrastructure as code practices over the last decade has made it even more important to not only be familiar with Git but proficient with it. As teams move into the realm of infrastructure as code, understanding and using Git is an important skill.

This is the first of a multipart series intended to share practical knowledge about Git as well as some tips and tricks that I have learned over the years that will hopefully make your journey easier!

What You’ll Learn

Basic commands to create and utilize a local Git repository

Installing Git

You will need Git version 2.23 or higher installed to follow along.

TIP: Running Windows? If you install Git for Windows from the provided link, Bash emulation will be installed, which will allow you to follow the steps here locally on your Windows machine.

Git is a version control system, or VCS. Version control systems record changes to files over time, giving you the ability to recall previous revisions and see the history of changes to the files. On the surface it seems simple, but things get more complicated when you start working with others on the same set of files. Git is known as a distributed VCS, which means that a copy of the repo is available on every team member’s workstation. This is primarily how the Linux kernel development team utilizes Git. For most, the use of a centralized location like GitHub, GitLab, or an internal Git server is typical, especially because there is even more functionality associated with these centralized Git systems like GitHub and GitLab.

Short History

Git was created by Linus Torvalds in 2005 to replace a commercial VCS used by the Linux kernel developers. Torvalds, who was also the creator of the Linux kernel, designed Git to:

Nowadays, Git is maintained by a team of open-source developers who are continuing to improve Git as we know it.

Reference: https://git-scm.com/book/en/v2/Getting-Started-A-Short-History-of-Git

Unlike other version control systems, Git uses a snapshot method to track changes instead of a delta-based method. Every time you commit in Git, it basically takes a snapshot of those files that have been changed while simply linking unchanged files to a previous snapshot, efficiently storing the history of the files. Think of it as a series of snapshots where only the changed files are referenced in the snapshot and unchanged files are referenced to previous snapshots.

Git operations are local for the most part, meaning it does not need to interact with a remote or central repository. (There are specific commands that do interact with remote repositories, but we will be covering that in the next article.)

Everything in Git is checksummed. This not only maintains the integrity of the repository by allowing Git to detect corruption, but it is used as the reference to most objects in Git, such as commits, tags, etc. Git uses SHA-1 hash, which is represented as a 40-character string of hex (digits and a-f characters) as the checksum. You will see these types of hashes a lot when working with Git.

It is important to note (and this will become clearer as you use Git) that Git has three main states for a file:

This will make more sense later once you see it in action.

Reference: https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F

I think it’s important to understand the basic operations in Git, and one of the best ways is to create your own local repository to learn. Now, a local repository is just that—local. It does not have any remote repository locations configured, and we will not be interacting with a remote repository. Don’t worry, we’ll be covering remote repositories in the next article in this series.

We will be working with the command-line version of Git. For the most part, a majority, if not all, graphical user interface Git tools have the same concepts or even refer to the operations the same way as the command-line operations. Understanding the command line will be helpful to later using graphical Git tools.

The git config command allows the setting and getting of configuration options systemwide, global for the user, or at the repository level.

Setting Git User Information

Before we can really do anything in Git, we need to set our user.name and user.email. These are used when we commit and will become much more important when working with remote repositories. Typically, these are set globally, but they can be set at the repository level as well. When a configuration option is set at the repository level, that value overrides the set global configuration values.

Let’s set your user.name and then user.email:

Setting Git user information


MacOS $ git config --global user.name "Tony Roman"
MacOS $ git config --global user.email "tonyroman@example.com"
MacOS $ git config --global --list
user.name=Tony Roman
user.email=tonyroman@example.com
MacOS $

In the above example, I’m setting the user.name value to Tony Roman and the user.email value to tonyroman@example.com. You should set your settings to the name and email address that you use.

Setting Git Default Branch to main

Another configuration option worth using to align with some community changes around naming conventions is setting the default branch to main instead of master.

Let’s set the init.defaultBranch to main so that when we create a Git repository, the default branch is correctly named main:

Setting git init default branch to main


MacOS $ git config --global init.defaultBranch main
MacOS $ git config --global --list
user.name=Tony Roman
user.email=tonyroman@example.com
init.defaultbranch=main
MacOS $

The git init command is used to create a Git repository either in the current directory or a directory name passed as the first argument to the command.

Create a Repository

Let’s create a Git repository named git-series:

Initializing a Git repository


MacOS $ git init git-series
Initialized empty Git repository in /Users/toroman/git-series/.git/
MacOS $

TIP: Doing a git init without specifying the directory will initialize the Git repository in the current directory. This is useful if you want to take an existing directory and start tracking files with Git.

The git status command is probably the Git command I use the most. This command gives you the status of the repository and file for every possible state. Because I believe that doing is the best way to learn, we’ll be using git status in conjunction with the rest of the Git commands we will be working with.

Run git status

Let’s change to the git-series directory and run git status:

Empty Git Status


MacOS $ cd git-series
MacOS $ git status
On branch main

No commits yet

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

Notice how the git status command told us we are On branch main, with No commits yet. To keep this simple, we will cover branches when we talk about working with others.

By default, git status has pretty helpful output. It typically outputs helpful hints and the associated commands to guide you along. An example of something helpful is the nothing to commit (create/copy files and use “git add” to track) output at the end. If even tells you we have not made any commits to the repository yet.

Create a New File

OK, now we have this empty Git repository, and we want to start tracking our files. It is pretty common in open-source software to have a README.md text file written in Markdown, so let’s create one.

Git Add New File Create File


MacOS $ echo "# Git Series Readme" > README.md
MacOS $

Check the Status

Now let’s check the status of the new file.

Git Add New File Status


MacOS $ git status
On branch main

No commits yet

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

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

Notice how the new file shows up in the Untracked files:. We created the new file, but we didn’t add it to Git for tracking, hence it being an untracked file.

Add New File to be Tracked

Now let’s add the new file so that it is being tracked.

Git Add New File Add


MacOS $ git add README.md
MacOS $ git status
On branch main

No commits yet

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

MacOS $

Commit Tracked Files

Now the file is added and being tracked by Git, but we still need to commit it or record our changes. Let’s do that and see how the status changes.

Git Commit New File


MacOS $ git commit
[main (root-commit) 351f1ed] Add new README.md
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
MacOS $ git status
On branch main
nothing to commit, working tree clean
MacOS $

When you invoke the git commit command, it will typically open your default editor—in my case, vim—to create the commit message. As you can see, Git populates the commit message with helpful text, but notice how the helpful text lines start with # character. It is important to note technically that the help text is commented out so that when you save the commit message, the commented out lines are ignored.

Git Commit VIM Editor

TIP: I always advise people to think of commit messages as a short description of the changes that are being committed to the repository. I also like to think about this as a message to my future self. What do I mean? Well, in the moment, you might understand the context of your commit message, but later, even years later, you might not remember the context or what you were thinking when you wrote that commit message. Be concise and descriptive without being overly wordy. By writing a good commit message, you also help others you may be collaborating with now or in the future.

Modify a File

By now, we have added a file and committed it to the repository, which is just a first step. Now let’s add a few more lines to the README.md, modify it, and then check the status.

Git Modify File Edit


MacOS $ echo >> README.md
MacOS $ echo 'Welcome to my first local repository!' >> README.md
MacOS $ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   README.md

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

Once again, the git status command tells us typical actions that are taken. In this case, we can stage the modified file by using the git add, or, because the file is already being tracked, we can use the git commit -a command to add any modified tracked files to staging before committing. Think of -a as a shortcut; you would otherwise have to use the git add and then git commit commands to do the same work that git commit -a will do.

Reviewing Changes

We have changed the README.md, and we know this by the modified status of the file. We know what we changed—we added a new line and a welcome message—but let’s review the changes with the git diff command.

Git Modify File Diff


MacOS $ git diff
diff --git a/README.md b/README.md
index 9e8201a..7e16aee 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
 # Git Series Readme
+
+Welcome to my first local repository!
MacOS $

The git diff output is like most difference utilities outputs. First, we have the header information about the file in question, but Git adds more information like the index line. The rest is pretty standard: the filename in question, the line number of the change, and then the change represented with + meaning an addition and - meaning a removal. In our case, we added two lines, and we can confirm that by finding the + sign in front of the two lines we added.

When dealing with multiple changed, added, and/or removed files, the output of the git diff command can get long. If you want to just review a single file or files with in a directory using git diff, then you can simply add the file or directory to the end of the command.

TIP: The git diff command shows the difference between the file Git knows to the changes currently not staged. Once the files have been staged, the git diff command will not show the differences between the file Git has snapshotted and the staged file unless you use the git diff --cached command.

Commit Changes

Now let’s commit our changes to the README.md file. We are going to use one command with options to git add and git commit without opening an editor for the commit message, using the git commit -a -m "<message>" command.

Git Modify File Commit


MacOS $ git commit -a -m "Update README.md to include welcome message"
[main let's] Update README.md to include welcome message
 1 file changed, 2 insertions(+)
MacOS $ git status
On branch main
nothing to commit, working tree clean
MacOS $

From time to time, you need to move or rename files in your repository. For that, we use the git mv command. First, let’s set up a scenario with some new, incorrectly named files. We will commit the files, then realize we named them wrong and have to fix them.

Create Files and Commit

Let’s create our files, then add and commit them to the repository.

Git Move Add Files Commit


MacOS $ mkdir devices
MacOS $ echo "sw0.main.office" >> devices/switches
MacOS $ echo "sw1.main.office" >> devices/switches
MacOS $ echo "sw0.branch.office" >> devices/switches
MacOS $ echo "r0.main.office" >> devices/routers
MacOS $ echo "r0.branch.office" >> devices/routers
MacOS $ git status
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	devices/

nothing added to commit but untracked files present (use "git add" to track)
MacOS $ git add devices/
MacOS $ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   devices/routers
	new file:   devices/switches

MacOS $ git commit -m "Add switches and routers"
[main 0c72f59] Add switches and routers
 2 files changed, 5 insertions(+)
 create mode 100644 devices/routers
 create mode 100644 devices/switches
MacOS $ git status
On branch main
nothing to commit, working tree clean
MacOS $

As you can see, we created the files, then checked the Git status. Now we cannot use the shortcut to add modified files because these are new. We have to use the git add command to change their status from Untracked to Staged so that we can commit. Finally, we commit using the command-line option for the commit message.

Renaming Files

Now, after some review, we realize that we do not want to use the plural for the switching and routing device files. Let’s move them around to fix it.

Git Move Files Commit


MacOS $ git mv devices/switches devices/switch
MacOS $ git mv devices/routers devices/router
MacOS $ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    devices/routers -> devices/router
	renamed:    devices/switches -> devices/switch

MacOS $ git commit -m "Remove plural context for switch and router files"
[main cf2dfb6] Remove plural context for switch and router files
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename devices/{routers => router} (100%)
 rename devices/{switches => switch} (100%)
MacOS $ git status
On branch main
nothing to commit, working tree clean
MacOS $

You can see that when we use the git mv command, it stages the move immediately. With the move already staged, we just have to commit the update.

And finally, we need to remove a file from the repository. For that, we use the git rm command.

Removing Files

Let’s go ahead and remove the devices/router file and commit that change.

Git Remove File Commit


MacOS $ git rm devices/router
rm 'devices/router'
MacOS $ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	deleted:    devices/router

MacOS $ git commit -m "Remove router devices file"
[main 4612520] Remove router devices file
 1 file changed, 2 deletions(-)
 delete mode 100644 devices/router
MacOS $ git status
On branch main
nothing to commit, working tree clean
MacOS $

Once again, like the git mv command, the git rm command automatically stages the removal. Because it is already staged, we can then commit the changes to the repository with git commit.

Now that we have a commit in the repository, let’s review the work we have done.

Git Log


MacOS $ git log
commit 46125202f573989ee8d2024c49f879132ccc7c05 (HEAD -> main)
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:43:51 2022 -0400

    Remove router devices file

commit cf2dfb65aa1d05b10cc6d4ae5afc33ebdc0a603f
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:30:48 2022 -0400

    Remove plural context for switch and router files

commit 0c72f59874b405c6b38643990dccd5962a603c79
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:23:04 2022 -0400

    Add switches and routers

commit 4b58fbf4905daaafa7f7c89fb61dc9ea69545e67
Author: Tony Roman <tonyroman@example.com>
Date:   Mon Jul 11 20:39:58 2022 -0400

    Update README.md to include welcome message

commit 351f1ed01e58124e9f23ef07727b669c2b1e7fda
Author: Tony Roman <tonyroman@example.com>
Date:   Mon Jul 11 14:35:17 2022 -0400

    Add new README.md
MacOS $

The git log command lets you view a list of the commits or snapshots made to the repository.

Let’s focus on the first block the git log command outputted.

commit 46125202f573989ee8d2024c49f879132ccc7c05 (HEAD -> main)
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:43:51 2022 -0400

    Remove router devices file

The first line starts with commit, and following on the same line after commit is the SHA-1 checksum for the commit. Almost everything in Git is referenced by the SHA-1 or a shorted version of it. The trailing text denotes the HEAD or the most up-to-date commit in the main branch. We have been working in the default branch of main within this repository. Branches and how they work will be covered in the next article. The Author and Date are pretty self-explanatory. Then finally, the commit message is shown.

The git show command along with git log command are useful in reviewing what has been done in the repository. The git show command shows “various types of objects,” as the documentation summarizes. Here, we are going to use it to look at the git diff of a particular commit made on the repository.

First, let’s use git log and copy the SHA-1 commit checksum from the third commit from the top.

Git Show Log 3


MacOS $ git log -n 3
commit 46125202f573989ee8d2024c49f879132ccc7c05 (HEAD -> main)
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:43:51 2022 -0400

    Remove router devices file

commit cf2dfb65aa1d05b10cc6d4ae5afc33ebdc0a603f
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:30:48 2022 -0400

    Remove plural context for switch and router files

commit 0c72f59874b405c6b38643990dccd5962a603c79
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:23:04 2022 -0400

    Add switches and routers
MacOS $

Now, if you are following along at home, the SHA-1 checksum will probably not be the same, so you will need to copy it from the git log output. Let’s copy the SHA-1 checksum for the last commit displayed.

Git Show


MacOS $ git show 0c72f59874b405c6b38643990dccd5962a603c79
commit 0c72f59874b405c6b38643990dccd5962a603c79
Author: Tony Roman <tonyroman@example.com>
Date:   Tue Jul 12 11:23:04 2022 -0400

    Add switches and routers

diff --git a/devices/routers b/devices/routers
new file mode 100644
index 0000000..704aefd
--- /dev/null
+++ b/devices/routers
@@ -0,0 +1,2 @@
+r0.main.office
+r0.branch.office
diff --git a/devices/switches b/devices/switches
new file mode 100644
index 0000000..4767bdf
--- /dev/null
+++ b/devices/switches
@@ -0,0 +1,3 @@
+sw0.main.office
+sw1.main.office
+sw0.branch.office
MacOS $

As you can see from the output of the git show command, it looks like git log and git diff outputs together for one commit. In the case of this commit, we added two files. The git diff output as part of git show tells us the new files and then proceeds to show the content of the file preceded with + denoting the addition of those lines in a new file. The new file is denoted by the git diff output of --- /dev/null, which basically says, in computer geek speak, that the file did not previously exist or was null.

The git show command can be very helpful to view many things in a Git repository, but the primary usage case is typically to inspect what was done in a commit.

We did not really talk about the difference statuses a file can potentially have, but I think it is important to cover it.

There are basically four statuses:

Git File Life Cycle Diagram

When you add new files, they are initially Untracked. We use the git add command for the new files status to be changed to Staged.

Editing files that already exist in the repository will change their status to Modified. Modified files need to be staged using the git add command to change the status to Staged.

Staged is the status that git add, git rm, and git mv commands use to immediately change said files because there is not a Modified status for those operations. Files must be Staged before they can be committed to the repository, so Modified files need to be added to staging using the git add command.

As you can see on the diagram, there is a Remove arrow from Unmodified to Untracked. By default, when you use the git rm command, it removes the file. In the background, the file is moved from Unmodified to Untracked, then deleted by the git rm command. It is ultimately removed after following this status path.

Finally, Unmodified is the status once the Staged files have been committed to the repository.

Reference: https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository

Summary

After all that we have covered, you should now be able to create your own local Git repository to track not just coding projects, but configuration files, your notes, almost anything! Think of Git as a tool to track and record changes over time for any file-based project you are working on.

More Learning

What’s next?

Next in this series on Git, we move into the real world of working effectively in a team. Not only will I be covering the technical aspects of using a remote repository, including branching, merging, and rebasing, we will also be covering strategies to avoid common issues and pitfalls.