{"id":19918019,"url":"https://github.com/sethfowler/git-remote-subtree","last_synced_at":"2026-06-09T08:31:06.823Z","repository":{"id":145982448,"uuid":"76616803","full_name":"sethfowler/git-remote-subtree","owner":"sethfowler","description":"Treat git subtrees as remotes. A less painful alternative to git submodule.","archived":false,"fork":false,"pushed_at":"2017-01-04T04:42:34.000Z","size":7,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-01T09:45:00.442Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sethfowler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2016-12-16T03:11:44.000Z","updated_at":"2017-01-03T01:14:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"51a9ab06-c609-4a3b-992a-ca56cd91f060","html_url":"https://github.com/sethfowler/git-remote-subtree","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sethfowler/git-remote-subtree","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethfowler%2Fgit-remote-subtree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethfowler%2Fgit-remote-subtree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethfowler%2Fgit-remote-subtree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethfowler%2Fgit-remote-subtree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sethfowler","download_url":"https://codeload.github.com/sethfowler/git-remote-subtree/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethfowler%2Fgit-remote-subtree/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34098787,"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-09T02:00:06.510Z","response_time":63,"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":[],"created_at":"2024-11-12T21:52:09.403Z","updated_at":"2026-06-09T08:31:06.804Z","avatar_url":"https://github.com/sethfowler.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# git-remote-subtree\n\nAn alternative to git submodules and subtrees. Subrepos appear as normal remotes\nwhich differ from your main repository only in the contents of one subdirectory.\n\nThe idea is that hide the functionality of `git-subtree` behind a protocol which\nworks transparently, doesn't pollute the commit history of the repo, and doesn't\ninterfere with normal git functionality. Just as a failing escalator becomes\nstairs, any issue with `git-remote-subtree` should yield a monorepo that\ncontinues to work perfectly, with no loss of data or commits.\n\n# Project status\n\nThere is no working implementation yet. `git-remote-subtree` currently just\nmirrors a remote repository in a hidden bare repo and performs pushes and pulls\nindirectly through it.\n\nThe next step is to add support for rewriting paths and basing the rewritten\ncommits on top of a specific parent commit provided by the user. This will be\nsufficient to allow pulling as long as no new commits are added to the super\nrepo, and because it's deterministic, it won't require scanning the super repo\nfor matching commits.\n\n# Proposed approach\n\nSay the user sets up a remote like so:\n\n```\ngit remote add subRepo subtree::subRepoDir::on::branch::from::http://example.com/normalRepo\n```\n\nThis creates a git remote called `subRepo` which wraps a normal git repo at\n`http://example.com/normalRepo`. The ref `subRepo/branch` contains the same\ncontent as `normalRepo/branch`, but it is as if all of the files in `normalRepo`\nare moved into a single top-level directory `subRepoDir/`, and `subRepoDir` is\ngrafted into the same tree as all the other content at `superRepo/branch`. If\n`subRepoDir` already exists in `superRepo/branch`, the effect is as if its\nexisting contents were replaced by the contents of `normalRepo/subBranch`. If\n`subRepoDir` already exists in `superRepo/branch` and *the contents are exactly\nthe same*, then `subRepo/subBranch` will have the same SHA as\n`superRepo/branch`, and pulling one into the other will be a no-op.\n\nSimilarly, pushing from `superRepo/branch` to `subRepo/subBranch` behaves as if\nthe contents of `subRepoDir` were at the top level, and everything else was\nthrown away. If the contents of `subRepoDir` are the same as\n`normalRepo/subBranch`, then pushing is a no-op.\n\nThis setup means that the functionality of `git-subtree` can be implemented\ntotally by pushing to and pulling from a `subtree::` remote. Because all the\nmagic is inside the remote helper, the main repo remains clean, and all other\ngit functionality works just as you would expect.\n\nThis should be fairly simple to implement while preserving history. A hand-wavy\nalgorithm for fetching is as follows:\n\n- Do a fetch on `normalRepo` and `superRepo` into our hidden repo.\n\n- See if the tree object of the oldest commit in `normalRepo/subBranch` is\n  present in the local repo. If not, we know that we've never merged this branch\n  in before, and we can skip some of the following work.\n\n- Walk backwards in the commit graph from `normalRepo/subBranch` until we find a\n  tree object that's in the local repo. See if one of the associated commits in\n  the local repo is the same in every other respect except for the parent\n  commits and the fact that the tree is rewritten. If so, this is the last\n  common commit. If not, keep walking backwards; if we run out of commits, we've\n  never merged this branch in before, and in the steps below we can just start\n  at the current commit in the local repo.\n\n- Create a temporary branch in our hidden repo pointing at the version of the\n  common commit in our local repo.\n\n- Cherry-pick commits from our local repo until we either hit a commit that\n  modifies the tree object for `subRepoDir` (which we won't cherry-pick), or we\n  run out of commits.\n\n- Cherry-pick all of the commits from `normalRepo/subBranch` onto our temporary\n  branch, with the tree rewritten appropriately. We know they'll apply cleanly\n  because the state of `subRepoDir` in our temporary repo is clean with respect\n  to `normalRepo`.\n\n- The temporary repo contains the data we'll return from the fetch. Repeat as\n  necessary for the other branches.\n\nThis is obviously quite expensive, so in practice we'll want to cache some\ninformation to speed this up.\n\nPushing is a bit simpler; once we find the common commit we just need to\ntransform each commit in our local repo that touches `subRepoDir` into a\ncorresponding commit on `normalRepo/subBranch`.\n\nIt might sound like there's a lot to implement here, but actually\n`git-subhistory` in particular is fairly close to what's needed here, and\ntranslating it into e.g. Python would get us 80% of the way there.\n\n# Resources and related work\n\n[How to Write a New Git Protocol](https://rovaughn.github.io/2015-2-9.html)\n\n[Mastering Git Subtrees](https://medium.com/@porteneuve/mastering-git-subtrees-943d29a798ec#.us0rtft89)\n\n[git-subtree docs](https://raw.githubusercontent.com/git/git/master/contrib/subtree/git-subtree.txt)\n\n[git-subhistory](https://github.com/laughinghan/git-subhistory)\n\n[git-subrepo](https://github.com/ingydotnet/git-subrepo)\n\n[Which commit has this blob?](http://stackoverflow.com/questions/223678/which-commit-has-this-blob)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsethfowler%2Fgit-remote-subtree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsethfowler%2Fgit-remote-subtree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsethfowler%2Fgit-remote-subtree/lists"}