Mert Özen Further With Every Line
DevOps Git & GitHub — #06

Looking at Git History: git log, git show, git blame and Aliases

Mert Özen Nov 6, 2026 12 min 2 views
Looking at Git History: git log, git show, git blame and Aliases

We move around history with git log's --oneline, --graph, --stat, -p, --format, --author, --since, --grep and --follow options and see the difference between -S and -G on a line whose price changed. We inspect a single commit and an old file version with git show, trace lines with git blame -L and shorten long commands with aliases.

Version note: The commands in this post were tested with Git 2.54.

Guess First

Alan changed only the price on the "Neuromancer" line in src/books.js: 8.99 instead of 9.99. You want to find every commit involving the word "Neuromancer", so you run git log -S Neuromancer. Does Alan's commit show up in the list?

The answer is not what most people guess. In this post we look at the most-used options of git log, at git show, at git blame and at aliases that shorten long commands. This post corresponds to the "log and tracking changes" part of the videos.

git log and the Repo's New State

A few days have passed since the previous post. Alan has joined the project, and the two of them made five more commits. We will see how Alan shared these commits in the post on remotes; for now the history itself is all we need. In its plainest form:

$ git log
commit 8b5e66cab4a4e5f59b2ee1de843c956b884b9397 (HEAD -> main)
Author: Alan Turing <alan@example.com>
Date:   Thu Nov 12 10:00:00 2026 +0300

    Lower the price of Neuromancer

commit db44b0610fb6e017a6e6d6760543cf7bd43081b2
Author: Ada Lovelace <ada@example.com>
Date:   Wed Nov 11 10:00:00 2026 +0300

    Document discounts in README

commit b9d0ac5b7070886495623546f55a902b66c8b3af
Author: Alan Turing <alan@example.com>
Date:   Tue Nov 10 10:00:00 2026 +0300

    Fix rounding in cartTotal
    
    0.1 + 0.2 style float errors showed up as 22.490000000000002 in the UI.
...

I shortened the output; the rest continues the same way down to the oldest commit. The newest commit is at the top. For each commit there is the full hash, the author, the date and the indented message. (HEAD -> main) on the first line tells you where you are right now. In a terminal, if the output does not fit on the screen, Git opens it in a pager, usually less. You move around with the arrow keys, search with / and quit with q. If you want the output printed straight to the terminal, use git --no-pager log.

Short, Visual and Detailed

In daily work you rarely look at the long form:

$ git log --oneline
8b5e66c (HEAD -> main) Lower the price of Neuromancer
db44b06 Document discounts in README
b9d0ac5 Fix rounding in cartTotal
e6396b8 Add The Hobbit to the book list
f1f6068 Add discount support to cart
e351c69 Normalize line endings
1634232 Rename LICENSE.txt to LICENSE
6d89b81 Ignore build output, logs and local settings
5517793 Add license and local settings
e3ef418 Add cart total
fae4454 Add shopping cart
29f27c8 Add README and book list
$ git log --oneline -3
8b5e66c (HEAD -> main) Lower the price of Neuromancer
db44b06 Document discounts in README
b9d0ac5 Fix rounding in cartTotal

--oneline reduces each commit to a single line with the short hash and the summary line. In the earlier posts we discussed why the summary line should be short and meaningful; this is why. A number such as -3 shows only the last N commits.

$ git log --oneline --graph --all
* 8b5e66c (HEAD -> main) Lower the price of Neuromancer
* db44b06 Document discounts in README
* b9d0ac5 Fix rounding in cartTotal
* e6396b8 Add The Hobbit to the book list
...

--graph draws the shape of history to the left of the commits, and --all shows every branch, not just the one you are on. Our history is a straight line for now, because there is only one branch so far. In the branch and merge posts this line starts to fork, and this command becomes one of the ones you use most.

To see what a commit changed, there are two options. --stat gives a summary:

$ git log --stat -2
commit 8b5e66cab4a4e5f59b2ee1de843c956b884b9397 (HEAD -> main)
Author: Alan Turing <alan@example.com>
Date:   Thu Nov 12 10:00:00 2026 +0300

    Lower the price of Neuromancer

 src/books.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

commit db44b0610fb6e017a6e6d6760543cf7bd43081b2
Author: Ada Lovelace <ada@example.com>
Date:   Wed Nov 11 10:00:00 2026 +0300

    Document discounts in README

 README.md | 2 ++
 1 file changed, 2 insertions(+)

