Skip to content

History and blame

These commands read the commit graph. None of them write to the repository. The examples use the repository from Getting started, with two branches: main, and topic, which forked from main~1 and renamed a file. Comparing snapshots is on the Diffs page. The library side is in Read objects and history.

girt log lists commits reachable from the revisions you give it, newest committer date first, starting from HEAD when you give none. It accepts ^A, A..B and A...B. -n stops after that many commits and --oneline prints one line each.

$ girt log --oneline
7ed7a0cc68995713f52d973e6adfd3d9e667324f Ignore target
3dc25d2e2eeed699cba1e1ea0314dd88299f9b02 Add a second line
e896f1ed22a211bd58c4b542fba2323ad09a52b0 Initial commit
$ girt log main..topic --oneline
90772130c5df4563a3c4c46956d4120724dfed1a Rename main to lib

The default format prints full IDs and the parent line, which makes the graph easy to follow in plain text:

$ girt log -n 1
commit 7ed7a0cc68995713f52d973e6adfd3d9e667324f
parent 3dc25d2e2eeed699cba1e1ea0314dd88299f9b02
author A. Writer <[email protected]>
date 2026-10-01 12:00:00 +0000
Ignore target

The excluded side of a range is walked in full, so A..B with a long history behind A costs a full walk of that history. Git stops earlier; this is a known gap in the library’s walk.

girt history count prints how many commits the revisions select. It takes the same ranges as log.

$ girt history count main...topic
2

Find a common ancestor (girt history merge-base)

Section titled “Find a common ancestor (girt history merge-base)”

girt history merge-base A B prints the best common ancestor of two commits, and --all prints every one when there is more than one. It exits 1 with no output when the commits share no history.

$ girt history merge-base main topic
3dc25d2e2eeed699cba1e1ea0314dd88299f9b02

Name a commit by its nearest tag (girt history describe)

Section titled “Name a commit by its nearest tag (girt history describe)”

girt history describe names a commit by the nearest earlier tag, as build tools do for version strings. v0.1-1-g7ed7a0c means one commit after v0.1, at commit 7ed7a0c; a tagged commit prints just the tag. By default only annotated tags count: --tags adds lightweight tags and --all any reference, printed as heads/main or tags/v0.1. --match and --exclude filter names by glob, --first-parent counts only first-parent steps, --long always prints the count, --abbrev=0 prints only the name and --always falls back to the abbreviated ID.

$ git tag -a v0.1 -m 'First release' main~1
$ girt history describe main
v0.1-1-g7ed7a0c

The count is exact: the commits reachable from the described commit and not from the tag. git describe estimates it while walking by commit date, so on merge-heavy history it can print a larger count, or a different tag whose estimate was smaller.

girt history contains A B asks whether A is an ancestor of B. It prints yes or no and exits 0 or 1, so it works directly in a shell if. With --git it prints nothing, as git merge-base --is-ancestor does.

$ girt history contains main~2 topic
yes
$ girt history contains topic main
no

girt blame <path> [revision] finds the commit that last changed each line of a file. Each output line is a range: the commit, the range’s first line and length in the final file, its first line in that commit, and the path it had there.

$ girt blame README.md
e896f1ed22a211bd58c4b542fba2323ad09a52b0 1 1 1 README.md
3dc25d2e2eeed699cba1e1ea0314dd88299f9b02 2 1 2 README.md

Blame follows renames, so the path column can name a file the range had before it was moved. It doesn’t detect lines copied or moved between files, and refuses files with a textconv driver. The path is a literal repository-relative path, not a pathspec, and line numbers start at 1. Its output is ranges rather than Git’s annotated lines, so girt blame --git is refused.