Search the commit history of every git repository under a directory.
Built for workspaces made of many independent repositories, where "which
repos did that ticket touch?" means running git log --grep in each one.
$ logr PROJ-142 ~/work
api
3f9c2a1 2026-09-23 PROJ-142: validate redirect URL (#212) (Ann Lee, main, #212)
b71e0d4 2026-09-22 PROJ-142: add failing test (Ann Lee, origin/fix/PROJ-142)
web
c02a9f7 2026-09-24 Merge branch 'fix/PROJ-142' into 'main' (Bob Stone, origin/main, !88)
5d1e6b2 2026-09-23 PROJ-142: keep the return URL (Bob Stone, fix/PROJ-142, !88)
4 commits in 2 of 14 repositories# Homebrew
brew install kova1max/tap/logr
# npm
npm install -g @kova1/logrTo try it without installing anything:
npx @kova1/logr PROJ-142 ~/workEvery release is also mirrored to
GitHub Packages as
@kova1max/logr (GitHub requires the repo owner as scope). Installing from
there needs a GitHub token, so the npm registry above is the easier choice.
logr only reads. It will never:
- Fetch, pull or touch the network. It searches what is already on disk, so it is instant and works offline. To include your colleagues' latest commits, fetch first (for example with pullr).
- Change a repository - no checkouts, no writes to
.git, no changes to the working tree. - Enter a nested repository, unless it is a submodule and you ask for
submodules with
--submodules. - Follow symlinked directories.
logr [options] [PATTERN] [DIR]
PATTERN is literal text, matched against the whole commit message. Lowercase
text matches any case; text with a capital letter matches exactly (smart
case), so logr login finds "Login" while logr PROJ-142 does not find
"proj-142".
Every local and remote-tracking branch is searched, and each commit is listed
once, with the branch it was found on. Options can go before or after
PATTERN and DIR.
Short options can be combined: -ic is -i -c, and -m5 or -im 5 set the count.
| Option | Default | Meaning |
|---|---|---|
DIR, -C DIR |
current directory | Where to look for repositories |
-E, --regex |
Treat PATTERN as an extended regular expression |
|
-i, --ignore-case |
smart case | Always ignore case |
--case-sensitive |
smart case | Never ignore case |
-c, --code |
Search the changes instead of the messages: list the commits that add or remove PATTERN (git log -S, or -G with --regex). Slower on large histories. |
|
--author WHO |
Only commits by an author whose name or email matches WHO |
|
--since DATE |
Only commits after DATE, e.g. 2026-09-01 or 2.weeks |
|
--until DATE |
Only commits before DATE |
|
--current |
Only search the checked-out branch of each repository | |
-m, --max-count N |
Show at most N commits per repository |
|
-j, --jobs N |
8 |
Search up to N repositories in parallel. Output is printed in discovery order, so it reads the same as a sequential run. |
--max-depth N |
2 |
How many directory levels below DIR to search. 0 = only DIR itself, 1 = DIR and its direct children, ... |
-s, --submodules |
off | Also search each checked-out submodule |
--no-color |
Disable colored output. Also disabled when the NO_COLOR environment variable is set, or when output is not a terminal. |
|
-V, --version |
Print the version | |
-h, --help |
Show help |
PATTERN can be left out when searching only by --author, --since or
--until; pass '' in its place when also giving DIR.
# every commit that mentions a ticket
logr PROJ-142 ~/work
# what Ann committed in the last week
logr '' ~/work --author ann --since 1.week
# recent reverts, searching from the current directory
logr -E 'revert|rollback' --since 1.month
# the commits that added or removed a call
logr -c getUserInfo ~/work
# the latest release commit on each checked-out branch
logr -m 1 --current releaseOn a terminal, commits are grouped under each repository, newest first, as
in the example at the top. When the output is piped, each commit is one
tab-separated line - repository, commit, date, author, branch, pull request
(empty if none), subject - so it works with cut, sort and awk:
$ logr PROJ-142 ~/work | awk -F'\t' '$6 != "" { print $1, $6 }' | sort -u
api #212
web !88The summary line and any errors go to stderr, so piped output stays pure data.
Each commit shows the pull request it came in with, worked out from the history on disk - no API calls, no tokens:
- a merge or squash commit that names one:
Merge pull request #12andFix login (#12)on GitHub,See merge request group/project!12on GitLab (shown as!12),(pull request #12)on Bitbucket andMerged PR 12:on Azure DevOps; - any other commit gets the pull request of the merge that brought it into a branch - the oldest one that names a pull request, if it was merged more than once.
A commit that never came in through a pull request - made directly on the branch it is on, or on a branch not merged yet - has none. Fetch first to see pull requests merged since your last fetch.
Exits 0 if any commit matched, 1 if none did, and 2 on invalid arguments
or if a repository could not be searched, like grep.