-p (patch) shows the change itself as a diff:

$ git log -p -1
commit 8b5e66cab4a4e5f59b2ee1de843c956b884b9397 (HEAD -> main)
Author: Alan Turing <alan@example.com>
Date:   Thu Nov 12 10:00:00 2026 +0300

    Lower the price of Neuromancer

diff --git a/src/books.js b/src/books.js
index 0dc2038..1097f9e 100644
--- a/src/books.js
+++ b/src/books.js
@@ -1,5 +1,5 @@
 export const books = [
   { id: 1, title: "Dune", price: 12.5 },
-  { id: 2, title: "Neuromancer", price: 9.99 },
+  { id: 2, title: "Neuromancer", price: 8.99 },
   { id: 3, title: "The Hobbit", price: 11 },
 ];

A line starting with - is the old version going out, a line starting with + the new one coming in. As we said in an earlier post, this diff is not stored anywhere; Git produced it on the spot by comparing the commit's snapshot with its parent's. We will talk about reading diffs in detail in the next post.

If the built-in formats are not enough, you can build your own with --format:

$ git log --format="%h %ad %an %s" --date=short -4
8b5e66c 2026-11-12 Alan Turing Lower the price of Neuromancer
db44b06 2026-11-11 Ada Lovelace Document discounts in README
b9d0ac5 2026-11-10 Alan Turing Fix rounding in cartTotal
e6396b8 2026-11-09 Ada Lovelace Add The Hobbit to the book list

%h is the short hash, %ad the author date, %an the author name and %s the summary line. --date=short writes the date as year-month-day. A format like this is very handy when you put together release notes or a weekly report.

Filtering: Author, Date, Message and File

As history grows, what you really need is to find what you are looking for:

$ git log --oneline --author=Alan
8b5e66c (HEAD -> main) Lower the price of Neuromancer
b9d0ac5 Fix rounding in cartTotal
f1f6068 Add discount support to cart
$ git log --oneline --since="2026-11-10 00:00" --until="2026-11-11 23:59"
db44b06 Document discounts in README
b9d0ac5 Fix rounding in cartTotal
$ git log --oneline --grep=cart
b9d0ac5 Fix rounding in cartTotal
f1f6068 Add discount support to cart
e3ef418 Add cart total
fae4454 Add shopping cart

--author looks for the text in the author's name or email; Alan is enough. --since and --until give a date range; they also understand relative expressions such as "2 weeks ago" or "yesterday". Be careful when you leave out the time: with --since=2026-11-10, Git does not use the start of the day but the time of day at which you run the command. If a commit made that morning drops out of the list, this is why. --grep searches commit messages and is case-sensitive; add -i to make it case-insensitive. You can combine these filters: git log --author=Alan --grep=cart returns the commits that match both.

To see the history of a file, you give its path after --:

$ git log --oneline -- src/books.js
8b5e66c (HEAD -> main) Lower the price of Neuromancer
e6396b8 Add The Hobbit to the book list
29f27c8 Add README and book list
$ git log --oneline -- LICENSE
1634232 Rename LICENSE.txt to LICENSE
$ git log --oneline --follow -- LICENSE
1634232 Rename LICENSE.txt to LICENSE
5517793 Add license and local settings

For LICENSE there is a single commit, because the file was born under that name in that commit. --follow follows the rename we did in the previous post and goes back to the LICENSE.txt days too. The -- marker tells Git "what follows is a file path"; it avoids confusion when a branch and a file have the same name.

Searching Inside the Code: -S and -G

--grep searches messages. To search the code itself there is -S, known as the pickaxe. Let's look at the opening question:

$ git log --oneline -S Neuromancer
29f27c8 Add README and book list
$ git log --oneline -G Neuromancer
8b5e66c (HEAD -> main) Lower the price of Neuromancer
29f27c8 Add README and book list
$ git log --oneline -S applyDiscount
db44b06 Document discounts in README
f1f6068 Add discount support to cart

-S Neuromancer found only the first commit; Alan's commit is not there. The reason is that -S looks for commits in which the number of occurrences of that string in the files changes. Both before and after Alan's commit, "Neuromancer" appears exactly once; the line changed, but the count did not. The real question -S answers is "when was this text added or removed?". -G, on the other hand, shows a commit if one of the lines added or removed in its diff matches the regular expression you give, which is why it found Alan's commit too.

