---
name: git-log-history-extraction
source: https://app.decimal.ai/s/git-log-history-extraction@1/SKILL.md
source_sha256: 6e47a71c5cc3
---

# Git log / shortlog history extraction

## Contract

Enforces Git's real, documented pretty-format placeholders and `git log` /
`git shortlog` flags whenever you write a command to pull commit history into a
report, script, or table. Apply whenever the task is "give me a command that
lists / counts / summarizes commits" — even when no placeholder is named.

## Rules

### 1. Author vs committer — the sharpest distinction

Every person/date placeholder comes in two families. `%a…` = **author** (who
wrote the change, and when it was originally written). `%c…` = **committer**
(who applied it, and when it landed). A rebase, cherry-pick, or amend keeps the
author but rewrites the committer. When the task says "who wrote it" / "when it
was written," use the `%a…` form; use `%c…` only when the task is explicitly
about when it was committed/merged.

### 2. Pretty-format placeholders (used inside `--format=` / `--pretty=format:`)

| token | expands to |
|---|---|
| `%H` | full 40-char commit hash |
| `%h` | abbreviated commit hash |
| `%an` | author name |
| `%aN` | author name, respecting `.mailmap` (folds duplicate identities) |
| `%ae` | author email |
| `%aE` | author email, respecting `.mailmap` |
| `%s` | subject (first line of the message) |
| `%b` | body (everything after the subject and the blank line) |
| `%B` | raw body (unwrapped subject + body together) |
| `%ad` | author date, rendered per `--date=` |
| `%ai` | author date, ISO-8601-**like**: `2026-07-01 11:00:04 -0700` (a space) |
| `%aI` | author date, **strict** ISO 8601: `2026-07-01T11:00:04-07:00` (a `T`) |
| `%at` | author date as a UNIX timestamp (seconds) |
| `%cn` / `%cN` | committer name (`N` respects `.mailmap`) |
| `%cd` / `%ci` / `%cI` / `%ct` | committer date (same suffix scheme as author) |
| `%d` / `%D` | ref names — `%d` wrapped in ` (…)`, `%D` bare |
| `%n` | a literal newline inside the format string |
| `%%` | a literal percent sign |

Choose your own literal delimiter between placeholders (a `|`, a tab, `%x09`).
Use `--format=` (alias `--pretty=format:`); a bare `--pretty=oneline` will not
let you pick fields.

### 3. Range / filter flags

- `--since=<date>` (alias `--after=<date>`) — commits **more recent** than the date.
- `--until=<date>` (alias `--before=<date>`) — commits **older** than the date.
- `--author=<pattern>` — keep only commits whose author matches (regex on name+email).
- `--no-merges` — drop merge commits (equivalent to `--max-parents=1`).
- `--all` — walk every ref, not just the current branch.

### 4. Stat / file flags (pick by output shape)

- `--shortstat` — ONLY the summary line: `N files changed, N insertions(+), N deletions(-)`.
- `--numstat` — machine-readable per file: `added<TAB>deleted<TAB>path`; a binary file shows `-<TAB>-`.
- `--name-only` — just the changed file paths, one per line.
- `--name-status` — a status letter (`A`/`M`/`D`/`R`) then the path.
- `--stat` is the human diffstat with a `+/-` bar graph — do NOT use it when you
  need parseable numbers; `--numstat` or `--shortstat` is what a script wants.

### 5. Date rendering (`--date=`)

`%ad` / `%cd` render through `--date=`: `--date=short` (`YYYY-MM-DD`),
`--date=iso`, `--date=iso-strict`, `--date=relative`, `--date=unix`,
`--date=local`, or `--date=format:<strftime>` for a custom string, e.g.
`--date=format:"%Y-%m-%d"`. For a fixed shape prefer the direct placeholder
(`%aI`, `%at`) over `%ad` + a `--date=` mode.

### 6. `git shortlog` — per-author tallies

`git shortlog` groups commits by author. `-s` (summary) suppresses the
descriptions and prints just a count per author; `-n` sorts by that count
(descending) instead of alphabetically; `-e` shows the email too. The common
form is `git shortlog -sn`. Do NOT hand-roll `git log --format="%an" | sort |
uniq -c | sort -rn` — `git shortlog -sn` is the built-in, and it also honors
`.mailmap`.

## Worked examples

Each shows the base model's wrong default (BEFORE) and the conforming form (AFTER).

