Skip to content

Repository files navigation

githubstats

Collects monthly GitHub language statistics from the GH Archive and the GitHub GraphQL API, producing per-language weighted activity ratings for a given month.


Pipeline overview

Five tools run in sequence to produce language-rating files for a month:

github_archive_loader → archive-YYYYMM.csv
↓
filter_archive → archive-YYYYMM-filtered.csv
↓
github_language_loader → languages-YYYY-MM.jsonl
↓
produce_statistics → language-ratings-YYYY-MM-<type>.jsonl (one per statistic type)
↓
pack_statistics → language-ratings-all-<type>.jsonl (one per statistic type)
ToolInputOutput
github_archive_loaderGH Archive hourly .json.gz files (downloaded automatically)archive-YYYYMM.csv (CSV)
Sample:
actor,repo,event_type,action,language,count
torvalds,torvalds/linux,PushEvent,,,42
octocat,octocat/Hello-World,PullRequestEvent,opened,,3
filter_archivearchive CSVarchive-YYYYMM-filtered.csv (CSV)
same format as above, with bots, CI actors, single-event repos, etc. removed
github_language_loaderstdin — one owner/repo slug per linelanguages-YYYY-MM.jsonl (JSONL)
Sample:
{"repo":"torvalds/linux","total_size":1247804,"languages":[{"language":"C","size":1100000},{"language":"Shell","size":80000}],"fetched_at":"2026-08-02T17:54:00Z"}
{"repo":"octocat/Hello-World","total_size":1024,"languages":[{"language":"Ruby","size":1024}],"fetched_at":"2026-08-02T17:54:00Z"}
produce_statisticsfiltered archive CSV + languages JSONLlanguage-ratings-YYYY-MM-<type>.jsonl (JSONL, one per statistic type)
Sample:
{"language":"TypeScript","percentage":22.63,"rating":586871.81}
{"language":"Python","percentage":15.94,"rating":413407.79}
{"language":"JavaScript","percentage":11.12,"rating":288414.50}
pack_statisticsper-month language-ratings-YYYY-MM-<type>.jsonl fileslanguage-ratings-all-<type>.jsonl (JSONL, one per statistic type)
Sample:
{"month":"2026-01","language":"TypeScript","percentage":22.63,"rating":586871.81}
{"month":"2026-01","language":"Python","percentage":15.94,"rating":413407.79}
{"month":"2026-02","language":"TypeScript","percentage":21.87,"rating":568204.13}

Required environment variable for github_language_loader:

export GITHUB_TOKEN=ghp_… # GitHub PAT with public_repo read access

Running the pipeline

YEAR=2026
MONTH=01 # zero-padded for file names# Step 1 — download & aggregate GH Archive events for the month
cargo run --release --bin github_archive_loader -- \
--year "$YEAR" \
--month "$MONTH" \
--parallelism 10 \
--output "data/archive-${YEAR}${MONTH}.csv"# Step 2 — filter out bots, CI actors, noise repos
cargo run --release --bin filter_archive -- \
--input "data/archive-${YEAR}${MONTH}.csv" \
--output "data/archive-${YEAR}${MONTH}-filtered.csv"# Step 3 — resolve language breakdown for repos with PR activity## For archives up to and including September 2025 the GH Archive CSV already# contains a language column (field 5). Extract it directly with awk — no# GitHub API calls required:## awk -F',' 'NR==1{next} $5==""{next} seen[$2]++{next} \# {printf "{\"repo\":\"%s\",\"total_size\":1,\"languages\":[{\"language\":\"%s\",\"size\":1}]}\n", $2, $5}' \# "data/archive-${YEAR}${MONTH}.csv" > "data/languages-${YEAR}-${MONTH}.jsonl"## Note: use the unfiltered archive here so no repos are missed; the language# column reflects the first event seen for each repo in the raw archive.# total_size and size are set to 1 (byte counts are not in the archive).## From October 2025 onwards GitHub stripped language from event payloads, so# the GraphQL fallback below is required for those months.# See: https://github.blog/changelog/2025-08-08-upcoming-changes-to-github-events-api-payloads/## (extract unique repo slugs that had PushEvents, skip the header)export GITHUB_TOKEN=ghp_…
awk -F',''NR>1 && $3=="PushEvent" {print $2}' \
"data/archive-${YEAR}${MONTH}-filtered.csv"| sort -u \
| cargo run --release --bin github_language_loader -- \
>"data/languages-${YEAR}-${MONTH}.jsonl"# Step 4 — compute weighted per-language ratings (one output file per statistic type)
cargo run --release --bin produce_statistics -- \
--archive "data/archive-${YEAR}${MONTH}-filtered.csv" \
--languages "data/languages-${YEAR}-${MONTH}.jsonl" \
--output-dir data/
# Step 5 — pack all monthly ratings into combined files (run once after all months are produced)forTYPEin pr-count issue-count push-count developer-activity active-repos star-count;do
cargo run --release --bin pack_statistics -- \
--type "$TYPE" \
--input-dir data/ \
--output-dir data/
done