The third example is instructive as well. We were looking for when the applyDiscount function was added, and two commits came back: the one that added the function and the one that put the applyDiscount(100, 15) example into the README. -S searches all files. If you only want to look at the code, narrow the search by adding -- src/ at the end.

git show: A Close Look at One Commit

git log lists a range; git show opens a single thing:

$ git show b9d0ac5
commit b9d0ac5b7070886495623546f55a902b66c8b3af
Author: Alan Turing <alan@example.com>
Date:   Tue Nov 10 10:00:00 2026 +0300

    Fix rounding in cartTotal
    
    0.1 + 0.2 style float errors showed up as 22.490000000000002 in the UI.

diff --git a/src/cart.js b/src/cart.js
index d9a0b0f..c723fcd 100644
--- a/src/cart.js
+++ b/src/cart.js
@@ -6,7 +6,8 @@ export function addToCart(cart, bookId) {
 }
 
 export function cartTotal(cart) {
-  return cart.reduce((sum, book) => sum + book.price, 0);
+  const total = cart.reduce((sum, book) => sum + book.price, 0);
+  return Math.round(total * 100) / 100;
 }
 
 export function applyDiscount(total, percent) {

The commit's details, its message and its diff, all together. The @@ -6,7 +6,8 @@ line ending in export function addToCart is Git giving you a hint about which function the change is near. git show understands the options of git log as well, and it can show not only commits but also a file as it was in a given commit:

$ git show --stat HEAD~1
commit db44b0610fb6e017a6e6d6760543cf7bd43081b2
Author: Ada Lovelace <ada@example.com>
Date:   Wed Nov 11 10:00:00 2026 +0300

    Document discounts in README

 README.md | 2 ++
 1 file changed, 2 insertions(+)
$ git show HEAD~3:src/books.js
export const books = [
  { id: 1, title: "Dune", price: 12.5 },
  { id: 2, title: "Neuromancer", price: 9.99 },
  { id: 3, title: "The Hobbit", price: 11 },
];

HEAD~3:src/books.js means "src/books.js in the snapshot three commits before HEAD". There we see Neuromancer's old price, 9.99. To look at an old version of a file you don't need to undo anything; you read it without touching the working tree.

git blame: Who Wrote This Line, and When?

git blame writes, next to every line of a file, the commit that last changed that line:

$ git blame src/cart.js
fae44540 (Ada Lovelace 2026-11-07 10:01:00 +0300  1) import { books } from "./books.js";
fae44540 (Ada Lovelace 2026-11-07 10:01:00 +0300  2) 
fae44540 (Ada Lovelace 2026-11-07 10:01:00 +0300  3) export function addToCart(cart, bookId) {
fae44540 (Ada Lovelace 2026-11-07 10:01:00 +0300  4)   const book = books.find((b) => b.id === bookId);
fae44540 (Ada Lovelace 2026-11-07 10:01:00 +0300  5)   return book ? [...cart, book] : cart;
fae44540 (Ada Lovelace 2026-11-07 10:01:00 +0300  6) }
e3ef4186 (Ada Lovelace 2026-11-07 10:02:00 +0300  7) 
e3ef4186 (Ada Lovelace 2026-11-07 10:02:00 +0300  8) export function cartTotal(cart) {
b9d0ac5b (Alan Turing  2026-11-10 10:00:00 +0300  9)   const total = cart.reduce((sum, book) => sum + book.price, 0);
b9d0ac5b (Alan Turing  2026-11-10 10:00:00 +0300 10)   return Math.round(total * 100) / 100;
e3ef4186 (Ada Lovelace 2026-11-07 10:02:00 +0300 11) }
f1f60685 (Alan Turing  2026-11-09 10:00:00 +0300 12) 
f1f60685 (Alan Turing  2026-11-09 10:00:00 +0300 13) export function applyDiscount(total, percent) {
f1f60685 (Alan Turing  2026-11-09 10:00:00 +0300 14)   return total - (total * percent) / 100;
f1f60685 (Alan Turing  2026-11-09 10:00:00 +0300 15) }

Each line shows the commit's short hash, the author, the date and the line number. You can see three different periods of the file: Ada's original cart code, the cartTotal Ada added, and Alan's rounding fix and discount function. Lines 9 and 10 are Alan's, but lines 8 and 11 of the same function still come from Ada's commit. In big files you narrow the range with -L:

$ git blame -L 8,11 src/cart.js
e3ef4186 (Ada Lovelace 2026-11-07 10:02:00 +0300  8) export function cartTotal(cart) {
b9d0ac5b (Alan Turing  2026-11-10 10:00:00 +0300  9)   const total = cart.reduce((sum, book) => sum + book.price, 0);
b9d0ac5b (Alan Turing  2026-11-10 10:00:00 +0300 10)   return Math.round(total * 100) / 100;
e3ef4186 (Ada Lovelace 2026-11-07 10:02:00 +0300 11) }
$ git blame -L '/function applyDiscount/,+3' src/cart.js
f1f60685 (Alan Turing 2026-11-09 10:00:00 +0300 13) export function applyDiscount(total, percent) {
f1f60685 (Alan Turing 2026-11-09 10:00:00 +0300 14)   return total - (total * percent) / 100;
f1f60685 (Alan Turing 2026-11-09 10:00:00 +0300 15) }
$ git blame -s -L 1,3 src/books.js
^29f27c8 1) export const books = [
^29f27c8 2)   { id: 1, title: "Dune", price: 12.5 },
8b5e66ca 3)   { id: 2, title: "Neuromancer", price: 8.99 },

