[{"data":1,"prerenderedAt":603},["ShallowReactive",2],{"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns":3,"\u002Freports\u002Fgit-remote-helpers":8},["Island",4],{"key":5,"result":6},"NoscriptNav_XrRK2e2e8meJ0jKVGkb5ULGQDVi3UiFQ9nupAr7Yns",{"head":7},{},{"id":9,"title":10,"authors":11,"body":13,"canonicalUrl":589,"canonicalWebsiteName":590,"category":591,"date":592,"description":593,"extension":594,"featured":595,"fullWidthLayout":595,"image":596,"imageAlt":596,"location":596,"meta":597,"metaImage":596,"navigation":598,"path":599,"seo":600,"stem":601,"venue":596,"venueUrl":596,"__hash__":602},"reports\u002Freports\u002Fgit-remote-helpers.md","Git Remote Helpers",[12],"andrew",{"type":14,"value":15,"toc":576},"minimark",[16,65,82,92,95,101,120,160,186,189,194,197,233,237,285,289,329,333,361,365,397,401,461,465,471,483,487,505,509,562],[17,18,19,26,27,32,33,38,39,43,44,47,48,53,54,59,60,64],"p",{},[20,21,25],"a",{"href":22,"rel":23},"https:\u002F\u002Fbzg.fr\u002F",[24],"nofollow","Bastien Guerry"," from ",[20,28,31],{"href":29,"rel":30},"https:\u002F\u002Fwww.softwareheritage.org\u002F",[24],"Software Heritage"," recently nerd-sniped me with ",[20,34,37],{"href":35,"rel":36},"https:\u002F\u002Fnanodash.knowledgepixels.com\u002Fexplore?id=https:\u002F\u002Fw3id.org\u002Fnp\u002FRAJIQOw50gSAqzKUJoFSbNQghA_b72Y3-ImjTRN4YOF9s&label=Idea:+git+clone+from+SoftWare+Hash+IDentifiers&forward-to-part=true",[24],"an idea"," for a ",[40,41,42],"code",{},"git-remote-swh"," that would let you ",[40,45,46],{},"git clone"," from a ",[20,49,52],{"href":50,"rel":51},"https:\u002F\u002Fwww.swhid.org\u002F",[24],"SWHID",", pulling source code directly from Software Heritage's archive by content hash rather than by URL. Building that means writing a git remote helper, which sent me back to the ",[20,55,58],{"href":56,"rel":57},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgitremote-helpers",[24],"gitremote-helpers docs"," and down the rabbit hole of how many of these things already exist. I covered remote helpers briefly in my earlier post on ",[20,61,63],{"href":62},"\u002Freports\u002Fextending-git-functionality","extending git functionality",", but the protocol deserves a closer look.",[17,66,67,68,70,71,74,75,78,79,81],{},"A ",[40,69,42],{}," would need to be an executable on your ",[40,72,73],{},"$PATH"," so that git invokes it when it sees a URL like ",[40,76,77],{},"swh:\u002F\u002F",". The helper and git talk over stdin\u002Fstdout using a text-based line protocol. For ",[40,80,42],{}," the end goal would be something like:",[83,84,89],"pre",{"className":85,"code":87,"language":88},[86],"language-text","git clone swh:\u002F\u002Fswh:1:rev:676fe44740a14c4f0e09ef4a6dc335864e1727ca;origin=https:\u002F\u002Fgithub.com\u002Fwikimedia\u002Fmediawiki\n","text",[40,90,87],{"__ignoreMap":91},"",[17,93,94],{},"Or using the double-colon form, which reads a bit cleaner when adding a remote:",[83,96,99],{"className":97,"code":98,"language":88},[86],"git remote add archive swh::swh:1:rev:676fe44740a14c4f0e09ef4a6dc335864e1727ca;origin=https:\u002F\u002Fgithub.com\u002Fwikimedia\u002Fmediawiki\n",[40,100,98],{"__ignoreMap":91},[17,102,103,104,107,108,110,111,116,117,119],{},"The SWHID identifies a specific revision by content hash, and the ",[40,105,106],{},"origin"," qualifier tells the helper where to fall back if that revision isn't in the archive yet. The helper would resolve the SWHID against Software Heritage's archive, and if the revision isn't archived yet, use the ",[40,109,106],{}," qualifier to ask Software Heritage to ",[20,112,115],{"href":113,"rel":114},"https:\u002F\u002Farchive.softwareheritage.org\u002Fsave\u002F",[24],"import it"," first, so the clone always comes through the archive and can be verified against the content hash. You'd end up with ",[40,118,46],{}," as a content-addressed fetch primitive rather than just a URL fetch, which is an interesting building block for reproducible builds and supply chain verification.",[17,121,122,123,126,127,130,131,130,134,130,137,130,140,143,144,146,147,150,151,153,154,156,157,159],{},"Git opens by sending ",[40,124,125],{},"capabilities"," and the helper responds with what it can do: ",[40,128,129],{},"fetch",", ",[40,132,133],{},"push",[40,135,136],{},"import",[40,138,139],{},"export",[40,141,142],{},"connect",", or some combination. A SWHID helper would only need ",[40,145,136],{}," and ",[40,148,149],{},"list"," since Software Heritage is a read-only archive and its API returns objects individually rather than as packfiles. ",[40,152,136],{}," lets the helper pull snapshots, revisions, trees, and blobs via the REST API and stream them into git's fast-import format, which is easier to implement than ",[40,155,129],{}," where you'd have to reconstruct packfiles yourself for not much gain on a read-only helper. ",[40,158,142],{}," establishes a bidirectional pipe where git speaks its native pack protocol as if it were talking to a real git server, but that only makes sense when the remote actually speaks git's wire protocol.",[17,161,162,163,165,166,168,169,174,175,177,178,181,182,185],{},"After capability negotiation, git sends ",[40,164,149],{}," to get the remote's refs, then issues import commands in batches. For a SWHID helper, ",[40,167,149],{}," would resolve the SWHID against Software Heritage's ",[20,170,173],{"href":171,"rel":172},"https:\u002F\u002Farchive.softwareheritage.org\u002Fapi\u002F",[24],"API",", translate the archive's snapshot into a ref listing, and then ",[40,176,136],{}," would stream the objects through as fast-import data. Each batch ends with a blank line, and the helper responds with status lines like ",[40,179,180],{},"ok refs\u002Fheads\u002Fmain"," or ",[40,183,184],{},"error refs\u002Fheads\u002Fmain \u003Creason>",".",[17,187,188],{},"Writing a remote helper from scratch is more work than writing a git subcommand but less work than building a full git server. Most implementations are a few hundred to a few thousand lines of code, and the hardest part is mapping git's object model onto whatever storage backend you're targeting. Software Heritage already stores git objects natively, so a SWHID helper might be one of the easier ones to build.",[190,191,193],"h3",{"id":192},"built-in","Built-in",[17,195,196],{},"Git ships with remote helpers for its standard network transports, and they follow the same protocol as everything else below.",[198,199,200,218,227],"ul",{},[201,202,203,207,208,211,212,217],"li",{},[204,205,206],"strong",{},"git-remote-http"," \u002F ",[204,209,210],{},"git-remote-https"," implement the ",[20,213,216],{"href":214,"rel":215},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fhttp-protocol",[24],"smart HTTP protocol"," that most hosted git services use",[201,219,220,207,223,226],{},[204,221,222],{},"git-remote-ftp",[204,224,225],{},"git-remote-ftps"," fetch over FTP, though this is rarely used in practice",[201,228,229,232],{},[204,230,231],{},"git-remote-ext"," pipes git's protocol through an arbitrary command, which makes it a building block for custom transports without writing a full remote helper",[190,234,236],{"id":235},"cloud-and-object-storage","Cloud and object storage",[198,238,239,249,259,269],{},[201,240,241,248],{},[20,242,245],{"href":243,"rel":244},"https:\u002F\u002Fgithub.com\u002Fanishathalye\u002Fgit-remote-dropbox",[24],[204,246,247],{},"git-remote-dropbox"," stores git repos in Dropbox using the Dropbox API, and is one of the better documented remote helpers if you're looking for implementation examples.",[201,250,251,258],{},[20,252,255],{"href":253,"rel":254},"https:\u002F\u002Fgithub.com\u002Fawslabs\u002Fgit-remote-s3",[24],[204,256,257],{},"git-remote-s3"," from AWS Labs uses S3 as a serverless git server with LFS support. Written in Rust. There are several other S3-backed helpers floating around but this is the most complete.",[201,260,261,268],{},[20,262,265],{"href":263,"rel":264},"https:\u002F\u002Fgithub.com\u002Faws\u002Fgit-remote-codecommit",[24],[204,266,267],{},"git-remote-codecommit"," provides authenticated access to AWS CodeCommit repositories without needing to configure SSH keys or manage HTTPS credentials manually.",[201,270,271,278,279,284],{},[20,272,275],{"href":273,"rel":274},"https:\u002F\u002Fgithub.com\u002Fdatalad\u002Fgit-remote-rclone",[24],[204,276,277],{},"git-remote-rclone"," pushes and fetches through ",[20,280,283],{"href":281,"rel":282},"https:\u002F\u002Frclone.org\u002F",[24],"rclone",", so it gets rclone's 70+ cloud storage providers for free: Google Drive, Azure Blob Storage, Backblaze B2, and the rest.",[190,286,288],{"id":287},"encryption","Encryption",[198,290,291,301,311],{},[201,292,293,300],{},[20,294,297],{"href":295,"rel":296},"https:\u002F\u002Fgithub.com\u002Fspwhitton\u002Fgit-remote-gcrypt",[24],[204,298,299],{},"git-remote-gcrypt"," encrypts an entire git repository with GPG before pushing it to any standard git remote. The remote stores only encrypted data, so you can use an untrusted host as a private git server with multiple participants sharing access through GPG's key infrastructure.",[201,302,303,310],{},[20,304,307],{"href":305,"rel":306},"https:\u002F\u002Fgithub.com\u002FGenerousLabs\u002Fgit-remote-encrypted",[24],[204,308,309],{},"git-remote-encrypted"," takes a different approach where each git object is individually encrypted before being stored as a file in a separate git repository. The remote looks like a normal git repo full of encrypted blobs.",[201,312,313,316,317,322,323,328],{},[204,314,315],{},"git-remote-keybase"," was part of the ",[20,318,321],{"href":319,"rel":320},"https:\u002F\u002Fgithub.com\u002Fkeybase\u002Fclient",[24],"Keybase client"," and stored encrypted git repos on Keybase's infrastructure using the Keybase identity and key management system. Keybase was ",[20,324,327],{"href":325,"rel":326},"https:\u002F\u002Fkeybase.io\u002Fblog\u002Fkeybase-joins-zoom",[24],"acquired by Zoom in 2020"," and the service has been winding down since.",[190,330,332],{"id":331},"content-addressed-storage","Content-addressed storage",[198,334,335],{},[201,336,337,344,345,130,350,130,355,360],{},[20,338,341],{"href":339,"rel":340},"https:\u002F\u002Fgithub.com\u002Fcryptix\u002Fgit-remote-ipfs",[24],[204,342,343],{},"git-remote-ipfs"," maps git objects onto IPFS, storing repositories in a content-addressed merkle DAG. Written in Go using the IPFS API. Several other IPFS-based remote helpers exist (",[20,346,349],{"href":347,"rel":348},"https:\u002F\u002Fgithub.com\u002Fdhappy\u002Fgit-remote-ipfs",[24],"dhappy\u002Fgit-remote-ipfs",[20,351,354],{"href":352,"rel":353},"https:\u002F\u002Fgithub.com\u002Fipfs-shipyard\u002Fgit-remote-ipld",[24],"git-remote-ipld",[20,356,359],{"href":357,"rel":358},"https:\u002F\u002Fgithub.com\u002FElettraSciComp\u002FGit-IPFS-Remote-Bridge",[24],"Git-IPFS-Remote-Bridge",") taking slightly different approaches to the same problem.",[190,362,364],{"id":363},"vcs-bridges","VCS bridges",[198,366,367,377,387],{},[201,368,369,376],{},[20,370,373],{"href":371,"rel":372},"https:\u002F\u002Fgithub.com\u002Ffelipec\u002Fgit-remote-hg",[24],[204,374,375],{},"git-remote-hg"," lets you clone and push to Mercurial repositories transparently using git commands, converting between the two object models on the fly using the fast-import\u002Ffast-export capabilities.",[201,378,379,386],{},[20,380,383],{"href":381,"rel":382},"https:\u002F\u002Fgithub.com\u002Ffelipec\u002Fgit-remote-bzr",[24],[204,384,385],{},"git-remote-bzr"," does the same for Bazaar repositories, also by Felipe Contreras.",[201,388,389,396],{},[20,390,393],{"href":391,"rel":392},"https:\u002F\u002Fgithub.com\u002FGit-Mediawiki\u002FGit-Mediawiki",[24],[204,394,395],{},"git-remote-mediawiki"," treats a MediaWiki instance as a git remote where each wiki page becomes a file. You can clone a wiki, edit pages locally with your text editor, and push changes back. Written in Perl.",[190,398,400],{"id":399},"p2p-and-decentralised","P2P and decentralised",[198,402,403,413,429,445],{},[201,404,405,412],{},[20,406,409],{"href":407,"rel":408},"https:\u002F\u002Fgithub.com\u002Fcjb\u002FGitTorrent",[24],[204,410,411],{},"git-remote-gittorrent"," distributed git over BitTorrent, using a DHT for peer discovery and Bitcoin's blockchain for user identity. A research prototype from 2015 that demonstrated the concept but never saw wide adoption.",[201,414,415,422,423,428],{},[20,416,419],{"href":417,"rel":418},"https:\u002F\u002Fgithub.com\u002Fgugabfigueiredo\u002Fgit-remote-nostr",[24],[204,420,421],{},"git-remote-nostr"," publishes git objects as ",[20,424,427],{"href":425,"rel":426},"https:\u002F\u002Fnostr.com\u002F",[24],"Nostr"," events, using the relay network for distribution.",[201,430,431,438,439,444],{},[20,432,435],{"href":433,"rel":434},"https:\u002F\u002Fgithub.com\u002Flez\u002Fgit-remote-blossom",[24],[204,436,437],{},"git-remote-blossom"," builds on the ",[20,440,443],{"href":441,"rel":442},"https:\u002F\u002Fgithub.com\u002Fhzrd149\u002Fblossom",[24],"Blossom protocol",", a Nostr-adjacent system for content-addressed blob storage.",[201,446,447,454,455,460],{},[20,448,451],{"href":449,"rel":450},"https:\u002F\u002Fgithub.com\u002Fclehner\u002Fgit-remote-ssb",[24],[204,452,453],{},"git-remote-ssb"," stored repositories on ",[20,456,459],{"href":457,"rel":458},"https:\u002F\u002Fscuttlebutt.nz\u002F",[24],"Secure Scuttlebutt",", a gossip-based peer-to-peer protocol where data replicates through social connections rather than central servers. Dormant since the SSB ecosystem contracted.",[190,462,464],{"id":463},"transport-wrappers","Transport wrappers",[17,466,467,468,470],{},"These don't provide their own storage or collaboration model, they wrap existing git remotes with a different transport layer, closer in spirit to the built-in ",[40,469,231],{}," than to the storage-backed helpers above.",[198,472,473],{},[201,474,475,482],{},[20,476,479],{"href":477,"rel":478},"https:\u002F\u002Fgithub.com\u002Fagentofuser\u002Fgit-remote-tor",[24],[204,480,481],{},"git-remote-tor"," routes git traffic through Tor hidden services, written in Rust.",[190,484,486],{"id":485},"blockchain","Blockchain",[198,488,489],{},[201,490,491,498,499,504],{},[20,492,495],{"href":493,"rel":494},"https:\u002F\u002Fgithub.com\u002Fharry-hov\u002Fgit-remote-gitopia",[24],[204,496,497],{},"git-remote-gitopia"," pushes repositories to ",[20,500,503],{"href":501,"rel":502},"https:\u002F\u002Fgitopia.com\u002F",[24],"Gitopia",", a code collaboration platform built on the Cosmos blockchain where repository metadata and access control live on-chain.",[190,506,508],{"id":507},"other-storage-backends","Other storage backends",[198,510,511,526,542,552],{},[201,512,513,520,521,185],{},[20,514,517],{"href":515,"rel":516},"https:\u002F\u002Fgithub.com\u002Fchrislloyd\u002Fgit-remote-sqlite",[24],[204,518,519],{},"git-remote-sqlite"," stores git objects as rows in a SQLite database, which can then be replicated using tools like ",[20,522,525],{"href":523,"rel":524},"https:\u002F\u002Flitestream.io\u002F",[24],"Litestream",[201,527,528,535,536,541],{},[20,529,532],{"href":530,"rel":531},"https:\u002F\u002Fgithub.com\u002FCGamesPlay\u002Fgit-remote-restic",[24],[204,533,534],{},"git-remote-restic"," bridges git and ",[20,537,540],{"href":538,"rel":539},"https:\u002F\u002Frestic.net\u002F",[24],"restic"," backup repositories, inheriting restic's encryption and support for dozens of storage backends.",[201,543,544,551],{},[20,545,548],{"href":546,"rel":547},"https:\u002F\u002Fgithub.com\u002Fperitus\u002Fgit-remote-couch",[24],[204,549,550],{},"git-remote-couch"," stores git repos in CouchDB, gaining CouchDB's replication and conflict resolution for free.",[201,553,554,561],{},[20,555,558],{"href":556,"rel":557},"https:\u002F\u002Fgithub.com\u002Frovaughn\u002Fgit-remote-grave",[24],[204,559,560],{},"git-remote-grave"," pushes repositories into a content-addressable store that deduplicates across multiple repos.",[17,563,564,565,570,571,185],{},"If I've missed one, reach out on ",[20,566,569],{"href":567,"rel":568},"https:\u002F\u002Fmastodon.social\u002F@andrewnez",[24],"Mastodon"," or submit a pull request on ",[20,572,575],{"href":573,"rel":574},"https:\u002F\u002Fgithub.com\u002Fandrew\u002Fnesbitt.io\u002Fblob\u002Fmaster\u002F_posts\u002F2026-03-18-git-remote-helpers.md",[24],"GitHub",{"title":91,"searchDepth":577,"depth":577,"links":578},2,[579,581,582,583,584,585,586,587,588],{"id":192,"depth":580,"text":193},3,{"id":235,"depth":580,"text":236},{"id":287,"depth":580,"text":288},{"id":331,"depth":580,"text":332},{"id":363,"depth":580,"text":364},{"id":399,"depth":580,"text":400},{"id":463,"depth":580,"text":464},{"id":485,"depth":580,"text":486},{"id":507,"depth":580,"text":508},"https:\u002F\u002Fnesbitt.io\u002F2026\u002F03\u002F18\u002Fgit-remote-helpers","nesbitt.io","tooling","2026-03-18","Git can talk to anything if you write the right helper.","md",false,null,{},true,"\u002Freports\u002Fgit-remote-helpers",{"title":10,"description":593},"reports\u002Fgit-remote-helpers","KW9Z87mXgC38LBQFgnmotXfMCsynORX4NkWlp6VcqzM",1780596103395]