1. Introduction

Git offers many ways to explore a repository’s current and past states. This enables tracking versions and changes, promoting a better understanding of the development and creativity process. One of the most used subcommands for Git history exploration is log. However, reflog can sometimes be even more useful.

In this tutorial, we explore how to show and customize the Git log for specific branches. First, we briefly refresh our knowledge of the Git structure and workflow. After that, we turn to the log and reflog. Next, we check both for all branches. Then, we see how we can check the log and reflog of a specific branch. Finally, we see an alternative way to analyze the commits that are only part of a single branch.

We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15 and Git 2.39.2. Unless otherwise specified, it should work in most POSIX-compliant environments.

2. Git Structure and Workflow

Git has three trees:

When using Git, the workflow usually consists of three sequential steps, with several ways to backtrack:

  1. make changes to the working tree
  2. submit some or all changes to the staging tree
  3. commit the current staging tree snapshot to the root commit tree

The commit tree is the main structure that holds data in a Git repository. Of course, we can switch to different parts of that tree by using checkout, rebase, and other subcommands. This action usually means a change of HEAD and leads to a working directory content switch according to the target commit.

Perhaps the most common reason for a HEAD movement is a branch checkout. Yet, we’re not always interested in a single branch.

3. Log and Reflog

All actions within a Git repository are monitored. However, only some are recorded in the public log, while others end up in the so-called reflog.

The main difference between these two entities is the fact that the log is available to everybody with access to the repository, while the reflog differs according to the user and location.

Still, another way that reflog goes beyond the public log is content.

Let’s see an example of the basic log subcommand output:

$ git log
commit a89d48bea4444f80e2afc95c9c0ed2d703a2ebcd (HEAD -> master)
Author: x <[email protected]>
Date:   Wed Mar 13 09:19:10 2024 -0400

    new structure

commit 73fa11ebded1be52f00a4f4596000195db4e2d63
Author: x <[email protected]>
Date:   Wed Mar 13 08:08:16 2024 -0400

    wipeout

commit 6332bd9924b69881d268c6fb58140d8bdcb62654
Author: x <[email protected]>
Date:   Wed Mar 13 07:00:15 2024 -0400

    major modifications

commit e392676bda1e401ac719465d1de1928ca2e8423a
Author: x <[email protected]>
Date:   Wed Mar 13 06:13:00 2024 -0400

    minor modifications

commit 2ccb08fcc43e83223992eaa5af85033940a2a548
Author: x <[email protected]>
Date:   Wed Mar 13 06:00:13 2024 -0400

    restructuring

commit e5b2bf231c79e49a6f499e0f151081fed9cb0ef8
Author: x <[email protected]>
Date:   Wed Mar 13 05:11:00 2024 -0400

    structuring

commit 850845b4eddf36057d6567f8df44d4815fa44e88
Author: x <[email protected]>
Date:   Wed Mar 13 04:09:00 2024 -0400

    init commit

In essence, the log is a list of all commits and related metadata in the commit tree.

Now, we can compare this with the output of reflog, which implies reflog show:

$ git reflog
a89d48b (HEAD -> master) HEAD@{0}: commit: new structure
73fa11e HEAD@{1}: commit: wipeout
6332bd9 HEAD@{2}: commit: major modifications
e392676 HEAD@{3}: commit: minor modifications
2ccb08f HEAD@{4}: checkout: moving from branch1 to master
04ef448 (tag: v0.1, branch1) HEAD@{5}: commit: branch feature
37ec911 HEAD@{6}: checkout: moving from branch2 to branch1
3236f06 (branch2) HEAD@{7}: commit: secondary feature WIP
c39bec1 HEAD@{8}: commit: secondary branch modifications
37ec911 HEAD@{9}: checkout: moving from branch3 to branch2
a583e9d (branch3) HEAD@{10}: commit: experimental
37ec911 HEAD@{11}: checkout: moving from branch2 to branch3
37ec911 HEAD@{12}: checkout: moving from branch1 to branch2
37ec911 HEAD@{13}: commit: branch modifications
2ccb08f HEAD@{14}: checkout: moving from master to branch1
2ccb08f HEAD@{15}: commit: restructuring
e5b2bf2 HEAD@{16}: commit: structuring
850845b HEAD@{17}: commit (initial): init commit

Here, the default format is more condensed, but reflog actually accepts any formatting flags and options that log offers. However, we see many more activities in the reflog, not only the commits.

In summary, anything that creates an object in Git leaves a trace in the reflog, while the log only lists commits.

4. Show (Ref) Log Data for All Branches

By default, both the log and reflog subcommands only output data for the currently checked-out branch and HEAD. In the previous examples, we saw the log and reflog entries related to the primary master branch.

On the other hand, we can get data on all branches with the –all flag in either command.

Let’s start with log:

$ diff --unchanged-line-format= --old-line-format= --new-line-format='%L' <(git log
) <(git log --all)
commit 04ef448b671b43cfae7688adcee5db1fdc0395b6
Author: x <[email protected]>
Date:   Wed Mar 13 12:15:00 2024 -0400

    branch feature

commit 3236f068a5d574c71aac0d6ac1087a1655f9fecd
Author: x <[email protected]>
Date:   Wed Mar 13 11:11:02 2024 -0400

    secondary feature WIP

