LibraLibra
LibraLibra
DocsBlogLibra
Getting Started
Architecture
Design
Commands
libra addlibra agentlibra alternateslibra applylibra archivelibra authlibra automationlibra bisectlibra blamelibra branchlibra bundlelibra cachelibra cat-filelibra check-attrlibra check-ignorelibra check-mailmaplibra checkoutlibra cherry-picklibra cleanlibra clonelibra cloudlibra code-controllibra codelibra commit-treelibra commitlibra completionslibra configlibra credentiallibra depslibra describelibra diff-fileslibra diff-indexlibra diff-treelibra difflibra dirtylibra fast-exportlibra fast-importlibra fetchlibra filelibra for-each-reflibra format-patchlibra fscklibra gclibra graphlibra greplibra hash-objectlibra hookslibra hydratelibra index-packlibra initlibra investigatelibra layerlibra lfslibra loglibra logfilelibra loginlibra logoutlibra ls-fileslibra ls-remotelibra ls-treelibra maintenancelibra medialibra merge-baselibra merge-filelibra mergelibra metadatalibra mvlibra noteslibra oplibra openlibra pack-objectslibra packagelibra prunelibra publishlibra pulllibra pushlibra read-treelibra rebaselibra refloglibra remotelibra repacklibra replacelibra rererelibra resetlibra restorelibra rev-listlibra rev-parselibra revertlibra reviewlibra revisionlibra rmlibra sandboxlibra servicelibra shortloglibra show-reflibra showlibra sparse-viewlibra stashlibra statslibra statuslibra switchlibra symbolic-reflibra taglibra update-indexlibra update-reflibra usagelibra verify-packlibra whoamilibra worktreelibra write-tree
API Reference
Policy
Commands

libra grep

Command reference for `libra grep`

Search for patterns in tracked files.

Synopsis

libra grep [<options>] <pattern> [-- <pathspec>...]
libra grep -e <pattern> [-e <pattern>...] [-- <pathspec>...]
libra grep -f <file> [-- <pathspec>...]

Description

libra grep searches for text patterns in tracked files. By default it searches the working tree, but it can also search the index (--cached) or a specific revision (--tree <revision>). Patterns are interpreted as regular expressions unless --fixed-string is specified.

Multiple patterns can be supplied via -e flags or read from files via -f. When multiple patterns are active, a file matches if any pattern matches at least one line (OR semantics). With --all-match, a file is included only if every pattern matches at least one line in that file (AND semantics across patterns, not across lines).

Output can be tuned with flags to show only filenames (-l, -L), match counts per file (-c), line numbers (-n), byte offsets (-b), or inverted matches (-v). The command supports pathspec filtering to restrict the search to specific files or directories.

When stdout is a terminal, output is sent through a pager. In JSON mode, structured output is emitted for programmatic consumption.

Options

FlagShortLongDescription
PatternpositionalThe pattern to search for. Required unless -e or -f is specified.
Regexp-e--regexp <PATTERN>Add a pattern to search for. Can be specified multiple times.
Pattern file-f--file <FILE>Read patterns from a file, one per line. Can be specified multiple times.
All match--all-matchRequire all patterns to match at least once in a file for that file to be included.
Fixed string-F--fixed-stringTreat the pattern as a literal string, not a regular expression.
Ignore case-i--ignore-casePerform case-insensitive matching.
Count-c--countShow only the number of matching lines for each file.
Files with matches-l--files-with-matchesShow only the names of files that contain matches.
Files without matches-L--files-without-matchesShow only the names of files that do not contain matches.
Line number-n--line-numberPrefix each matching line with its 1-based line number.
Word regexp-w--word-regexpMatch only lines where the pattern forms a complete word (surrounded by word boundaries).
Invert match-v--invert-matchSelect non-matching lines instead of matching lines.
Byte offset-b--byte-offsetShow the 0-based byte offset of the first match on each line.
Max count-m--max-count <NUM>Stop after NUM matching lines per file.
Only matching-o--only-matchingPrint only the matched parts of a line, one match per output line (context lines are suppressed).
Pathspecpositional (trailing)Restrict search to files matching the given paths.
Tree--tree <REVISION>Search in the specified revision or commit tree instead of the working tree.
Cached--cachedSearch in the index (staging area) instead of the working tree.
Untracked--untrackedIn addition to tracked files, also search untracked, non-ignored files in the working tree. Cannot be combined with --cached or --tree.
No index--no-indexSearch the filesystem directly (the given paths, or the current directory) without a repository or index. Works outside a repository, recurses every file including ignored ones (skipping .git/.libra), and shows paths relative to the current directory. Cannot be combined with --cached, --untracked, or --tree.
Max depth--max-depth <DEPTH>Descend at most DEPTH levels of directories below each pathspec. A file directly inside the pathspec directory is depth 0; a negative value means no limit. With no pathspec, depth is measured from the worktree root (not the current directory) — libra grep always searches the whole worktree with worktree-relative paths, so to limit to a subdirectory, pass it as a pathspec.
Heading--heading / --no-headingPrint each file name once as a heading above its matches instead of prefixing every line. --no-heading is the default.
Break--break / --no-breakPrint an empty line between matches from different files. --no-break is the default.
Null-z--nullOutput a NUL byte after the file name (and line number) instead of :, for machine consumption.

