Search
Overview
Type what you remember and get back everything you can read that matches it: work items, merge requests, comments, releases, repositories, groups, services, specification documents and wiki pages, ranked together in one list rather than nine lists you have to visit in turn. You never have to decide first whether the thing you are looking for was a work item or a merge request — that is the search's job, not yours.
You can point a search at everything you can see, at one group and everything beneath it, or at a single repository. Whichever you choose, a result only appears if you could already open it, so a search is a safe thing to run in a group you share with people who cannot see everything you can.
Before you start
- Nothing to switch on.Search reads what is already there. A work item is findable the moment it is saved, and so is an edit to it.
- You find what you can already read, and nothing else.A public repository's contents answer anyone, an internal one answers anyone signed in, and a private one answers its members and the members of the groups above it. Signed out, the search across everything still works and returns only public results.
- The right to open the container you scope to.A group or repository search authorizes that group or repository before it matches anything. A public one lets anybody in, an internal one lets in anybody signed in, and a private one needs Reporter access — a membership on it, or on a group above it. Without that the call is refused before any matching happens:
404when you are signed in and cannot see it,401when you are not signed in at all. - A token with
search:read, if you are calling the API with a personal or service-account access token. Search has its own scope rather than borrowing the ones belonging to the domains it spans, so a token carryingrepo:readandissue:readstill cannot search. A signed-in browser session and a CLI signed in withgitmoap logincarry your own access and need nothing extra. A token bound to one repository or group is refused with403when a group or repository search is aimed outside that binding. - A service account searches with its own access.A service-account token's search finds what the account's grants let it read — the same answer the account's own listings give.
- An id, or a path.Over the API the group or repository you scope to is named by its id. The CLI takes either an id or the path you see in the address bar, and resolves the path for you.
How it works
One ranked list, not eight
Every kind of result is scored against your query and the scores are compared with each other, so the most relevant merge request can outrank the least relevant work item and land above it in the same list. Each hit carries a rank, which is the score that put it where it is; the list is ordered by that, and by id where two hits tie, so the same query gives the same order twice.
A hit is a signpost, not a copy of the thing it points at. You get its kind, its id, a title, a short excerpt and when it last changed, plus the scope it sits in and its number for the kinds that have them — enough to render a row and follow the link. A hit with nothing to excerpt, a repository with no description say, arrives without one. Everything else lives at the resource's own address.
A comment's scope and number are those of the work item or merge request it was written on, and its on says which of the two it is, so every comment leads back to its thread. A service's scope is the group it belongs to. A specification document carries its file's path in the repository, and a wiki page carries the page's current path, which is what reads it.
What each kind matches
| Kind | What is searched | The hit's title | The hit's id |
|---|---|---|---|
issue | title and description | the work item's title | its own id |
merge_request | title and description | the merge request's title | the repository id and the merge request number, joined by / |
comment | the comment body | the first 120 characters of the body | the comment's id |
release | name and release notes | the release name | the repository id and the tag, joined by / |
repository | display name and description as words; the full path as a fragment | the full path | the repository id |
group | display name and description as words; the full path as a fragment | the full path | the group id |
service | slug, display name and description as words; the slug as a fragment | the slug | the service id |
spec | title, summary and document id as words; the document id as a fragment | the document title | the repository id and the document id, joined by / |
wiki | title, summary and page id as words; the page id as a fragment | the page title | the wiki id and the page id, joined by / |
Two different kinds of matching are at work in that table, and the difference is the thing people trip over. Titles, descriptions and bodies match whole words, with English stemming — deploying finds deployed and deploys, but deplo finds nothing. Paths, slugs and document ids also match fragments, because they are identifiers and no amount of stemming makes check find checkout-service. So a half-typed word finds a repository and does not find a work item.
Comments are searched as themselves rather than as part of the thing they are attached to, which is why a discussion that repeats a term ten times produces ten hits rather than one very relevant work item. Specification documents are matched on their title, summary and document id — not their full body.
Writing a query
The box takes the syntax a search box has trained you to expect:
| You type | You get |
|---|---|
flaky login test | results matching all three words, in any order and any position |
"connection pool" | results containing that exact phrase, in that order |
timeout -docker | results matching timeout with everything mentioning docker removed |
retry or backoff | results matching either word |
Casing never matters. Punctuation is dropped from word matching, but it counts in a path, a slug or a document id — which is what makes platform/pay match the path it looks like. The operators shape word matching only: a fragment is compared against the whole of what you typed, so quoting a phrase or excluding a word does not change which repositories, groups or services a fragment finds.
A query of nothing but spaces is refused rather than treated as "everything", because a search that returns everything you can read is never what anybody meant. So is a query made only of very common words, in effect — the and and are dropped from word matching and leave nothing to match on.
Where the search runs
| Scope | What it covers | What it can return |
|---|---|---|
| Everything you can see | every group you can read — the ones you belong to, everything public, and everything internal once you are signed in | all nine kinds |
| A group | that group, its subgroups, and every repository under any of them | work items, merge requests, comments, releases, services, specification documents and wiki pages |
| A repository | that one repository | work items, merge requests, comments, releases, specification documents and wiki pages |
A scoped search returns the things filed inside the scope. Repositories and groups are containers rather than things filed in one, so a group or repository search never returns them — to find one, search across everything and narrow with types. A service belongs to a group, so a search in that group or one above it finds it, and a repository search does not.
Paging
A page holds up to 50 hits and defaults to 20. A full page comes back with a next_token; pass it on the next call to get the page after it, and keep going until a response arrives without one. That is the only reliable stopping condition — never stop because a page looked short, and never pass a cursor from a listing to a search or the other way round. A search cursor is a position in relevance order, a listing cursor is a position in most-recently-updated order, and the two mean different things.
How to
Search everything you can see
The broadest search: every group you can read, every kind of result, ranked together. It is the right starting point when you know the words but not where the thing lives.
Choose the search box in the top bar, or press / from anywhere. Suggestions appear as you type; press Enter for the full results page. That page shows one ranked list with a label on each row saying what the row is, and chips for narrowing it by kind.
gitmoap search search-global --q <q>curl -X GET https://api.gitmoap.example/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"Signed out, this call still works and answers with public results only. Signed in, it spans everything your memberships reach — which can be surprisingly wide, so scope the search when you already know where you are looking.
Search a group and everything below it
A group search covers the group, every subgroup underneath it and every repository in any of them. It is the search to use when a term means one thing in your team's group and something else across the company.
Open the group and choose Search in the side navigation. If you start typing in the top bar while you are already on a group's page, the first option offered is in this group — choosing it scopes the query without leaving the page.
gitmoap search search-group --group-id <id> --q <q>curl -X GET https://api.gitmoap.example/groups/{group_id}/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"You need to be able to see the group. A private group you are not a member of answers 404 rather than telling you it exists, and answers 401 if you are not signed in at all.
Search one repository
The narrowest scope, and the fastest to read: everything filed against one repository, with no results from anywhere else competing for the top of the list.
Open the repository and choose Search in the side navigation, or press / while a repository page is open — the box scopes to that repository by default and offers search everywhere as the alternative.
gitmoap search search-repo --repo-id <id> --q <q>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"Narrow a search to one kind of thing
types takes a comma-separated list of the kind names from the table above — issue, merge_request, repository, group, release, comment, service, spec. Leave it out and every kind is searched.
The chips above the results — All, Work items, Merge requests, Comments, Releases, Repositories, Groups, Services, Specs — set this. They are multi-select: choosing two shows both, ranked against each other.
gitmoap search search-global --q <q>curl -X GET https://api.gitmoap.example/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"A name that is not on the list is refused with 400, rather than being ignored. Asking for ?types=issues and quietly getting merge requests back would be an answer about a search you did not run, so the refusal names every value that would have worked.
Write a query that finds one specific thing
When a plain query returns too much, the three operators in the table above cut it down fast, and they compose. "connection pool" -test finds the design discussion and drops the test that mentions it in passing. timeout or deadline widens a term whose spelling nobody agreed on.
Type the operators straight into the box — quotes for a phrase, a leading - to exclude a word, or between alternatives. They compose, so a quoted phrase and an exclusion can go in the same query.
gitmoap search search-global --q <q>curl -X GET https://api.gitmoap.example/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"A query may be up to 256 characters. If you are pasting a stack trace, quote the one distinctive line from it rather than the whole thing.
Page through a long result set
Ask for up to 50 hits at a time, then follow the cursor. Every response that fills its page carries next_token; a response without one is the end.
Results load a page at a time as you scroll — the next page is fetched for you as you reach the bottom of the list, so there is no cursor to carry by hand.
gitmoap search search-group --group-id <id> --q <q>curl -X GET https://api.gitmoap.example/groups/{group_id}/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"Asking for more than 50 is not an error — you get 50. Asking for a page size that is not a number is refused with 400.
Find a repository, group or service by name or path
These three kinds match on a fragment of their path or slug as well as on their description, which makes search a fast way to jump to somewhere you can half-remember the name of. platform/pay finds platform/payments-api, and check finds the checkout service.
Type the fragment into the top bar and look for the repository, group or service rows among the suggestions. Pressing Enter opens the full results page, where the Repositories, Groups and Services chips leave only those.
gitmoap search search-global --q <q>curl -X GET https://api.gitmoap.example/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"Search these from the top-level scope. A group or repository search cannot return them.
Find a specification document
Indexed specification documents are searchable by title, summary and document id, so you can find the document that owns a decision without knowing which repository it was written in.
Open the repository, choose Search, and pick the Specs chip. Each hit shows the document's title and its summary line, and links to the document as it was last indexed.
gitmoap search search-repo --repo-id <id> --q <q>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/search \ -H "Authorization: Bearer $GITMOAP_TOKEN"A document becomes searchable once it has been indexed on the repository's default branch. On any other branch the corpus is checked but not published, so a document that exists only on a working branch does not appear until the change lands — and a document deleted on the default branch stops appearing once that change is indexed.
Reference
3 endpoints
GET/searchsearch:read
Search everything the caller can see
Token scope
search:read
Send
Query
- qstringFree text. Quote a phrase with
"…", and exclude a word with a leading- - typesstringoptionalComma-separated kinds to search — issue, merge_request, repository, group, release, comment. Omit for all
- limitint32optionalPage size (default 20, max 50)
- next_tokenstringoptionalPagination cursor — a position in rank order, not recency order
Refuses
400Missing or invalid q/types/limit/cursor401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient
Receive
items[]object10 fields
- excerptobjectoptionalA short excerpt of the matching body, when the kind has one.
- idstringThe resource's own identifier, as its domain names it (an issue
uid, a repository id, a group id, a releasetag_name). - iidint64optionalThe per-scope sequential number, for the kinds that have one. A comment carries the number of the work item or merge request it was written on.
- kindcommentgroupissuemerge_requestreleaserepositoryservicespecwikiWhat kind of thing a search result is.
- onobjectoptional
- pathobjectoptionalFor a spec: the document's file in its repository. For a wiki page: the page's path, which is what reads it.
- rankfloatHow well the result matches the query; results are ordered by it, highest first.
- scopeobjectoptional
- titlestring
- updated_atstring
- next_tokenobjectoptional
GET/groups/{group_id}/searchsearch:read
Search a group and everything beneath it
Token scope
search:read
Send
Path
- group_iduuidGroup ID
Query
- qstringFree text
- typesstringoptionalComma-separated kinds to search
- limitint32optionalPage size (default 20, max 50)
- next_tokenstringoptionalPagination cursor
Refuses
400Missing or invalid q/types/limit/cursor401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Group not found, or private with no access
Receive
The same shape search_global returns — 2 fields. Go to it
Show it here
items[]object10 fields
- excerptobjectoptionalA short excerpt of the matching body, when the kind has one.
- idstringThe resource's own identifier, as its domain names it (an issue
uid, a repository id, a group id, a releasetag_name). - iidint64optionalThe per-scope sequential number, for the kinds that have one. A comment carries the number of the work item or merge request it was written on.
- kindcommentgroupissuemerge_requestreleaserepositoryservicespecwikiWhat kind of thing a search result is.
- onobjectoptional
- pathobjectoptionalFor a spec: the document's file in its repository. For a wiki page: the page's path, which is what reads it.
- rankfloatHow well the result matches the query; results are ordered by it, highest first.
- scopeobjectoptional
- titlestring
- updated_atstring
- next_tokenobjectoptional
GET/repositories/{repo_id}/searchsearch:read
Search one repository
Token scope
search:read
Send
Path
- repo_iduuidRepository ID
Query
- qstringFree text
- typesstringoptionalComma-separated kinds to search
- limitint32optionalPage size (default 20, max 50)
- next_tokenstringoptionalPagination cursor
Refuses
400Missing or invalid q/types/limit/cursor401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Repository not found, or private with no access
Receive
The same shape search_global returns — 2 fields. Go to it
Show it here
items[]object10 fields
- excerptobjectoptionalA short excerpt of the matching body, when the kind has one.
- idstringThe resource's own identifier, as its domain names it (an issue
uid, a repository id, a group id, a releasetag_name). - iidint64optionalThe per-scope sequential number, for the kinds that have one. A comment carries the number of the work item or merge request it was written on.
- kindcommentgroupissuemerge_requestreleaserepositoryservicespecwikiWhat kind of thing a search result is.
- onobjectoptional
- pathobjectoptionalFor a spec: the document's file in its repository. For a wiki page: the page's path, which is what reads it.
- rankfloatHow well the result matches the query; results are ordered by it, highest first.
- scopeobjectoptional
- titlestring
- updated_atstring
- next_tokenobjectoptional
Limits
| What | Value | What happens at it |
|---|---|---|
| Query length | 256 characters | Longer is refused with 400 and nothing is searched |
| Page size | 20 by default, 50 maximum | A larger number is reduced to 50 rather than refused; zero or a negative number becomes 1 |
| Excerpt length | 200 characters | The excerpt is the opening of the description, body or summary, cut at that point |
| Comment hit title | 120 characters | A comment has no title, so the opening of its body stands in for one |
| Kinds | the eight in the table above | An unrecognised name in types is refused with 400 |
Searching creates nothing, so it consumes none of your namespace's creation limits, and the size of a result page is the only bound on what one call returns. Nothing here differs by plan.
Troubleshooting
400 BAD_REQUEST, q is required
- Cause
- The call went out with no query parameter at all
- What to do
- Send
q. There is no "match everything" mode — to list a whole domain, use that domain's own listing
400 BAD_REQUEST, q must not be empty
- Cause
qwas present but blank, or only whitespace- What to do
- Type at least one word. A blank query is refused rather than treated as a request for everything
400 BAD_REQUEST, q must be at most 256 characters
- Cause
- The query is longer than the limit — usually a pasted log line or stack trace
- What to do
- Quote the one distinctive phrase from it instead
400 BAD_REQUEST, unknown search type 'issues'
- Cause
- A value in
typesis not one of the eight names — plural forms are the usual cause - What to do
- Use the singular names. The message lists every value that would have worked
400 BAD_REQUEST, invalid limit
- Cause
- The page size is not a whole number
- What to do
- Send an integer. Anything above 50 is reduced to 50, so send 50 for the biggest page
400 BAD_REQUEST, invalid search cursor
- Cause
- The cursor is damaged, or it came from somewhere else — a listing's cursor is not a search cursor
- What to do
- Start again from the first page and follow only the
next_tokena search returned
400 BAD_REQUESTmentioning group_id or repo_id
- Cause
- A path was pasted where an id belongs
- What to do
- Use the id, or run the search from the CLI, which accepts a path and resolves it for you
401 UNAUTHORIZEDon a group or repository search while signed out
- Cause
- The group or repository is internal or private, and a scoped search must first see its container
- What to do
- Sign in. The search across everything works signed out and returns public results
401 UNAUTHORIZEDwith a token attached
- Cause
- The token has expired, been revoked, or was truncated in transit
- What to do
- Mint a fresh token and retry. A revoked token cannot be revived
403 INSUFFICIENT_SCOPE
- Cause
- The token is valid but was minted without
search:read— the scopes of the domains being searched do not stand in for it - What to do
- Mint a new token carrying
search:read. Scopes are fixed when a token is created and cannot be added afterwards
403 FORBIDDEN
- Cause
- The token is bound to one repository or group, and a group or repository search was aimed outside that binding
- What to do
- Aim the search at the bound resource, or use a token without a resource binding
404 NOT_FOUNDon a group or repository you know exists
- Cause
- It is private and you are not a member of it or of any group above it — or the id is wrong
- What to do
- A private resource answers
404rather than403so its existence stays private. Check your membership, then check the id
Nothing found for a word you can see on the screen
- Cause
- You typed part of a word. Titles, descriptions and bodies match whole words with stemming
- What to do
- Type the whole word. Only paths, service slugs and specification document ids match on a fragment
A repository, group or service is missing from a group or repository search
- Cause
- Those three are containers rather than things filed inside a scope, so a scoped search cannot return them
- What to do
- Search across everything and narrow with
types=repository,grouporservice
A comment on a merge request is missing from a repository search
- Cause
- In a scoped search, comment hits are the ones on work items
- What to do
- Search across everything with
types=merge_request,comment, or open the merge request and read its discussion
Results seem to stop early
- Cause
- You stopped paging because a page looked short. A full page always carries a cursor and a short one is genuinely the end
- What to do
- Page until a response arrives with no
next_token, never until a page is smaller than you asked for
A service-account token's search misses a repository you can see yourself
- Cause
- Search answers as the token's owner, and the account holds no grant on that repository
- What to do
- Grant the account access to the repository, or run the search with your own token or signed-in session
A common word finds nothing
- Cause
- Words like
the,andandisare dropped from word matching, so a query made only of them has nothing left to match - What to do
- Search on the distinctive words in the phrase, and quote them together when their order matters
A specification document that exists in the repository is not found
- Cause
- It has not been indexed on the default branch — a working branch is checked but not published
- What to do
- Land the change on the default branch and retry once the indexing run for that push has finished
The excerpt does not contain the words you searched for
- Cause
- The excerpt is the opening of the body, not a highlight of the match
- What to do
- Open the result. The match may be further down the body, or in the title
A file's contents are not found
- Cause
- Search covers the eight kinds above, and file contents are not one of them
- What to do
- Clone the repository and use
git grep, or find the merge request that changed the file
FAQ
Why is this separate from the filter box on the work item list?
They answer different questions. A listing's filter narrows a list that stays in most-recently-updated order, so you can page through it and know where you are. Search reorders by relevance, which is what you want when you are hunting and the wrong thing when you are reviewing a queue. Both accept free text; use the listing filter when you care about recency and search when you care about the best match.
Does a search ever show me something I cannot open?
No. Visibility is applied while the results are being selected, not afterwards, so a page is never quietly shortened by hidden rows and a hit you can see is a hit you can follow.
Can I search inside files?
The eight kinds in the table are what search covers, and file contents are not among them. Clone the repository and use git grep for a string in the code.
Can I search a pipeline run's logs?
Not from here. A step's log output has its own search on the run itself, which matches your term case-insensitively and hands back the lines it appears on — a ranked hit would lose exactly the line numbers you were after. See Pipelines.
Can I search published packages?
Yes, through the registry protocols rather than here — the npm and Cargo endpoints answer their own clients' search requests, so npm search and a Cargo registry search work against your own registries unchanged. See Package registries.
Why does a comment result show a wall of text where a title should be?
A comment has no title, so the first 120 characters of its body stand in for one. The excerpt below it is the first 200.
Related
- Issuesthe work items search finds, and the listing filter that keeps them in recency order
- Merge requeststhe reviews and discussions on the other side of a hit
- Repositoriesvisibility, which decides what your search can see at all
- Groupsnesting, which decides how far a group-scoped search reaches
- Service catalogwhat a service is, and why its slug matches on a fragment
- Spec indexhow a repository's specification corpus gets indexed and when
- Access tokensminting a token that carries
search:read - Service accountswhat a service account can read, and so what its search finds
- Pipelinessearching a run's logs, which is its own surface
- Package registriesthe separate search each registry protocol provides