Searching
A search runs one pattern over every searchable document in an index and returns every match, sorted by key, then line, then offset. The examples search acme/monorepo, a git repository kept in step with gv sync, and acme/support, an index of support tickets.
Run a search.
Section titled “Run a search.”This search finds every HTTP handler under services/billing/ and returns three lines after each one.
import osimport grepvinefrom grepvine.models import QueryRequest
client = grepvine.Client(api_key=os.environ["GREPVINE_API_KEY"])
query = QueryRequest( pattern=r"func \w+Handler\(", filter_="path STARTSWITH 'services/billing/'", lines_after=3,)for hit in grepvine.paginate(client.search.query, organization="acme", index="monorepo", body=query): print(f"{hit.key}:{hit.line_number}: {hit.text}")import { GrepvineClient, paginate } from "@grepvine/sdk";
const client = new GrepvineClient({ apiKey: process.env.GREPVINE_API_KEY! });
for await (const hit of paginate(client.search.query, { organization: "acme", index: "monorepo", pattern: String.raw`func \w+Handler\(`, filter: "path STARTSWITH 'services/billing/'", lines_after: 3,})) { console.log(`${hit.key}:${hit.line_number}: ${hit.text}`);}gv search acme/monorepo 'func \w+Handler\(' --filter "path STARTSWITH 'services/billing/'" -A 3curl -X POST "$API/organizations/acme/indexes/monorepo/query?amount=200" -H "$AUTH" \ -H "Content-Type: application/json" --data-binary @- <<'EOF'{"pattern": "func \\w+Handler\\(", "filter": "path STARTSWITH 'services/billing/'", "lines_after": 3}EOFEach client names the query options a little differently:
| API and TypeScript | Python QueryRequest |
gv search |
Default |
|---|---|---|---|
pattern |
pattern |
the positional PATTERN |
Required. |
mode: "literal" |
mode=QueryRequestMode.LITERAL |
-F |
regex |
case_sensitive: false |
case_sensitive=False |
-i |
true |
whole_word: true |
whole_word=True |
-w |
false |
multiline: true |
multiline=True |
-U |
false |
filter |
filter_ |
--filter |
None. |
lines_before, lines_after |
lines_before, lines_after |
-B, -A, or -C for both |
0 |
amount (page size) |
amount= on search.query |
--amount |
50, or 200 in gv |
Match literal text.
Section titled “Match literal text.”Literal mode matches the pattern’s bytes exactly, so it needs no escaping. Use it for identifiers, error messages, URLs and paths.
case_sensitive: falsematches under simple Unicode case folding.whole_word: truematches only where the characters on either side of the match aren’t word characters: letters, digits or_. It works in literal mode only.
gv search -F acme/support 'upstream timeout after 30000ms'gv search -F -w acme/monorepo retry_count # skips max_retry_countgv search -F -i acme/support 'connection reset by peer'Match a regular expression.
Section titled “Match a regular expression.”Regex mode, the default, uses RE2 syntax. It has no backreferences and no lookaround; tradeoffs explains why.
- Character classes.
\d,\w,\sand\bare ASCII. For other scripts, use Unicode classes such as\p{L},\p{Lu}or\p{Han}. - Case.
case_sensitive: falseapplies Unicode case folding to the whole pattern.(?i)does the same for part of it:(?i:error) E\d{4}. - Escaping. Quote patterns in single quotes in a shell, and use raw strings in Python (
r"...") and TypeScript (String.raw`...`).
| To find | Pattern | Index |
|---|---|---|
| Go HTTP handlers | func \w+Handler\( |
acme/monorepo |
| TODOs that name an owner | TODO\(\w+\): |
acme/monorepo |
| Possible AWS access key IDs | AKIA[0-9A-Z]{16} |
acme/monorepo |
Exact pins of requests |
^requests==\d+\.\d+\.\d+$ |
acme/monorepo |
| Timeouts of 10 seconds or more | timeout after \d{5,}ms |
acme/support |
| Order IDs pasted into tickets | ord_[0-9a-z]{24} |
acme/support |
Match across lines.
Section titled “Match across lines.”By default, a pattern matches within one line:
^and$mark the start and end of a line.- A character class never matches a line break.
- A pattern that contains a line break is rejected.
With multiline: true, the pattern runs over the whole document:
^and$still mark line edges, and\Aand\zmark the document’s edges.- A match can contain line breaks, and its result covers lines
line_numberthroughlast_line_number.
Even in multiline mode, . doesn’t match a line break. Use \n or \s to cross one, or start the pattern with (?s) to let . match it.
This finds Go error checks that return nil, which drops the error:
gv search -U acme/monorepo 'if err != nil \{\n\s*return nil\n\s*\}'Filter by metadata.
Section titled “Filter by metadata.”A filter restricts a search to documents whose metadata matches an expression:
path STARTSWITH 'services/billing/' AND NOT path STARTSWITH 'services/billing/vendor/'mtime >= (CURRENT_DATE - INTERVAL '7 days')product = 'checkout' AND priority <= 2 AND status != 'closed'opened_at >= TIMESTAMP '2026-10-01T00:00:00Z' AND opened_at < TIMESTAMP '2026-11-01T00:00:00Z'(lang = 'go' OR lang = 'rust') AND size < 1048576generated = falseassignee IS NULLOperators.
Section titled “Operators.”| Operator | Applies to |
|---|---|
=, != |
Every type. |
<, <=, >, >= |
Strings, integers, numbers and timestamps. Strings compare in byte order. |
STARTSWITH |
Strings. |
IS NULL, IS NOT NULL |
Whether the document has the field at all. |
Combine predicates with AND, OR, NOT and parentheses. NOT binds tighter than AND, and AND tighter than OR. Keywords are case-insensitive. ENDSWITH and CONTAINS aren’t supported; best practices shows what to store instead.
Each predicate puts the field on the left, as in size < 1048576. A field name starts with a letter or _ and holds letters, digits, _, . and -.
Values.
Section titled “Values.”| Type | Write it as | Examples |
|---|---|---|
string |
Single quotes. Double a quote to include one. | 'checkout', 'O''Brien' |
integer |
Digits, with an optional -. |
102400, -1 |
number |
Digits with a . or an exponent. |
0.5, 1e6 |
boolean |
true or false. |
generated = false |
timestamp |
TIMESTAMP and an RFC 3339 string. |
TIMESTAMP '2026-10-01T00:00:00Z' |
CURRENT_DATE is midnight UTC today. Subtract an interval of days, hours, minutes or seconds from any timestamp: CURRENT_DATE - INTERVAL '36 hours'.
Types and missing fields.
Section titled “Types and missing fields.”A comparison is true only when the document has the field with a value of the literal’s type. size = 1024.0 never matches an integer size, and priority = '1' never matches an integer priority.
When the field is missing or has another type, the comparison is unknown, and NOT of unknown is still unknown. A document matches only when the whole filter is true. Both status = 'closed' and NOT status = 'closed' therefore skip tickets with no status. To include them, say so:
status != 'closed' OR status IS NULLErrors.
Section titled “Errors.”A pattern or filter that doesn’t parse is rejected with a 400 and the code invalid_pattern or invalid_filter. A filter error names the byte where parsing stopped. For status = open, it reads filter byte 9: expected a value; quote a string, as in 'open'.
Read a result.
Section titled “Read a result.”A result is one match, or several matches that share lines. With --json, gv search prints each result in the API’s shape:
{ "key": "services/billing/refund.go", "revision": "9f2c41d07a3e", "line_number": 42, "last_line_number": 42, "line_start_offset": 1187, "line_end_offset": 1247, "text_start_offset": 1187, "text_line_number": 42, "text": "func RefundHandler(w http.ResponseWriter, r *http.Request) {", "matches": [{ "offset": 1187, "length": 19 }], "text_truncated": false, "matches_truncated": false, "lines_after": [ { "line_number": 43, "text": "\tctx := r.Context()", "text_truncated": false } ]}- Offsets count bytes from the start of the document. A match starts at byte
offset - text_start_offsetoftext. - Context lines arrive in
lines_beforeandlines_after, up to 20 each. There are fewer at the start and end of a document, and the field is left out when there are none. - Long lines and dense matches are cut, and
text_truncatedandmatches_truncatedsay so. Guarantees gives the limits.
To read more of the document around a result, request a byte range:
document = client.documents.get("acme", "monorepo", hit.key, range_="bytes=1187-4095")print(document.payload.read().decode())const blob = await client.documents.get({ organization: "acme", index: "monorepo", key: hit.key, Range: "bytes=1187-4095" });console.log(await blob.text());gv get acme/monorepo services/billing/refund.go --range bytes=1187-4095Page through results.
Section titled “Page through results.”paginate and gv search fetch page after page until the last one. To stop early, break out of the loop, or pass --max-results to gv search. If you page by hand, keep going until has_next is false: a page can be empty while more results follow. Guarantees covers page tokens and snapshots.
gv search exits with status 0 when something matched, 1 when nothing did, and 2 on an error.