Option Details

Positional <pattern>

The primary search pattern. Interpreted as a regular expression by default:

$ libra grep "fn\s+execute"
src/command/merge.rs:pub async fn execute(args: MergeArgs) {
src/command/rebase.rs:pub async fn execute(args: RebaseArgs) {

-e / --regexp

Add additional patterns. When combined with --all-match, all patterns must match in a file:

# Find files containing both "TODO" and "FIXME"
$ libra grep -e "TODO" -e "FIXME" --all-match

-f / --file

Read patterns from a file, one per line:

$ libra grep -f patterns.txt

-F / --fixed-string

Treat the pattern as a literal string. Useful when searching for strings that contain regex metacharacters:

$ libra grep -F "Vec<String>"

-i / --ignore-case

Case-insensitive matching:

$ libra grep -i "error"

-c / --count

Show match counts instead of matching lines:

$ libra grep -c "TODO"
src/main.rs:2
src/lib.rs:5

-l / --files-with-matches

Show only filenames with matches:

$ libra grep -l "TODO"
src/main.rs
src/lib.rs

-L / --files-without-matches

Show only filenames without matches:

$ libra grep -L "TODO"
src/utils.rs

-n / --line-number

Show line numbers:

$ libra grep -n "TODO"
src/main.rs:42:// TODO: refactor this

-w / --word-regexp

Match whole words only:

# Matches "error" but not "errors" or "error_handler"
$ libra grep -w "error"

-v / --invert-match

Show lines that do not match:

$ libra grep -v "^$" src/main.rs

-b / --byte-offset

Show byte offsets of matches:

$ libra grep -b "TODO"
src/main.rs:1024:// TODO: refactor

--tree

Search in a specific revision:

$ libra grep --tree HEAD~3 "deprecated"
$ libra grep --tree v1.0 "config"

--cached

Search in the index instead of the working tree:

$ libra grep --cached "TODO"

Common Commands

# Search for a pattern in tracked files
libra grep "TODO"

# Case-insensitive regex search
libra grep -i "error|warning"

# Search for a literal string
libra grep -F "HashMap<String, Vec<u8>>"

# Show only filenames with matches
libra grep -l "deprecated"

# Show match counts
libra grep -c "unwrap()"

# Search with line numbers
libra grep -n "fn main"

# Search in a specific revision
libra grep --tree HEAD~5 "old_function"

# Search in the index
libra grep --cached "staged_change"

# Restrict to specific paths
libra grep "TODO" -- src/command/

# Multiple patterns (OR)
libra grep -e "TODO" -e "FIXME" -e "HACK"

# Multiple patterns (AND across file)
libra grep -e "use.*serde" -e "Serialize" --all-match

Human Output

Default output (file:line format):

src/main.rs:// TODO: refactor this function
src/lib.rs:// TODO: add error handling

With line numbers (-n):

src/main.rs:42:// TODO: refactor this function
src/lib.rs:15:// TODO: add error handling

With byte offset (-b):

src/main.rs:1024:// TODO: refactor this function

Count mode (-c):

src/main.rs:1
src/lib.rs:3

Files-with-matches mode (-l):

src/main.rs
src/lib.rs

Files-without-matches mode (-L):

src/utils.rs
src/config.rs

Tree search (--tree):

HEAD~3:src/main.rs:// TODO: old code

Structured Output (JSON)

{
  "pattern": "TODO",
  "patterns": ["TODO"],
  "context": "working-tree",
  "total_matches": 5,
  "total_files": 2,
  "matches": [
    {
      "path": "src/main.rs",
      "line_number": 42,
      "line": "// TODO: refactor this function",
      "byte_offset": null
    }
  ],
  "counts": null,
  "files_with_matches": null,
  "files_without_matches": null,
  "warnings": []
}

When using --count:

{
  "pattern": "TODO",
  "patterns": ["TODO"],
  "context": "working-tree",
  "total_matches": 5,
  "total_files": 2,
  "matches": null,
  "counts": [
    { "path": "src/main.rs", "count": 2 },
    { "path": "src/lib.rs", "count": 3 }
  ],
  "warnings": []
}

When using -l:

{
  "pattern": "TODO",
  "patterns": ["TODO"],
  "context": "working-tree",
  "total_matches": 5,
  "total_files": 2,
  "matches": null,
  "files_with_matches": ["src/main.rs", "src/lib.rs"],
  "warnings": []
}

The warnings array contains entries for files that could not be read or were skipped (e.g., binary files):

{
  "path": "assets/image.png",
  "message": "binary file, skipping"
}

Design Rationale

Why built-in instead of relying on external grep/ripgrep?

External tools like grep, rg, or ag are excellent for searching files on disk, but they have no awareness of version control state. They cannot:

  • Search the index: --cached searches staged content, which may differ from the working tree. External tools can only see the working tree.
  • Search historical revisions: --tree searches the content of a specific commit without checking it out. External tools would require extracting the entire tree to a temporary directory.
  • Respect tracked-file semantics: Libra's grep searches only tracked files by default, automatically excluding untracked and ignored files without needing a separate ignore configuration.
  • Produce structured output: The JSON output includes metadata like total counts, file lists, and warnings in a single parseable structure.

For pure working-tree searches, external tools are often faster. Libra's grep trades raw speed for version-control integration.

Why --tree for revision search?

The --tree flag searches the content of a specific revision's tree object, reading blob data directly from the object store. This is equivalent to git grep <revision> but uses a named flag instead of a positional argument to avoid ambiguity between patterns and revision specifiers.

Git's positional syntax (git grep <pattern> <revision>) is a common source of confusion because the parser must distinguish between a pattern and a revision. Libra avoids this ambiguity by requiring --tree as an explicit flag.

Why --cached for index search?

The index (staging area) may contain different content than the working tree when files have been staged but not yet committed. Searching the index is useful for verifying what will be committed, especially in scripting and CI workflows.

Why regex by default?

Regular expressions are the standard pattern language for code search. Most developers expect grep to support regex, and Libra follows this convention. The --fixed-string flag is available for literal searches when regex metacharacters would cause problems.

How does this compare to Git and jj?

Git's grep is similar in scope: it searches tracked files with regex support, offers -l, -c, -n, -w, -v, -i, and can search in revisions and the index. Libra's implementation covers the same core feature set with the addition of structured JSON output.

jj does not have a built-in grep command. Users are expected to use external tools for text search. This works well for working-tree searches but means there is no integrated way to search historical revisions without first checking them out.

Parameter Comparison: Libra vs Git vs jj

ParameterLibraGitjj
PatternPositionalPositionalN/A (no grep)
Additional patterns-e / --regexp-eN/A
Pattern file-f / --file-fN/A
All match--all-match--all-matchN/A
Fixed string-F / --fixed-string-F / --fixed-stringsN/A
Ignore case-i / --ignore-case-i / --ignore-caseN/A
Count-c / --count-c / --countN/A
Files with matches-l / --files-with-matches-l / --files-with-matchesN/A
Files without matches-L / --files-without-matches-L / --files-without-matchN/A
Line number-n / --line-number-n / --line-numberN/A
Word regexp-w / --word-regexp-w / --word-regexpN/A
Invert match-v / --invert-match-v / --invert-matchN/A
Byte offset-b / --byte-offsetNot supportedN/A
PathspecTrailing positionalTrailing positionalN/A
Revision search--tree <REVISION><revision> (positional)N/A
Index search--cached--cachedN/A
Context lines-A / -B / -C-C / -A / -BN/A
Extended regexp-E / --extended-regexp-E / --extended-regexpN/A
Perl regexpRejected (exit 129)-P / --perl-regexpN/A
Max count-m / --max-count-m / --max-countN/A
Only matching-o / --only-matching-o / --only-matchingN/A
Show functionNot supported-p / --show-functionN/A
Max depth--max-depth <DEPTH>--max-depth <DEPTH>Equivalent when a pathspec is given (depth is measured relative to the pathspec). With no pathspec, Libra measures depth from the worktree root rather than the current directory, because libra grep always searches the whole worktree with worktree-relative paths — pass the directory as a pathspec to scope it.
ThreadsNot supported--threadsN/A
ColorAutomatic (terminal detection)--colorN/A
JSON outputBuilt-in JSON structureNot supportedN/A

Note: jj does not have a built-in grep command. Users rely on external tools like grep, rg, or ag for text search.

Error Handling

ScenarioStableErrorCodeExit
Not a libra repositoryLBR-REPO-001128
No pattern provided (and no -e or -f)Clap argument error2
Invalid regex patternLBR-CLI-003 (CliInvalidTarget)129
Revision not found (--tree)LBR-CLI-003 (CliInvalidTarget)129
No matches foundLBR-CLI-003 (CliInvalidTarget)129
Failed to read file (non-fatal)Warning in output, file skipped0
Failed to read pattern file (-f)Error with file path details128

libra graph

Command reference for `libra graph`

libra hash-object

Command reference for `libra hash-object`

On this page

SynopsisDescriptionOptionsOption DetailsCommon CommandsHuman OutputStructured Output (JSON)Design RationaleWhy built-in instead of relying on external grep/ripgrep?Why --tree for revision search?Why --cached for index search?Why regex by default?How does this compare to Git and jj?Parameter Comparison: Libra vs Git vs jjError Handling