-L takes line numbers, but it can also take a regular expression: '/function applyDiscount/,+3' means "three lines starting at the line where this text appears", so it keeps working when lines are added to the file and the numbers shift. -s hides the author and date and leaves only the hash. The ^ in front of the hash says that the line has not changed since the repo's first commit.

The name sounds a bit accusing, but git blame is not for blaming someone; it is for finding context. The typical flow goes like this: you see an odd line, find its commit with git blame, then read that commit's message and its other changes with git show. Why line 9 is written the way it is gets explained by the "22.490000000000002" in b9d0ac5's message. This is the moment when writing good commit messages pays off the most.

Aliases

Some of the commands in this post are long. You can give the ones you use often a short name:

$ git config set --global alias.lg "log --oneline --graph --decorate --all"
$ git lg -3
* 8b5e66c (HEAD -> main) Lower the price of Neuromancer
* db44b06 Document discounts in README
* b9d0ac5 Fix rounding in cartTotal
# The classic form does the same job
$ git config --global alias.st "status -s"
# After adding a line to README.md
$ git st
 M README.md

git lg now means git log --oneline --graph --decorate --all; options you write after it, such as -3, are appended to the end of the alias. Like other settings, aliases live in the [alias] section of ~/.gitconfig. There is no difference at all between alias.lg, written with the new form, and alias.st, written with the old one. To list the aliases you have defined:

$ git config get --all --show-names --regexp "^alias\."
alias.lg log --oneline --graph --decorate --all
alias.st status -s

Don't overdo aliases. Your aliases do not exist on someone else's machine or on a CI server, so keep knowing the long form of the commands too.

Common Pitfalls

The first pitfall is the opening question: treating -S as a text search. -S finds commits in which the number of occurrences changes, not commits in which the content of a line changes. If your question is "who touched this line?", -G or git blame is the better tool.

The second is giving --since a date without a time. Git fills in the missing time from the moment you run the command. If the day boundary matters, write the time too: --since="2026-11-10 00:00".

The third is being surprised that, after a rename, git log -- file shows only part of the history. Add --follow to get the full history of a renamed file.

The fourth is taking the commit git blame shows as the commit where the line was "written". Blame shows the commit that last changed the line. If someone fixed the indentation of a file, every line shows up under their name; in cases like that, -w ignores whitespace changes.

A Small Experiment

★☆☆ In your own repo, compare the output of git log --oneline, git log --stat -2 and git log -p -1. Then write an --author filter that shows only your commits.

★★☆ Change the price of a book in src/books.js and commit. Search for that book's title first with -S, then with -G, and explain which one finds your commit.

★★★ Define a git hist alias for yourself: it should use --graph with a --format that shows the short hash, the author date (--date=short), the author name and the summary line. Then pick a line in src/cart.js with git blame, find its commit and read why that commit was made with git show.

In this post we looked at past changes. In the next post we go deeper into comparing changes: the differences between the working tree, the staging area and commits with git diff, --staged, the difference between A..B and A...B for two commits, --stat, --word-diff, --name-only and --merge-base.