[{"data":1,"prerenderedAt":286},["ShallowReactive",2],{"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns":3,"\u002Ftools\u002Fgit-pkgs-explore-your-dependency-history":8},["Island",4],{"key":5,"result":6},"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns",{"head":7},{},{"id":9,"title":10,"authors":11,"body":13,"canonicalUrl":272,"canonicalWebsiteName":273,"category":274,"date":275,"description":276,"extension":277,"featured":278,"fullWidthLayout":278,"image":279,"imageAlt":279,"location":279,"meta":280,"metaImage":279,"navigation":281,"path":282,"seo":283,"stem":284,"venue":279,"venueUrl":279,"__hash__":285},"tools\u002Ftools\u002Fgit-pkgs-explore-your-dependency-history.md","git-pkgs: explore your dependency history",[12],"andrew",{"type":14,"value":15,"toc":270},"minimark",[16,25,42,63,151,154,162,165,171,178,196,199,232,239,253,266],[17,18,19,20,24],"p",{},"Your dependency graph has a history, but it's buried in lockfile diffs that no one reads. GitHub even hides them by default in pull requests. You can ",[21,22,23],"code",{},"git log"," any source file and trace who changed it, when, and why, but try that on a lockfile and you get thousands of lines of noise per commit.",[17,26,27,28,35,36,41],{},"With ",[29,30,34],"a",{"href":31,"rel":32},"https:\u002F\u002Fventurebeat.com\u002Fprogramming-development\u002Fgithub-releases-open-source-report-octoverse-2022-says-97-of-apps-use-oss",[33],"nofollow","97% of applications"," depending on open source, most of your codebase is stuff you didn't write, and someone on your team decided to trust each piece of it. I wanted a way to trace those decisions: who added this package and why? So I built ",[29,37,40],{"href":38,"rel":39},"https:\u002F\u002Fgithub.com\u002Fgit-pkgs\u002Fgit-pkgs",[33],"git-pkgs",", a git subcommand that makes your dependency history searchable.",[17,43,44,45,50,51,56,57,62],{},"It runs entirely offline with no external services, and works across ecosystems (RubyGems, npm, Cargo, Go, PyPI, Docker, GitHub Actions, and ",[29,46,49],{"href":47,"rel":48},"https:\u002F\u002Fgithub.com\u002Fecosyste-ms\u002Fbibliothecary#supported-package-manager-file-formats",[33],"30+ more",") because it builds on ",[29,52,55],{"href":53,"rel":54},"https:\u002F\u002Fgithub.com\u002Fecosyste-ms\u002Fbibliothecary",[33],"bibliothecary",", the manifest parsing library behind ",[29,58,61],{"href":59,"rel":60},"https:\u002F\u002Fecosyste.ms",[33],"ecosyste.ms",".",[64,65,70],"pre",{"className":66,"code":67,"language":68,"meta":69,"style":69},"language-bash shiki shiki-themes github-light github-dark","git pkgs init              # one-time, ~300 commits\u002Fsec\ngit pkgs blame             # who added each dependency\ngit pkgs history rails     # full timeline of a package\ngit pkgs diff --from=v2.0  # what changed since a release\ngit pkgs stats             # overview of your dependency history\n","bash","",[21,71,72,92,105,121,138],{"__ignoreMap":69},[73,74,77,81,85,88],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sScJk","git",[73,82,84],{"class":83},"sZZnC"," pkgs",[73,86,87],{"class":83}," init",[73,89,91],{"class":90},"sJ8bj","              # one-time, ~300 commits\u002Fsec\n",[73,93,95,97,99,102],{"class":75,"line":94},2,[73,96,80],{"class":79},[73,98,84],{"class":83},[73,100,101],{"class":83}," blame",[73,103,104],{"class":90},"             # who added each dependency\n",[73,106,108,110,112,115,118],{"class":75,"line":107},3,[73,109,80],{"class":79},[73,111,84],{"class":83},[73,113,114],{"class":83}," history",[73,116,117],{"class":83}," rails",[73,119,120],{"class":90},"     # full timeline of a package\n",[73,122,124,126,128,131,135],{"class":75,"line":123},4,[73,125,80],{"class":79},[73,127,84],{"class":83},[73,129,130],{"class":83}," diff",[73,132,134],{"class":133},"sj4cs"," --from=v2.0",[73,136,137],{"class":90},"  # what changed since a release\n",[73,139,141,143,145,148],{"class":75,"line":140},5,[73,142,80],{"class":79},[73,144,84],{"class":83},[73,146,147],{"class":83}," stats",[73,149,150],{"class":90},"             # overview of your dependency history\n",[17,152,153],{},"The blame command shows who added each dependency:",[64,155,160],{"className":156,"code":158,"language":159},[157],"language-text","$ git pkgs blame --ecosystem=rubygems\n\nGemfile (rubygems):\n  bootsnap                        Andrew Nesbitt     2018-04-10  7da4369\n  factory_bot                     Lewis Buckley      2017-12-25  f6cceb0\n  omniauth-rails_csrf_protection  dependabot[bot]    2021-11-02  02474ab\n  rails                           Andrew Nesbitt     2016-12-16  e323669\n","text",[21,161,158],{"__ignoreMap":69},[17,163,164],{},"You can see which dependencies were human decisions versus bot updates, and how old each one is. The history command shows every change to a specific package over time:",[64,166,169],{"className":167,"code":168,"language":159},[157],"$ git pkgs history rails\n\n2016-12-16 Added = 5.0.0.1\n  Commit: e323669 Hello World\n  Author: Andrew Nesbitt\n\n2024-11-21 Updated = 7.2.2 -> = 8.0.0\n  Commit: 86a07f4 Upgrade to Rails 8\n  Author: Andrew Nesbitt\n",[21,170,168],{"__ignoreMap":69},[17,172,173,174,177],{},"The diff command compares dependencies between git refs, so you can see what changed between releases or across branches (",[21,175,176],{},"git pkgs diff --from=main --to=feature",") without wading through lockfile noise. And because the full history is indexed, you can search for packages that were dependencies in the past even if they've since been removed.",[17,179,180,181,186,187,190,191,62],{},"I tested on ",[29,182,185],{"href":183,"rel":184},"https:\u002F\u002Fgithub.com\u002Foctobox\u002Foctobox",[33],"Octobox",", a Rails app with 5,191 commits spanning eight years. Indexing the full history took 18 seconds and produced an 8.3 MB database, covering 2,531 commits with dependency changes and 250 dependencies across RubyGems, Docker, and GitHub Actions. I spent a fair amount of time making sure the commands stay fast after indexing too, not just the initial import. The database lives in ",[21,188,189],{},".git\u002Fpkgs.sqlite3"," and stays updated via git hooks, so once you run init you don't have to think about it again. If you want to run your own queries, the ",[29,192,195],{"href":193,"rel":194},"https:\u002F\u002Fgithub.com\u002Fgit-pkgs\u002Fgit-pkgs\u002Fblob\u002Fmain\u002Fdocs\u002Fschema.md",[33],"schema is documented",[17,197,198],{},"Since everything runs locally, you can use it in CI to surface dependency changes in pull requests:",[64,200,204],{"className":201,"code":202,"language":203,"meta":69,"style":69},"language-yaml shiki shiki-themes github-light github-dark","- name: Dependency changes\n  run: git pkgs diff --from=origin\u002Fmain >> $GITHUB_STEP_SUMMARY\n","yaml",[21,205,206,222],{"__ignoreMap":69},[73,207,208,212,216,219],{"class":75,"line":76},[73,209,211],{"class":210},"sVt8B","- ",[73,213,215],{"class":214},"s9eBZ","name",[73,217,218],{"class":210},": ",[73,220,221],{"class":83},"Dependency changes\n",[73,223,224,227,229],{"class":75,"line":94},[73,225,226],{"class":214},"  run",[73,228,218],{"class":210},[73,230,231],{"class":83},"git pkgs diff --from=origin\u002Fmain >> $GITHUB_STEP_SUMMARY\n",[17,233,234,235,238],{},"All commands support ",[21,236,237],{},"--format=json"," for scripting and integration with other tools.",[17,240,241,242,247,248,62],{},"I'm thinking about adding CVE history to see which vulnerabilities affected your dependencies over time, and instant SBOM export from any commit or branch. It's a query tool for your own history, no account required, no data leaves your machine. If you try it on a repo with some history, I'd like to hear what works and what's missing. ",[29,243,246],{"href":244,"rel":245},"https:\u002F\u002Fgithub.com\u002Fandrew\u002Fgit-pkgs\u002Fissues",[33],"Open an issue"," or find me on ",[29,249,252],{"href":250,"rel":251},"https:\u002F\u002Fmastodon.social\u002F@andrewnez",[33],"Mastodon",[254,255,258,259],"div",{"className":256},[257],"page-items","\n    ",[29,260,265],{"className":261,"href":38,"target":264},[262,263],"button","button--arrow","_blank","\n        Go to repository\n    ",[267,268,269],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":69,"searchDepth":94,"depth":94,"links":271},[],"https:\u002F\u002Fnesbitt.io\u002F2026\u002F01\u002F01\u002Fgit-pkgs-explore-your-dependency-history","nesbitt.io","software-supply-chains","2026-01-01","A git subcommand to explore the dependency history of your repositories.","md",false,null,{},true,"\u002Ftools\u002Fgit-pkgs-explore-your-dependency-history",{"title":10,"description":276},"tools\u002Fgit-pkgs-explore-your-dependency-history","srMRmkpL-YpHs7tgsOPol1mI51yWmZneSiHGnGyAsfA",1780596103457]