Discovering Repos¶
git-processor and pr-processor's default command is their worker
(worker.py), so one-shot scripts like discover_repos.py need
--entrypoint python3 to override that:
docker compose run --rm --entrypoint python3 git-processor discover_repos.py <org-or-group> --provider github
--provider is github (default), gitlab, bitbucket_cloud,
bitbucket_server, or azure_devops (Azure DevOps Services/cloud only —
Azure DevOps Server/TFS isn't supported). <org-or-group> is an
org/group/workspace, not a personal account — this discovers everything
the org owns, not one user's repos. Queues each repo found — safe to
re-run later to pick up new ones.
Bitbucket Cloud credentials
Set BITBUCKET_EMAIL to your Atlassian account email (not your
Bitbucket username) and BITBUCKET_API_TOKEN to a Bitbucket
API token
with read access to repositories and pull requests. That's all:
git-processor works out the git username on its own. Atlassian
removed app passwords in July 2026. If your .env still uses
BITBUCKET_USERNAME / BITBUCKET_APP_PASSWORD, it keeps working,
and the logs remind you to rename them.
If the provider rejects your credentials, discover_repos.py stops
with a message naming the settings to check.
Filtering what gets queued¶
To skip archived/inactive repos or ones matching a name pattern (e.g. bot
repos), or to track only part of an org, write a discover_config.yaml
to /var/lib/eng-metrics-suite/ on the host:
exclude_archived: true # skip repos the provider reports as archived
exclude_inactive_days: 730 # skip repos with no push in this many days
exclude_patterns: # skip repos whose name (not "org/repo") matches
- "-bot$"
- "^terraform-"
include_patterns: # bring back a repo any rule above dropped
- "^terraform-core$"
exclude_namespaces: # skip whole orgs, groups, workspaces or projects
- "^acme/sandbox(/|$)"
include_namespaces: # bring back a namespace excluded above
There are two kinds of rules. In each, excludes are applied first and includes bring repos back, and a repo is queued only if it passes both:
- Name rules match the repo name alone (
my-bot, notacme/my-bot).include_patternscan bring back a repo dropped as archived, inactive or by an exclude pattern. - Namespace rules match the repo's path minus its name: the GitHub
org, every GitLab subgroup level (
acme/payments/cards), the Bitbucket Cloud workspace, the Bitbucket Server project key, or the Azure DevOps project.include_namespacesonly undoesexclude_namespaces; an archived or inactive repo in an included namespace stays out.
All patterns are regular expressions, not wildcards: ^payments means
"starts with payments", while payments* means "payment" followed by any
number of "s". Every key is optional, a key left blank is the same as
leaving it out, and with no rules every repo is queued.
For example, to track only GitLab's acme/payments group and the
subgroups under it (the (/|$) keeps acme/payments-legacy out):
exclude_namespaces: [".*"]
include_namespaces: ["^acme/payments(/|$)"]
Or only the repos whose names start with payments:
exclude_patterns: [".*"]
include_patterns: ["^payments"]
Pass the config via --config:
docker compose run --rm --entrypoint python3 git-processor discover_repos.py <org-or-group> --provider github --config /var/lib/eng-metrics-suite/discover_config.yaml
Changing the rules later never untracks a repo that's already tracked;
use remove_repo.py (below) for that. Loosening them picks up the newly
included repos on the next run.
Preview with a dry run¶
Before queuing anything, add --dry-run to see what your rules include
and exclude. It only talks to your provider, so it works before the
database is set up. Run it from the directory where you want the
results:
docker compose run --rm -T --user "$(id -u):$(id -g)" -v "$PWD:/out" -w /out \
--entrypoint python3 git-processor /app/discover_repos.py <org-or-group> --provider github \
--config /var/lib/eng-metrics-suite/discover_config.yaml --dry-run
The terminal shows a summary, with a count for each rule:
Dry run for gitlab 'acme' (nothing was queued)
found: 3776
include: 96
96 rescued by include namespace '^acme/payments(/|$)'
exclude: 3680
3654 matched exclude namespace '.*'
26 archived
Per-repo list: ./discover-dry-run-acme-20261014T054523Z.csv
The CSV lists every repo with its decision (include or exclude) and
the reason, including which include rule brought a repo back and what
had excluded it. On a large org it can run to tens of thousands of rows.
The -v "$PWD:/out" -w /out part puts the CSV in your current directory.
Without it the file is written inside the container, which is deleted
when the run ends. --user makes you the file's owner instead of root;
if your config file is readable only by root (for example, created with
sudo and mode 600), chmod 644 it or drop --user.
Coverage varies by provider
GitHub and GitLab support every key. Bitbucket Cloud has no "archived"
concept (exclude_archived never matches there) and checks
updated_on instead of push date for inactivity. Bitbucket Server's
repo-list endpoint exposes neither signal, so only the pattern and
namespace rules do anything there. Azure DevOps is similar to Bitbucket Server: its
repo-list endpoint has no push date at all (exclude_inactive_days is
a no-op), and its closest "archived" signal (isDisabled) means
something slightly different — hidden org-wide, not just inactive —
so treat exclude_archived there as an approximation.
Renamed a repo upstream? Re-running this is all you need to do — see Repo Renames & Duplicate Repos for how that's handled and how to check for any repos duplicated by a rename that happened before you upgraded to a version with this fix.
Untracking a repo¶
Decommissioned a repo, added one by mistake, or a customer asked you to
stop tracking it? remove_repo.py permanently deletes it and everything
that depends on it (commits, tags, PR data, any linked Jira project and
its incidents):
docker compose run --rm --entrypoint python3 git-processor remove_repo.py <identity_key> \
--actor "you@company.com" --reason "decommissioned" --yes
<identity_key> is the normalized form discover_repos.py/worker.py
already use internally (e.g. github.com/owner/repo) — the same value
shown in logs and in a SELECT identity_key FROM repos if you need to
look it up. Omit --yes first to preview exactly what would be deleted
without changing anything. --actor is required and never inferred —
this self-hosted deployment has no user-identity system to infer it
from.
Once removed, this repo is permanently guarded against being silently
re-added — neither a future discover_repos.py run nor a manual
git_processor.py <path> import will resurrect it; both fail loudly
(the latter with an error pointing at the fix) instead of quietly
re-tracking it. If it should be trackable again later, lift the guard
deliberately:
docker compose run --rm --entrypoint python3 git-processor remove_repo.py <identity_key> \
--allow-readd --actor "you@company.com" --yes
This only lifts the guard — it doesn't restore any deleted data, and
doesn't itself re-add the repo. Run discover_repos.py (or
git_processor.py) again afterward to actually re-track it.
Next: Jira Integration if you want change failure rate/MTTR or an investment allocation report too, or straight to Running Workers to actually import what you just queued.