commit a583e9d242cbcb615031fb7d5a71fcbc3a4d9b85
Author: x <[email protected]>
Date:   Wed Mar 13 10:19:16 2024 -0400

    experimental

commit 37ec9115b08c6df4b5d1cef1d86aa2e7fc0df52b
    branch modifications
commit c39bec19ea75875b89344a02ead2a7f2f17536c3
    secondary branch modifications
commit 73fa11ebded1be52f00a4f4596000195db4e2d63
    wipeout
commit 6332bd9924b69881d268c6fb58140d8bdcb62654
Author: x <[email protected]>
Date:   Wed Mar 13 07:00:15 2024 -0400

    major modifications


commit e392676bda1e401ac719465d1de1928ca2e8423a
Author: x <[email protected]>
Date:   Wed Mar 13 06:13:00 2024 -0400

    minor modifications

To get only the additional lines of log with –all as compared to log alone, we use diff with command substitution and some additional formatting options:

  • –unchanged-line-format= leaves the unchanged lines hidden, i.e., no output
  • –old-line-format= leaves the output of log without –all hidden, i.e., no output
  • –new-line-format=’%L’ shows the additional lines of log with –all as only the [%L]ine contents

Let’s do the same for reflog:

$ diff --unchanged-line-format= --old-line-format= --new-line-format='%L' <(git reflog
) <(git reflog --all)
04ef448 refs/heads/branch1@{0}: commit: branch feature
37ec911 refs/heads/branch1@{1}: commit: branch modifications
2ccb08f refs/heads/branch1@{2}: branch: Created from HEAD
3236f06 refs/heads/branch2@{0}: commit: secondary feature WIP
c39bec1 refs/heads/branch2@{1}: commit: secondary branch modifications
37ec911 refs/heads/branch2@{2}: branch: Created from HEAD
a583e9d refs/heads/branch3@{0}: commit: experimental
37ec911 refs/heads/branch3@{1}: branch: Created from HEAD
a89d48b refs/heads/master@{0}: commit: new structure
73fa11e refs/heads/master@{1}: commit: wipeout
6332bd9 refs/heads/master@{2}: commit: major modifications
e392676 refs/heads/master@{3}: commit: minor modifications
2ccb08f refs/heads/master@{4}: commit: restructuring
e5b2bf2 refs/heads/master@{5}: commit: structuring
850845b refs/heads/master@{6}: commit (initial): init commit

Here, we see the additional lines of output from reflog with –all as compared to reflog without any flags while master is checked out. Notably, some lines repeat, but with a different ref format, i.e., the full path.

Essentially, this output follows the whole history of the repository from the local point of view. As already explained, this data isn’t public, except for the commits. Part of the reason behind this is its verbosity and context. In fact, verbosity and context are also the reasons for both log and reflog only working on the currently checked-out branch and HEAD.

5. Show (Ref) Log Data for Single Branch

If we don’t want to only see the (ref) log of a specific branch that’s not the current one, both log and reflog offer a way to do so.

Essentially, we only need to append the respective branch name to the command.

First, let’s see an example with log:

$ git log branch3
commit a583e9d242cbcb615031fb7d5a71fcbc3a4d9b85 (branch3)
Author: x <[email protected]>
Date:   Wed Mar 13 10:19:16 2024 -0400

    experimental

commit 37ec9115b08c6df4b5d1cef1d86aa2e7fc0df52b
Author: x <[email protected]>
Date:   Wed Mar 13 09:01:10 2024 -0400

    branch modifications

commit 2ccb08fcc43e83223992eaa5af85033940a2a548
Author: x <[email protected]>
Date:   Wed Mar 13 06:00:13 2024 -0400

    restructuring

commit e5b2bf231c79e49a6f499e0f151081fed9cb0ef8
Author: x <[email protected]>
Date:   Wed Mar 13 05:11:00 2024 -0400

    structuring

commit 850845b4eddf36057d6567f8df44d4815fa44e88
Author: x <[email protected]>
Date:   Wed Mar 13 04:09:00 2024 -0400

    init commit

With log, we only see commits that are part of the branch3 stem and branch.

Now, let’s do the same with reflog and branch3:

$ git reflog branch3
a583e9d (branch3) branch3@{0}: commit: experimental
37ec911 branch3@{1}: branch: Created from HEAD

Notably, reflog* only shows activity related to *branch3.

This includes the fork commit and the single branch3 commit, without the stem commits that are part of master.

6. Show Non-master Commits

Another way to show non-master commits is the cherry subcommand. It accepts two commits or ranges and searches for commits that aren’t part of the first range but are part of the second. By default, HEAD is the default for the second range.

To do so, we first check out the branch of interest, so HEAD is its latest commit:

$ git checkout branch1

After that, we run cherry for master and include the commit subjects (-v):

$ git cherry -v master
+ 37ec9115b08c6df4b5d1cef1d86aa2e7fc0df52b branch modifications
+ 04ef448b671b43cfae7688adcee5db1fdc0395b6 branch feature

This way, we extract only the commits that are part of branch1 but not those it shares with master.

7. Summary

In this article, we learned ways to explore logs and commit data for one or more branches.

In conclusion, Git is versatile when it comes to filtering by branches and commits, especially when it comes to log analysis.