Rating files

produce_statistics writes multiple JSONL files, all sorted descending by rating:

FileSignalFormula
language-ratings-YYYY-MM-pr-count.jsonlPull-request volumerating[L] += pr_count × (size_L / total_size)
language-ratings-YYYY-MM-issue-count.jsonlIssue volumerating[L] += issue_count × (size_L / total_size)
language-ratings-YYYY-MM-push-count.jsonlPush volumerating[L] += push_count × (size_L / total_size)
language-ratings-YYYY-MM-developer-activity.jsonlDistinct contributors (PR + push)rating[L] += distinct_contributors × (size_L / total_size)
language-ratings-YYYY-MM-active-repos.jsonlActive repository breadthrating[L] += 1 × (size_L / total_size) per active repo
language-ratings-YYYY-MM-star-count.jsonlStars (WatchEvents)rating[L] += star_count × (size_L / total_size)

Each record:

{"language":"TypeScript","rating":322361.9}

pack_statistics merges all monthly files for a given type into one combined file:

FileContents
language-ratings-all-pr-count.jsonlAll months, pr-count, sorted chronologically
language-ratings-all-issue-count.jsonlAll months, issue-count, sorted chronologically
language-ratings-all-push-count.jsonlAll months, push-count, sorted chronologically
language-ratings-all-developer-activity.jsonlAll months, developer-activity, sorted chronologically
language-ratings-all-active-repos.jsonlAll months, active-repos, sorted chronologically
language-ratings-all-star-count.jsonlAll months, star-count, sorted chronologically

Each record has a month field prepended:

{"month":"2026-01","language":"TypeScript","rating":322361.9}

Rating formula

For each repository with language breakdown {L: size_L} and total codebase size total_size:

rating[L] += event_count × (size_L / total_size)

Example: a repo with 2 PRs that is 70% TypeScript / 30% Python contributes 1.4 to TypeScript and 0.6 to Python.

The developer-activity variant uses distinct contributors — the union of unique actors from PullRequestEvent and PushEvent — instead of raw event counts. This counts each person who actively committed to or reviewed a repository once, regardless of how often they pushed or opened PRs, making the metric neutral to per-developer commit-frequency habits.

The active-repos variant contributes exactly 1 per repository (regardless of event volume) to each of that repository's languages by byte share. This measures breadth of language adoption — how many distinct active codebases use each language — rather than the volume of activity those codebases generate.


Why not just use the GH Archive for language data?

The GH Archive publishes every public GitHub event as hourly gzip-compressed NDJSON files (YYYY-MM-DD-H.json.gz). Until September 2025 those files contained rich payloads — including pull_request.base.repo.language — which made full language attribution possible with zero external API calls.

From October 2025 onwards GitHub stripped those payload fields. A 2026 PullRequestEvent contains only the PR URL, number, and the head/base ref and SHA. No language. No line counts. No merge flag.

This means for 2026 data the archive alone can tell you what happened and on which repository, but never in which language — hence the GraphQL language-lookup step.

Official reference

Upcoming changes to GitHub Events API payloads — GitHub Changelog, August 8, 2025. Rollout date: October 7, 2025.

Community impact documented in: Data size / number of events have dropped 100x since 2025-10-09


Why not just use Google BigQuery for archive data?

The GH Archive data is also available via Google BigQuery (githubarchive public dataset), which allows SQL queries over the full event history without downloading any files. There are two reasons this project downloads the raw .json.gz files directly instead:

  • Cost. BigQuery charges per byte scanned. A single month of GH Archive data is several hundred gigabytes; querying it repeatedly across many months adds up quickly. Downloading the hourly files is free.

  • Payload stripping. BigQuery mirrors whatever GH Archive publishes. From October 2025 onwards the payloads are already stripped (no language field) before they reach BigQuery, so the same GraphQL language-lookup step would still be required. BigQuery offers no advantage for post-2025 data.


Experiment: proportional weighting vs primary-language-only

produce_statistics --primary-only is an experimental mode that attributes all of a repo's score to its single dominant (largest-by-bytes) language, ignoring secondary languages entirely. Output filenames gain a -primary suffix (e.g. language-ratings-2024-01-pr-count-primary.jsonl).

What changes