**Full hash + author name + subject, one line each**
- BEFORE: `git log --pretty="%h - %cn: %s"`  (abbrev hash, committer name)
- AFTER: `git log --format="%H|%aN|%s"`

**Author date in strict ISO 8601 (with a `T`)**
- BEFORE: `git log --format="%ad" --date=iso`  (space-separated, ISO-like)
- AFTER: `git log --format="%aI"`

**Author date as a UNIX timestamp**
- BEFORE: `git log --format="%ad" --date=unix`
- AFTER: `git log --format="%at"`

**Per-file added/deleted counts a script can parse**
- BEFORE: `git log --stat`  (graph bars, not parseable)
- AFTER: `git log --numstat --format="%H"`

**Only the files-changed / insertions / deletions totals**
- BEFORE: `git log --stat | tail -1`
- AFTER: `git log --shortstat --format=""`

**Count commits per contributor, most first**
- BEFORE: `git log --format="%an" | sort | uniq -c | sort -rn`
- AFTER: `git shortlog -sn`

**Just the changed file paths**
- BEFORE: `git log --stat --format=""`
- AFTER: `git log --name-only --format=""`

**Exclude merge commits from a range**
- BEFORE: `git log --format="%H %s"`  (merges included)
- AFTER: `git log --no-merges --format="%H %s"`

**Only one person's commits**
- BEFORE: `git log --format="%an %s" | grep "Ada"`
- AFTER: `git log --author="Ada" --format="%s"`

## Edge cases & exceptions

- **"When the change was made" is ambiguous** — default to the AUTHOR date
  (`%ad`/`%ai`/`%aI`/`%at`); it is when the work was written. Use committer
  (`%cd`) only when the task is about when it merged/landed.
- **De-duplicating a person who commits under two emails** → use the mailmap
  forms `%aN` / `%aE` (and `git shortlog`, which folds mailmap identities).
- **A literal `%` in the output** → escape it as `%%`; a literal newline inside
  one record → `%n`, not a real line break in the shell string.
- **Binary files under `--numstat`** show `-` for both counts, not `0` — a parser
  must treat `-` as "not countable," not as zero.
- **`--since`/`--until` take flexible dates** — an absolute `2026-03-11`, an ISO
  timestamp, or relative text like `"2 weeks ago"`; all are valid.
- **`--shortstat` still prints the commit header** unless you pass `--format=""`
  to blank the per-commit line; combine them for a totals-only stream.

## Do / Don't

- DON'T use `%cn` / `%cd` for "who wrote it / when written." DO use the author
  forms `%an` (`%aN`) / `%ad` (`%aI` / `%at`).
- DON'T emit `%h` when a full hash is asked for. DO use `%H`.
- DON'T render a strict-ISO date with `--date=iso` (that is `%ai`). DO use `%aI`.
- DON'T reach for `--stat` when you need parseable numbers. DO use `--numstat`
  (per file) or `--shortstat` (totals).
- DON'T build a `git log | sort | uniq -c` author tally. DO use `git shortlog -sn`.
- DON'T grep the log to filter by person. DO use `--author=<pattern>`.
- DON'T leave merges in a "what shipped" list. DO add `--no-merges`.

## Common mistakes (the base's wrong defaults)

- Using committer placeholders (`%cn`, `%cd`, `%ci`) when the author is meant.
- `%h` (abbreviated) where a full `%H` was requested.
- `--date=iso` (yields the space-separated `%ai` shape) when strict ISO 8601 with
  a `T` (`%aI`) was asked for.
- `%ad` plus `--date=unix` instead of the direct `%at`.
- `--stat` (a human bar-graph diffstat) where `--numstat` or `--shortstat` is needed.
- A hand-rolled `git log --format="%an" | sort | uniq -c | sort -rn` instead of
  `git shortlog -sn`.
- Forgetting `--no-merges` on a "what changed" listing, so merge commits pollute it.

## Quick checklist

- [ ] Author (`%a…`) vs committer (`%c…`) chosen deliberately.
- [ ] `%H` for full hash, `%h` only when abbreviated is asked.
- [ ] Date shape matches: `%aI` strict-ISO, `%ai` ISO-like, `%at` UNIX, or `%ad`+`--date=`.
- [ ] `%aN`/`%aE` when mailmap de-duplication matters.
- [ ] Parseable stats via `--numstat` / `--shortstat`, never `--stat`.
- [ ] Per-author counts via `git shortlog -sn`, not a shell pipeline.
- [ ] `--author=`, `--since=`/`--until=`, `--no-merges` applied as the task needs.
