{"id":50770355,"url":"https://github.com/ewels/contributor-graphs","last_synced_at":"2026-06-11T18:00:28.600Z","repository":{"id":363982563,"uuid":"1265813070","full_name":"ewels/contributor-graphs","owner":"ewels","description":"Contributor timelines for any git or GitHub repo: a publication-ready SVG and an interactive HTML page","archived":false,"fork":false,"pushed_at":"2026-06-11T07:25:15.000Z","size":3612,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T08:08:48.900Z","etag":null,"topics":["cli","contributors","data-visualization","git","github","open-source","rust","svg","timeline","visualization"],"latest_commit_sha":null,"homepage":"https://ewels.github.io/contributor-graphs/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ewels.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T05:20:11.000Z","updated_at":"2026-06-11T07:23:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ewels/contributor-graphs","commit_stats":null,"previous_names":["ewels/contributor-graphs"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ewels/contributor-graphs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewels%2Fcontributor-graphs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewels%2Fcontributor-graphs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewels%2Fcontributor-graphs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewels%2Fcontributor-graphs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ewels","download_url":"https://codeload.github.com/ewels/contributor-graphs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewels%2Fcontributor-graphs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34211067,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","contributors","data-visualization","git","github","open-source","rust","svg","timeline","visualization"],"created_at":"2026-06-11T18:00:20.802Z","updated_at":"2026-06-11T18:00:28.590Z","avatar_url":"https://github.com/ewels.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/ewels/contributor-graphs/main/docs/logo-dark.svg\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/ewels/contributor-graphs/main/docs/logo.svg\" alt=\"contributor-graphs\" height=\"76\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Contributor timelines for any git or GitHub repository: a publication-ready\n  SVG and a self-contained interactive HTML page.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ewels/contributor-graphs/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/ewels/contributor-graphs/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/ewels/contributor-graphs/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-Apache--2.0-blue.svg\" alt=\"License: Apache-2.0\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://ewels.github.io/contributor-graphs/\"\u003e\u003cimg src=\"https://img.shields.io/badge/docs-website-2f5fd0.svg\" alt=\"Docs\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nThe x-axis is time (first commit to today); each row is a contributor. Bars\nare shaded by monthly commit activity, so it's easy to see who was active when\nand how a project grew over the years.\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://ewels.github.io/contributor-graphs/\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/ewels/contributor-graphs/main/docs/app-rnaseq-dark.png\"\u003e\n      \u003cimg src=\"https://raw.githubusercontent.com/ewels/contributor-graphs/main/docs/app-rnaseq.png\" alt=\"The interactive contributor-graphs page for nf-core/rnaseq\" width=\"100%\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003e\u003ca href=\"https://ewels.github.io/contributor-graphs/\"\u003eDocumentation \u0026amp; live examples →\u003c/a\u003e\u003c/b\u003e\n\u003c/p\u003e\n\n## Features\n\n- **Works with anything:** a local path (`.`), a GitHub slug (`nf-core/rnaseq`),\n  or any git URL. Remote repos are cloned (history only) into a local cache.\n- **GitHub enrichment:** resolves real names, `@usernames` and avatars via the\n  GitHub API, using your `gh` CLI token automatically to avoid rate limits.\n- **Identity merging:** folds together the many name and email spellings a\n  single person accumulates over the years, with a manual override file for the\n  stragglers.\n- **Affiliation grouping:** auto-detects organisations from GitHub profile\n  companies (e.g. _SciLifeLab_, _Seqera_) and colours by them. Optionally\n  **collapse the whole chart to one row per affiliation**, so each bar is an\n  organisation rather than a person. Supply your own grouping file for control.\n- **Noise filters:** exclude bots, set a minimum-commit threshold, cap to the\n  top _N_ contributors. In the HTML these are live controls.\n- **Interactive HTML:** search, sort, filter by group, switch between\n  per-contributor and per-affiliation rows, drag-to-zoom the timeline, hover\n  for detail + activity sparkline, dark mode, and SVG/PNG export. Everything is\n  embedded in one file; no server needed.\n\n## Install\n\nGrab a prebuilt binary, install with Cargo, or use Docker. The\n[GitHub CLI](https://cli.github.com) (`gh`) is optional but recommended for\nenrichment without rate limits.\n\n**Prebuilt binary:** download the archive for your platform from the\n[releases page](https://github.com/ewels/contributor-graphs/releases), unpack\nit, and put `contributor-graphs` on your `PATH`. No toolchain required.\n\n**Cargo** (needs [Rust](https://rustup.rs)):\n\n```bash\ncargo install --git https://github.com/ewels/contributor-graphs  # latest\ncargo install contributor-graphs                                 # once on crates.io\n```\n\n**Docker:** published to the GitHub Container Registry:\n\n```bash\ndocker run --rm -v \"$PWD:/work\" -e GITHUB_TOKEN \\\n  ghcr.io/ewels/contributor-graphs nf-core/rnaseq\n```\n\n## Usage\n\n```bash\n# A GitHub repo by slug: clones history, enriches, writes two files\ncontributor-graphs nf-core/rnaseq\n\n# A local checkout\ncontributor-graphs . -o docs/\n\n# A full git URL\ncontributor-graphs https://github.com/MultiQC/MultiQC\n\n# Several sources pooled into one timeline (any mix of slugs, paths, URLs)\ncontributor-graphs nf-core/rnaseq nf-core/sarek MultiQC/MultiQC --title \"nf-core + MultiQC\"\n```\n\nThis writes `\u003crepo\u003e.svg` and `\u003crepo\u003e.html` into the output directory.\n\n### Multiple sources\n\nPass more than one source to pool every commit into a single timeline. Author\nidentities are resolved across the whole pool, so someone who appears in several\nrepositories shows up as one row. Commits that appear in more than one source\n(overlapping histories — e.g. a repo and a fork, or a branch grafted onto a\nrewrite) are de-duplicated by commit SHA; disjoint sources (separate repos for\nan org-wide view) simply concatenate. Use `--title` to name the combined chart.\n\n### Authentication\n\nTo enrich with usernames and avatars (and dodge GitHub's anonymous rate limit),\nthe tool reads a token from `$GITHUB_TOKEN` or `$GH_TOKEN`, falling back to\n`gh auth token` if neither is set. Locally, just be logged in:\n\n```bash\ngh auth login\n```\n\nIn CI, the `$GITHUB_TOKEN` that GitHub Actions injects is picked up\nautomatically, with no extra setup:\n\n```yaml\n- run: contributor-graphs ${{ github.repository }} -o site/\n  env:\n    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n```\n\nPass `--no-github` to skip all network calls and render from git data alone.\n\n## Common options\n\n| Flag                                | Description                                                          |\n| ----------------------------------- | -------------------------------------------------------------------- |\n| `-o, --output-dir \u003cDIR\u003e`            | Where to write outputs (default: `.`)                                |\n| `--title \u003cTITLE\u003e`                   | Override the chart title                                             |\n| `-b, --branch \u003cREF\u003e`                | Which branch/ref to read (default: `HEAD`)                           |\n| `--since \u003cDATE\u003e` / `--until \u003cDATE\u003e` | Restrict the commit window                                           |\n| `--min-commits \u003cN\u003e`                 | Hide contributors below `N` commits in the SVG (default: 1)          |\n| `--min-span-days \u003cN\u003e`               | Drop one-off/short-burst contributors (first-to-last span) from SVG  |\n| `--max-contributors \u003cN\u003e`            | Cap SVG rows to the top `N` by commits (default: 40)                 |\n| `--include-bots`                    | Keep bot accounts (excluded by default)                              |\n| `--exclude \u003cPATTERN\u003e`               | Drop contributors matching a name/login (repeatable)                 |\n| `--by-affiliation`                  | Collapse each row to a whole affiliation, not one person             |\n| `--unaffiliated-label \u003cTEXT\u003e`       | Bucket name for people with no affiliation (default: `Unaffiliated`) |\n| `--sort \u003cKEY\u003e`                      | `first` · `last` · `commits` · `duration` · `name`                   |\n| `--groups \u003cFILE\u003e`                   | Manual affiliation mapping (see below)                               |\n| `--identities \u003cFILE\u003e`               | Manual identity-merge file (see below)                               |\n| `--no-affiliation`                  | Disable auto group detection from profiles                           |\n| `--no-name-merge`                   | Don't merge identities that share an author name                     |\n| `--accent \u003cHEX\u003e`                    | Bar accent colour (default: `#2f6feb`)                               |\n| `--theme \u003cID\u003e`                      | Theme id: `auto`, `light`, `dark`, `wikipedia`, or a custom one      |\n| `--themes \u003cFILE.json\u003e`              | Define extra themes / configure the page's theme menu                |\n| `--lock-theme`                      | Hide the page's theme switcher and pin to one theme                  |\n| `--width \u003cPX\u003e`                      | Static SVG width (default: 1100)                                     |\n| `--format \u003csvg\\|html\\|both\u003e`        | Which outputs to write (default: both)                               |\n| `--open`                            | Open the HTML in your browser when done                              |\n\nRun `contributor-graphs --help` for the full list.\n\n### Themes\n\nThe interactive page has a single Theme dropdown (top right) offering Light,\nDark, and **Wikipedia**; it opens on your OS light/dark preference unless you\npick one, and the choice is remembered per browser. The Wikipedia theme borrows\nthe look of Wikipedia's \"band members over time\" timelines: a Linux Libertine\nheading over a plain sans-serif body, Wikipedia colours, square controls, and a\ndistinct solid bar per contributor instead of activity-heat shading.\n\n`--theme \u003cID\u003e` sets the SVG's look and the page's initial theme; `auto` (the\ndefault) renders the SVG light and lets the page follow the viewer's OS.\n\n#### Custom themes\n\nDefine your own themes in a JSON file and pass it with `--themes`. Each theme\ninherits from `extends` (a built-in or another custom theme; default `light`)\nand overrides only what it needs:\n\n```json\n{\n  \"default\": \"seqera\",\n  \"available\": [\"seqera\", \"dark\"],\n  \"lock\": false,\n  \"themes\": {\n    \"seqera\": {\n      \"label\": \"Seqera\",\n      \"extends\": \"light\",\n      \"accent\": \"#0d6273\",\n      \"bg\": \"#f4f8f8\"\n    }\n  }\n}\n```\n\n- `default` — the theme the page opens with (also settable with `--theme`).\n- `available` — which themes appear in the menu, in order (default: all).\n- `lock` (or `--lock-theme`) — hide the menu and pin to a single theme, so you\n  can ship one custom look with no switching.\n\nA theme may set any of: `label`, `extends`, `dark` (bool, for `color-scheme`\nand avatar shading), `flat` (bool, solid band bars + sans-serif chart font),\n`radius` (px), `font_sans`, `font_display`, and the colours `bg`, `card`,\n`border`, `border_strong`, `text`, `muted`, `faint`, `accent`, `accent_soft`,\n`grid_year`, `grid_month`, `track`, `ctx_area`, `ctx_line`. Custom themes work\nin both the SVG (`--theme \u003cid\u003e`) and the interactive page.\n\n### Grouping by affiliation\n\nAffiliations are detected automatically from the `company` field of each\ncontributor's GitHub profile. Variant spellings are merged, so `seqeralabs`,\n`Seqera Labs` and `Seqera` all count as one group. The most common groups get\ndistinct colours; the long tail shares a neutral grey, and bots are dropped.\n\nFor full control, supply a tab-separated file. Each row is `matcher\u003cTAB\u003egroup`,\nwhere _matcher_ is a name, email, or GitHub login:\n\n```tsv\n# groups.tsv\newels\tSeqera\nphil.ewels@seqera.io\tSeqera\nAlexander Peltzer\tBoehringer Ingelheim\nqbicsoftware\tQBiC\n```\n\n```bash\ncontributor-graphs nf-core/methylseq --groups groups.tsv\n```\n\nManual mappings take precedence over auto-detected affiliations.\n\nTo make the affiliations the _subject_ of the chart, pass `--by-affiliation`:\none bar per organisation, with every member's commits merged into it. People\nwith no detected affiliation are pooled into a single \"Unaffiliated\" row\n(rename it with `--unaffiliated-label`). In the interactive HTML this is the\n**Rows** dropdown, so you can flip between people and organisations live.\n\n```bash\ncontributor-graphs nf-core/rnaseq --by-affiliation\n```\n\n### Merging identities\n\nMost duplicate identities (same email, or same name across emails) merge\nautomatically. To force-merge the stragglers, supply a file where each row\nlists the canonical display name followed by any aliases (names, emails,\nlogins):\n\n```tsv\n# identities.tsv\nAlexander Peltzer\tapeltzer\ta.peltzer@gmail.com\tAlex Peltzer\nPatrick Hüther\tphue\tpatrick.huether@example.org\n```\n\n```bash\ncontributor-graphs nf-core/methylseq --identities identities.tsv\n```\n\n## How it works\n\n1. `git log` extracts every commit's author name, email, and timestamp\n   (honouring `.mailmap`).\n2. Commits are clustered into identities by shared email, then by shared\n   author name.\n3. For GitHub repos, each identity is resolved to a login + avatar (noreply\n   emails offline; the rest via the commits API), then profiles are fetched for\n   real names and companies. Clusters that resolve to the same login merge.\n4. Per-contributor stats and per-month activity bins are computed.\n5. The SVG and HTML are rendered. Avatars are embedded as data URIs so both\n   files are fully self-contained.\n\n## Releasing\n\nReleases are cut by publishing a GitHub Release whose tag is the version\n(e.g. `v0.1.0`). That triggers the workflows to build cross-platform binaries,\nattach them to the release, build the multi-arch Docker image, and publish to\ncrates.io.\n\ncrates.io publishing uses [Trusted Publishing](https://crates.io/docs/trusted-publishing)\n(OIDC, so no API token is stored in the repo). One-time setup:\n\n1. Publish the first version manually (Trusted Publishing can't attach to a\n   crate that doesn't exist yet): create a short-lived token at crates.io →\n   Account Settings → API Tokens, then `cargo publish` (run `cargo publish\n--dry-run` first). Revoke the token afterwards.\n2. On crates.io → the crate → Settings → Trusted Publishing, add a GitHub\n   publisher: owner `ewels`, repo `contributor-graphs`, workflow `release.yml`.\n3. From then on, bump `version` in `Cargo.toml`, then publish a GitHub Release. The workflow mints a short-lived token via OIDC\n   and publishes automatically.\n\n## License\n\n[Apache-2.0](https://github.com/ewels/contributor-graphs/blob/main/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fewels%2Fcontributor-graphs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fewels%2Fcontributor-graphs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fewels%2Fcontributor-graphs/lists"}