Language typeProportionalPrimary-only
Markup/tooling (CSS, HTML, Shell, SCSS, Makefile, Dockerfile)Receive fractional credit from mixed reposDrop sharply — rarely the primary language
Pure-language repos (Go, Rust, Java, C#, PHP)Already near-primaryGain 8–16%
Dominant-ecosystem languages (TypeScript, Python)Top two in both modesGain 5–7%
Multi-language glue (JavaScript)Slight loss (~2–4%)Slight loss

Top-30 comparison — 2024-01, pr-count

RankLanguageProportionalPrimary-onlyRank ΔRating Δ
1TypeScript206,502216,060=+4.6%
2Python170,482180,602=+5.9%
3JavaScript114,230109,874=−3.8%
4Go95,636105,473=+10.3%
5Java87,18695,268=+9.3%
6C++68,94175,499=+9.5%
7Rust60,93966,430=+9.0%
8HTML43,41436,698−1−15.5%
9C37,60835,261−1−6.2%
10C#34,11837,342+2+9.5%
11Shell26,65420,116−1−24.5%
12PHP23,23028,068+1+20.8%
13Kotlin20,03919,470=−2.8%
14CSS18,8359,389−4−50.2%
15Ruby16,82618,685+1+11.0%
16Jupyter Notebook16,54216,169+1−2.3%
17MDX11,80710,105=−14.4%
18Swift10,40310,522+2+1.1%
19Vue9,7437,862−2−19.3%
20Dart8,1388,906+1+9.4%
21Nix7,6697,942+1+3.6%
22SCSS7,643dropped
23DM6,7747,476+1+10.4%
24Lua6,3086,307=−0.0%
25Scala6,0626,473+2+6.8%
26Markdown4,4124,468=+1.3%
27HCL4,3564,323=−0.7%
28Solidity4,0533,036−2−25.1%
29Makefile4,022dropped
30Svelte3,538dropped
LLVM6,107enters top-30
Julia3,634enters top-30
Haskell3,478enters top-30

Top-30 comparison — 2026-01, pr-count

RankLanguageProportionalPrimary-onlyRank ΔRating Δ
1TypeScript322,362339,471=+5.3%
2Python202,076216,536=+7.2%
3JavaScript134,815134,785=−0.0%
4Java81,93688,022=+7.4%
5HTML70,33559,251−2−15.8%
6Go57,37663,217+1+10.2%
7Rust55,01061,076+1+11.0%
8C++48,27852,468=+8.7%
9C#37,05141,320=+11.5%
10PHP27,94232,319=+15.7%
11CSS27,6025,935−10−78.5%
12Shell27,03720,152−1−25.5%
13C22,20720,818+1−6.3%
14Kotlin20,81020,933+3+0.6%
15Swift12,58812,209−1−3.0%
16Dart11,55713,065+2+13.0%
17Vue11,3709,731=−14.4%
18Ruby10,81912,804+3+18.4%
19Jupyter Notebook8,6868,057=−7.2%
20MDX8,2266,187=−24.8%
21Nix7,7088,374+3+8.6%
22HCL6,0125,638=−6.2%
23SCSS4,595dropped
24Lua4,2164,184+1−0.8%
25Blade3,6913,667=−0.7%
26DM3,4243,791+2+10.7%
27Svelte3,1542,175−3−31.0%
28PLpgSQL2,875dropped
29Astro2,8623,205+3+12.0%
30Scala2,8452,993+3+5.2%
LLVM2,899enters top-30
GDScript2,729enters top-30

Conclusion

The top-order ranking is largely stable between the two methods. The dominant languages (TypeScript, Python, JavaScript, Go, Java) hold their positions in both months under both formulas.

The meaningful differences are:

  1. Markup/tooling languages drop sharply in primary-only mode. CSS loses 50–78%, Shell 24–26%, SCSS and Makefile fall out of the top 30 entirely. These languages are almost always secondary in mixed repos, so their proportional score is mainly borrowed from other codebases.

  2. Pure-ecosystem languages gain modestly. Go, Rust, Java, C#, PHP each gain 8–16% because they tend to be the sole or dominant language in their repos — they were already getting most of the proportional credit.

  3. Systems languages enter the top 30 only in primary-only mode. LLVM, Julia, Haskell, and GDScript appear in the primary-only top 30. Their repos are dedicated to a single language, so they benefit most from eliminating fractional dilution.

  4. The proportional formula is more informative for understanding real-world language mix. A TypeScript repo that embeds 30% CSS genuinely represents CSS work; discarding that credit understates CSS activity. Primary-only is better treated as a "dominant language" index rather than a general activity index.

About

GitHub Programming Languages Statistics

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Contributors

Languages