PBS 104 of X: Tracking Changes (Git)
So far in this mini series we’ve looked at the larger landscape of version control, we’ve explained why we’ve chosen the distributed version control system Git for this series, and we’ve learned how to make our first Git repository and add some files to it.
In this instalment we’ll take things one step further and learn how to manage changes to our files.
Matching Podcast Episode
Listen along to this instalment on episode 658 of the Chit Chat Across the Pond Podcast.
You can also Download the MP3
Instalment Resources
- The instalment ZIP file — pbs104.zip.
Playing Along
If you’d like to play along with the examples, you’ll need to download this instalment’s ZIP file, extract the folder pbs104a
, change into it in a terminal, and execute the following commands to initialise it as a git repository with all the files added:
cd pbs104a
git init
[ `git symbolic-ref --short HEAD` = 'master' ] && git checkout -b main
git add .
git commit -m "initial version"
The majority of those commands are straight from the previous instalment, but notice that the optional command for re-naming master
to main
as needed with older versions of git
has been pre-fixed with some additional logic. Since this is Programming by Stealth rather than Taming the Terminal I won’t go into detail of exactly how that command works, instead, I suggest you save it for future use, perhaps in your text expansion utility of choice.
Having said that, for anyone who’s curious, the terminal command for conditionally re-naming master
to main
makes use of the following commands/concepts:
- the POSIX
test
command - the
git symbolic-ref
command (man git-symbolic-ref
) - So-called Lazy Evaluation to only execute the
git checkout
command when the current branch ismaster
.
Some Housekeeping for Mac Users — Globally Ignore .DS_Store
Files
To avoid macOS’s hidden .DS_Store
files getting in our way we need to configure git to globally ignore those files. We’ll explain these commands in detail in a future instalment, but for now, we’ll simply make the change without digging into the detail.
Firstly, if you’ve already a Git user and you have a global ignore file set up already, do not follow these instructions! Instead, edit your existing file and make sure it includes the line: **/.DS_Store
You can see if you have an existing global ignore file with the git config
command we learned about in the previous instalment:
git config --global core.excludesfile || echo 'NONE'
This will either show the path to your excludes file or NONE
.
To create a global excludes file that ignores all .DS_Store
files everywhere simply run:
echo '**/.DS_Store' >> ~/.gitignore_global
git config --global core.excludesfile ~/.gitignore_global
You can verify that the change has taken effect by re-running git config --global core.excludesfile || echo 'NONE'
.
Staging & Committing A Simple Change
For once, the typo in the first sentence of README.md
is not a mistake, I intentionally put it there so we could correct it 🙂.
Open README.md
in your favourite text editor and correct ‘example is instalment’ to ‘example in instalment’.
Once the typo is fixed git status
will show we have one un-staged change:
bart-imac2018:pbs104a bart% git status
On branch main
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: README.md
no changes added to commit (use "git add" and/or "git commit -a")
bart-imac2018:pbs104a bart%
Git is again being helpful, and letting us know that we can add our change to the staging area in the same way entirely new files are staged, with the git add
command.
Go ahead and stage the change with:
git add README.md
If we run git status
again we can see that our change has been successfully staged and is ready to be committed:
bart-imac2018:pbs104a bart% git status
On branch main
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README.md
bart-imac2018:pbs104a bart%
Like we did last week, we can now commit our staged change, along with a sensible commit message, with a command like:
git commit -m 'Fixed silly typo in README'
Staging & Committing Multiple Changes
Let’s make a more substantial change to our wee project — let’s edit our HTML file to use a local copy of Bootstrap 4.
This involves both adding new files (a contrib
folder containing Bootstrap’s CSS and license file), and editing our existing index.html
.
To play along at home, copy the contents of the folder pbs104a-v2
into your repository, replacing index.html
when asked.
Once you’ve updated the files, you can see all the un-staged changes with git status
:
bart-imac2018:pbs104a bart% git status
On branch main
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: index.html
Untracked files:
(use "git add <file>..." to include in what will be committed)
contrib/
no changes added to commit (use "git add" and/or "git commit -a")
bart-imac2018:pbs104a bart%
Before we do anything else, let’s stage the new contrib
folder:
git add contrib
Now, we know from git status
that we have made changes to index.html
but how do we see the actual changes we made?
Seeing Changes with git diff
To see the un-staged changes to a file use the command git diff PATH/TO/FILE
, for example, to see the changes to index.html
use:
git diff index.html
This will show all the changes in a command line pager appropriate for your OS, on Linux/Unix/Mac that will probably be either the less
or more
terminal commands. Basically, diff
will behave like man
, allowing you to navigate up and down with the arrow keys, and to exit the pager by pressing q
.
If you’d prefer not to use a pager and simply have all the changes printed to the terminal directly, use the --no-pager
flag:
git --no-pager diff index.html
Notice that the --no-pager
flag applies to the git
command as a whole, not to the diff
sub-command, so it goes between git
and diff
. As we’ll see later, this flag can be used with many git
subcommands, not just diff
.
The format of the output takes some getting used to. New and changed lines are shown pre-fixed with +
symbols, and deleted and old versions of changed lines are shows pre-fixed with -
symbols. A few lines of un-changed content are shown around the changed lines for context, and there’s some metadata shown too.
Note that the git diff
command can be used to compare versions across commits and branches as well. For details see the relevant section of the documentation or man git-diff
.
Most Git users don’t look at their changes on the command line, this is where Git GUIs really come into their own. Every Git GUI will allow you to see the changes to your files in a visual way. You may recognise some similarities to this terminal output though.
Let’s go ahead and stage the modified index.html and commit our changes:
git add index.html
git commit -m 'Moved from plain HTML to Bootstrap'
Dividing Changes Into Separate Commits
In an ideal world we would make one logical group of changes, commit it, then move on to something else, but in reality, we don’t work like that. We see an easy fix in a function next to the one we’re working on, so we fix that, or we spot a typo, or we change our mind of the wording of some error message we see in adjacent code. Or, probably the most common of all, we get into a flow and simply forget to commit! The end results is that we often have multiple logical changes in our working copy at the same time, and we really should commit them as multiple separate commits.
Git’s staging area was designed to address this inevitable reality. Conceptually, the approach is very simple, stage one set of changes, commit those, then stage the next, and commit those, and continue until everything is committed. When each logical grouping of changes affects separate files, that’s easy to do, but you can still do it when your changes are intermixed within the same files.
The Simple Case — Changes to Separate Files
To play along, copy both index.html
and README.md
from the pbs104a-v3
folder in this instalment’s ZIP into your repository (replacing the current versions of the files).
As we can see from git status
, there are two changed files:
bart-imac2018:pbs104a bart% git status
On branch main
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: README.md
modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
bart-imac2018:pbs104a bart%
These updated files contain two changes, a description of the Hello World meme in index.html
, and the inclusion of Bootstrap licensing information in README.md
.
Let’s first stage and commit the licensing info:
git add README.md
git commit -m 'Added Bootstrap licensing info to README'
As we can see with git status
, we still have one more un-staged and un-committed change:
bart-imac2018:pbs104a bart% git status
On branch main
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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
bart-imac2018:pbs104a bart%
Let’s now stage and commit that:
git add index.html
git commit -m 'Added link to Wikipedia article on the Hello World meme'
Separately Staging Changes to a Single File
To play along, copy index.html
from the pbs104a-v4
folder in this instalment’s ZIP into your repository (replacing the existing version).
This file contains two sets of edits — firstly the page has been re-worked to move the heading and the lead paragraph into a semantic header
tag, and a footer has been added. Secondly, my programming history in different hello worlds has been added into the main
tag.
You can explore these changes with git diff index.html
or using your favourite Git GUI.
We want to stage these two logical sets of changes as two separate commits. The key to doing this is Git’s ability to treat each edit to a file as a separate change, referred to as a hunk. Git GUIs will let you graphically stage individual hunks, but you can also do it from the terminal by using git add
with the --patch
(or just -p
for short) flag. When you use this flag you’ll be shown each change one-by-one, and you hit y
to stage it or n
not to. Let’s step through the changes to index.html
with:
git add --patch index.html
Git will offer you the changes at different granularities. In this case, the first hunk offered covers all the changes. We need to drill down deeper, so we can do that by splitting the hunk by entering s
and hitting return. We will then be offered a hunk for the inclusion of the header tag, so we want to accept that by hitting y
and then return. Next we’re offered the closing header
tag and the opening main
tag, we want that too, so hit y
and return again. Next we’re offered the entire programming history as a hunk, we don’t want this so hit n
and return. Finally, we’re offered the new footer
tag, which we do want, so hit y
and return on that.
We have now staged some of our changes to index.html
, but not others, so the file is both staged and un-staged, as you can see with git status
:
bart-imac2018:pbs104a bart% git status
On branch main
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: index.html
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: index.html
bart-imac2018:pbs104a bart%
We can use git diff index.html
to see our un-staged changes, but how do we see our staged changes? We can use the --cached
flag to do that, i.e. git diff --cached index.html
.
Using the --no-pager
flag we can output both sets of changes to terminal like so:
bart-imac2018:pbs104a bart% git --no-pager diff index.html
diff --git a/index.html b/index.html
index 4208db7..6cee467 100644
--- a/index.html
+++ b/index.html
@@ -15,6 +15,53 @@
<main class="container">
<p><em>Hello World</em> is a long-standing programming tradition, [learn more on Wikipedia](https://en.wikipedia.org/wiki/%22Hello,_World!%22_program).</p>
+<h2>Bart's Programming History in <em>Hello Worlds</em></h2>
+
+<pre class="border rounded p-3 bg-success text-light">
+// 1997 - Java
+public class HelloWorld{
+ public static void main(String[] args){
+ System.out.println('Hello World!');
+ }
+}
+
+// 1998 — JavaScript
+document.write("Hello World!<br>");
+
+// 1998 — C++
+#include <iostream>
+using namespace std;
+int main() {
+ cout << "Hello World!\n";
+ return 0;
+}
+
+-- 1998 SQL
+SELECT "Hello World\n";
+
+# 1999 Maple
+"Hello World!";
+
+# 1999 - PHP
+<?php echo 'Hello World!' ?>
+
+# 2000 - Perl
+print "Hello, World!\n";
+
+; 2000 - LISP
+(format t "Hello, World!~%"))
+
+/* 2001 - C */
+#include <stdio.h>
+int main(void){
+ printf("Hello World!\n);
+ return 0;
+}
+
+// 2015 - NodeJS
+console.info('Hello World!');
+</pre>
+
</main>
<footer class="container small text-muted">© Bart Busschots 2020 — Released under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noopener">Creative Commons Attribution-NonCommercial-ShareAlike</a>.</footer>
</body>
bart-imac2018:pbs104a bart% git --no-pager diff --cached index.html
diff --git a/index.html b/index.html
index 00ef106..4208db7 100644
--- a/index.html
+++ b/index.html
@@ -8,12 +8,14 @@
<link rel="stylesheet" href="contrib/Bootstrap4.5/bootstrap.min.css">
</head>
<body>
-<main class="container">
+<header class="container">
<h1 class="display-1">Hello World!!!</h1>
<p class="lead">Welcome to some dummy content for instalment 104 of the <a href="https://pbs.bart.ficer.net/" target="_blank" rel="noopener">Programming by Stealth</a> blog/podcast series.</p>
-
+</header>
+<main class="container">
<p><em>Hello World</em> is a long-standing programming tradition, [learn more on Wikipedia](https://en.wikipedia.org/wiki/%22Hello,_World!%22_program).</p>
</main>
+<footer class="container small text-muted">© Bart Busschots 2020 — Released under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noopener">Creative Commons Attribution-NonCommercial-ShareAlike</a>.</footer>
</body>
</html>
\ No newline at end of file
bart-imac2018:pbs104a bart%
Let’s now go ahead and commit the layout changes:
git commit -m 'Correct semantic markup by moving title and lead into header and adding a footer'
Running git status
again you can see we’re now down to just un-staged changes in one file, and no staged changes:
bart-imac2018:pbs104a bart% git status
On branch main
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: index.html
no changes added to commit (use "git add" and/or "git commit -a")
bart-imac2018:pbs104a bart%
We can now stage and commit our last change (my history of programming) as normal:
git add index.html
git commit -m 'Added my history of programming in Hello World programs'
Viewing Our Commits
We’ll dive into this in more detail in the next instalment, but to quickly see all our commits we can use the git log
command. If we give it no arguments it will show all our commits in reverse order in a pager. Like with git diff
we can force direct output to the terminal with the --no-pager
flag:
bart-imac2018:pbs104a bart% git --no-pager log
commit cb1e12277518e9e714bd72c2fc95e44e3108b26f (HEAD -> main)
Author: Bart Busschots <opensource@bartificer.net>
Date: Sat Oct 24 16:10:32 2020 +0100
Added my history of programming in Hello World programs
commit 51d923966741bf2a3bf56577a12d0523cea4e4f9
Author: Bart Busschots <opensource@bartificer.net>
Date: Sat Oct 24 16:07:36 2020 +0100
Correct semantic markup by moving title and lead into header and adding a footer
commit 91f1f73ea2adb93d6d5a913939a9fc4ba3db8d27
Author: Bart Busschots <opensource@bartificer.net>
Date: Sat Oct 24 15:14:10 2020 +0100
Added link to Wikipedia article on the Hello World meme
commit c4af22abd56ea75da4c80eb3fc50612e5e5e5fff
Author: Bart Busschots <opensource@bartificer.net>
Date: Sat Oct 24 15:12:36 2020 +0100
Added Bootstrap licensing info to README
commit 90d890f7d364665f3c662eea440db550c296b721
Author: Bart Busschots <opensource@bartificer.net>
Date: Sat Oct 24 15:07:32 2020 +0100
Moved from plain HTML to Bootstrap
commit daeab432cfe23297e2c76637c9fc9c4d34ac220a
Author: Bart Busschots <opensource@bartificer.net>
Date: Sat Oct 24 11:40:12 2020 +0100
Fixed silly typo in README
commit efd2b072af4340c7d3e86de6b7cb1eada5ca306e
Author: Bart Busschots <opensource@bartificer.net>
Date: Sat Oct 24 11:30:40 2020 +0100
initial version
bart-imac2018:pbs104a bart%
If you open your repository in your favourite Git client you’ll be able to see our list of commits graphically.
Final Thoughts
Our Git journey is progressing nicely. We can now create repositories, add files to them, and track our changes to those files.
In the next instalment we’ll start by learning how to explore our repository’s history, and how to time-travel by checking out older commits.