git log: where am I coming from, where can I go?
By Delicious Insights • Published on Jun 18, 2019

Last updated on January 31, 2023, 04:04pm

This post is also available in French.

Git’s log is a great tool to analyze a project’s history and dig for context. It allows us to follow a project both from a 10-000-feet view and in detail: features, patches, files and directories, authors, dates...

Although many graphical interfaces offer this type of analysis, none of them leverage the full potential of that Git command. This article will therefore focus on using git log from the command line.

Still, before going into the nitty-gritty, let's focus on use cases.

The log: what for?

  1. Display the project’s history to better grasp the context
    1. limit the depth/number of displayed commits
    2. narrow scope to specific files and directories
    3. view the changes introduced by each commit
  2. See the recent history of the branches
    1. target a specific branch
    2. target branches by pattern (feat/*)
  3. Check completed tasks
    1. view merged branches
    2. list commits between 2 versions
  4. Search
    1. in commit metadata: dates, authors, messages…
    2. in commit changes:
      1. additions and deletions of searched text
      2. changes around a known text (on the same line)
    3. in a specific file, the evolution of a fragment (typically a function/method body)
  5. Get stats for the projet

This is by no means a comprehensive list, and there are many display and filtering rules that we will not dive into here.

Note that most of the options described can be combined.

Configuration: setup an optimal default behavior

Before we dive into this Git command, we need to configure two important things:

  1. Tracking file renames by default through history.
  2. An alias for a “graphical” log, essential for a pleasant use in the terminal.

History across renames

You will sometimes need to track a file’s history regardless of any renames or moves it may have undergone. This can be achieved with the git log --follow option, but you may as well configure it once and forget about it:

git config --global log.follow true

A pleasant, handy display: git lg

The default display of the log is rather confusing and lack many useful bits. This is an adoption barrier to using Git from the command line.

Let’s define a git lg alias that we’ll use most of the time instead of the standard git log. It displays a graph view with a concise set of data, for easier history browsing:

git config --global alias.lg "log --graph --date=relative --pretty=tformat:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%an %ad)%Creset'"

Here’s the result! 🤩

Graph-based log with mono-line commits and useful data (author, date, branches and tags, HEAD)

Alternative without an alias

If you prefer redefining the default display behavior of the log, you can set the following configuration keys/values:

git config --global format.pretty 'tformat:%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%an %ad)%Creset'
git config --global relative

However, this will not spare you from specifying the `--graph' option. That's why I think the alias is still the better way to go.

Now that we have a clean, beautiful log we can tackle the useful aspects of the command.

Display the project’s history to better grasp the context

The default log behavior displays the history of the current reference since its very first commit. We’ll often want to restrict that to the latest X commits. Here is an example that shows only the 10 latest commits from the current position of HEAD:

git lg -10 # or `-n 10` or `--max-count=10`

It is common to only want commits that touch specific files or directories:

git lg my-file my-directory another-directory

It even works with globs 🤘:

git lg *.html *.js

You can view the actual changes in each listed commit:

git log -p # or `--patch`

That display may feel cumbersome because it provides surrounding file context for each change. Fortunately, the log command shares display options with its diff cousin. For instance, you can:

  • display only the names of the files: --name-only (--name-status to see whether it was an addition/modification/deletion).
  • display changes on the same line (rather than two lines) with --word-diff or --word-diff-regex=… options.
  • filter on modifications (M), additions (A), deletions (D)… Example: --diff-filter=AD.
  • tone down context lines by adding the --unified=0 option (0 here is the number of context lines, so none).

Protip for displaying patches/diffs: install diff-so-fancy. It provides a concise, convenient display.

What about branches?

The default log behavior only displays the history of the current reference (HEAD). It is naturally possible to target specific branches by using their names:

git lg my-branch [other-branch] [and-another-branch]

However, this syntax doesn't allow for globbing. To display a whole category of branches we can use any of the following:

  • the --branches option to display local branches ;

Local branches log

  • the --all option to display local and remote branches, tags, stashes and in general any known named reference of the repository.

Local and remote branches, tags, stashes

We can go further with the first option and choose to display the branches whose names match the desired pattern: git lg --branches='*demo*'.

Local branches filtered by a name pattern

Check completed tasks

View merged branches

If you want to verify that you merged some branches, evenafter removing their labels, you just have to do this:

git lg --merges

Show merge commits only

We can see in this example that the branches feat/first-demo and feat/second-demo were merged in 🤝.

Walk a specific interval of commits

Sometimes you need to display commits between two specific points in time. Here are some concrete examples.

Say we want to display the commits on a dev branch since its sprouting off master, to know the current state of its work:

git lg

Note: one might think that a git lg dev would have worked the same. In fact it would have also displayed all the history of master prior to the sprouting of dev, because the part of master is also part of the history of dev.

We may want the opposite of the previous situation: the new commits on master since the creation of dev. Say master has since merged a feature branch that is necessary for us to finish our work on dev: we would then know that it’s probably a good idea to update (rebase) dev to make it start off the latest master.

git lg dev..master

If you use tags to "version" your project, you will sometimes want to analyze the new features from one version to another, just like in a changelog (still, prefer a real changelog):

git lg v1.0.0..v2.0.0

View commits from versions 0.0.1 to 1.0.0

Search the log

Many filtering options are available. Not all of them are useful on a daily basis, so we’ll only see a curated selection.

Filtering on dates

We may want to search our log for work done since a given date, or up to that date.

Say I want to display the commits of my current branch for the past 24 hours; I could write:

git lg --since='1 day ago'

A team leader who would like to know the work shared by his team over the past 7 days would do:

git lg --since='1 week ago' --all

The date can be specified in plain English (e. g. yesterday, 5 minutes ago, 1 month 2 weeks 3 days 1 hour 1 second ago) or in ISO8601 format (2019-06-07 18:30:00).

This analysis can be further refined by using a bounded interval with the --until=... option.

Now if our team leader wants to see the work done over the previous week, they could use this:

git lg --since='3 weeks ago' --until='2 week ago' --all

Commits from 3 weeks ago to 2 weeks ago

Aliases are available for these options: since => after, until => before.

Filtering by commit author

Let's say one of your coworkers went on holidays without being able to tell you what tasks she has completed. You could analyze her recent history to determine what she shared/pushed:

git lg -20 --all --author='Anna'

Log filtered by author

Be careful, this option searches for authors whose names contain the given pattern. So if I have several Annas in my project, I will have to refine my search.

Similarly, we can filter on committers:

git lg -20 --all --committer='Christophe'

It is possible that the author and the committer are different, e.g. during a rebase, or when we do peer-programming and assign a commit explicitly:

git commit --author='Maxime'

There is an interesting alternative to the log that groups recent commits by author: git shortlog.

Commits grouped by author

Filtering by commit message

If you write useful commit messages (which I hope you do 😅), you may want to find some of them. Let's say you use a syntax such as GitHub's or GitLab to reference your tasks (issues). We could thus list all the commits related to task 42:

git log --branches --grep='#42'

This option accepts a regular expression that can be an extended expression when the -E option is added. It can also be told to ignore case with the -i option. Here is a more complete example:

git log --grep='(clos(e[sd]?|ing)|fix(e[sd]?|ing)?)\s*#42' -E -i

Searching commit messages

You probably noticed that I didn't use the lg alias there, but went instead with log. The reason is simple: I want to display the messages in their entirety, because my search is performed on the entire message, not just its first line. Displaying full messages allows me to view the text occurrence I'm looking for.

One more tip: if your display is paginated by a tool like less, you can type / (slash) to search in displayed text.

Searching for changes introduced by commits

The focus here is on content changes. There are two takes on this:

  • we’re looking for commits that added or modified/deleted a specific text, or
  • we’re looking for changes that occurred on the same line as a specific text.

Say I have a file my-module.js that contains a function that makes a console.log(...). This call was introduced when the module was created and several changes were made to improve the displayed message.

Now, if I'm looking for the console.log call itself, I should only hit on one commit (its creation). But if I'd like to track changes to the logged message (what occurs within that console.log(...)), I should hit on several commits.

Here is the current log of all the commits related to the file:

4 commits touched the file named `my-module.js`

Searching for additions or deletions

git lg -S 'searched text' my-module.js

This -S option takes a literal text. However, you can extend its behaviour to use a regular expression with a complementary --pickaxe-regex option (not an easy name to remember 😨).

Back in our use case, to retrieve all additions/deletions of console.log in that file, I can do:

git lg -p -S 'console.log' my-module.js

We get only one commit here: the one that created the module.

Result of our `log -S`: 1 commit

Searching for changes on lines matching a given pattern

We may want to list all the commits that introduced changes on the lines matching a given pattern.

Say I want to view changes to the message displayed by my console.log. I’ll assume my console.log stays on the same line as the message, so I can use it as a search pattern:

git lg -p -G 'console\.log' my-module.js

The result will provide us with both surrounding changes in theis line and additions/deletions of other occurrences.

Note that this -G option takes a regular expression without needing an additional option (hence the escaping of the period: \.).

Result of `log -G`: 4 commits

Tracing changes to a fragment / code block

That's my favorite option! It allows us to track the evolution of a fragment of a file, commit by commit. Imagine that you have a function and that overnight a bug appears. How would you go about it?

git lg -L 1,99:file-path

This option searches within a single file. The idea is simple: we define the interval to follow through the commits and Git will show us only changes to this block. We recommend you stay with a line-number interval syntax (comma-separated start line and end line; e.g. 3,99). Note that these are the line numbers in the most recent version of the file, and Git will automatically adjust these as it moves backwards in time through changes, according to the surrounding changes of the file (shifts due to insertions above, addition/deletion of lines in the block).

Let's take the following my-module.js file’s current version:

![It has a `logUppercased' function that starts at line 5 and ends at line 11](/assets/images/articles/git-log-L-file.png)

We want to track changes to the logUppercased function, so between lines 5 and 11, because a bug appeared (the function no longer displays in upper case) and we want to trace the origin of the problem:

git lg -L 5,11:my-module.js

Here comes magic ✨…

Result of the log: traceing block changes through the commit history

The result shows us that the commit 58462c4 test(log-l): add test for 'logUppercased' replaced the line doing the display with another that does not convert to upper case 😱. We can conclude that this is a mistake and not an intentional choice, so we can fix it!

A word about git blame

The blame command is often used for, well… blaming purposes when an error occurs in a project. Keep in mind that this command only displays the current status of the file, line by line. It only shows the last person to have touched each line, perhaps by removing trailing whitespace or re-indenting. It does not provide conclusive proof that the listed person actually introduced the error.

I think this command should be prohibited for two reasons:

  • it carries aggressive meaning;
  • it seldom provides truly useful information: prefer git log for a sharp analysis!

Project stats

The log features the same stats options as the diff command:

  • stat: stats by file (addition/deletion ratio);
  • shortstat: single-line overall commit stats;
  • dirstat: % changed (in terms of code line counts), per directory;
  • numstat: equivalent to stat but with a raw tab-separated output, ideal for scripting the result or exporting it to a spreadsheet.

Note that git diff will produce consolidated stats whilst git log will display stats on a per-commit basis. It is therefore very likely that you will lean more towards git diff for analysis.

Git services (GitHub, GitLab etc.) also provide advanced graphical stats that go beyond what can be obtained from the command line.

Final word

The Git log and status commands are the two commands I use most to analyze my current and past work. The log is a valuable way to remind me of context (assuming of course that my commit messages are meaningful).

There are many options not listed here, some of which are also diff and reflog options. I ecnourage you to remain curious and follow any updates to this article.

Want to know more and become a true Git master? Check out our kickass 360° Git training!

Check out our video courses! 🖥

Our screencasts are the ideal, affordable complement to our tech articles and in-room training courses, tackling Git, JavaScript and other topics. Check out these high-quality, affordable courses that are specially crafted to take on your biggest pain points and roadblocks.