Think about `gitfs`, which will serve a git repo: gitfs, gitdb/read, gitdb/pack, gitdb/... - mount a git repo, and manipulate term% gitfs [-s service] [-c cachedir] [-m mntpnt] [-d dialstring] path # for example: # gitfs tcp!github.com!git oridb/git9 # gitfs tcp!github.com oridb/git9 # gitfs /n/somewhere/perl.git # in the second form above, ssh, git, https, http, ftp, then 'dumb' service will be tried, in that order. When no service given print 'using https service', or the appropriate service, on stderr. term% lstree /mnt/git /mnt/git/ /ctl # echo dialstring .... > ctl # updates dial string to use, re-doing all of the synthetic tree # echo path .... > ctl # updates path to use, re-doing all of the synthetic tree # echo cachedir .... > ctl # update cachedir to use. cachedir stores # echo fetch > ctl # updates cachedir for dialstring & path # echo fetch branch > ctl # updates cachedir for dialstring & path for path # echo fetch /tcp > ctl # updates cachedir for all connections under /cachedir matching path # echo fetch /tcp master > ctl # updates cachedir for all connections under /cachedir matching /tcp for their master branches # echo dumps 300 >> ctl # make a backup every 5m, creating a hash /dialstring /path /dumps # 'dump' 's'econds /branch # <-- present if -b branch is passed in; remove to fetch/sync more branches /cachedir/ <---- cached content /tcp/github.com/git+ssh/oridb/git9.git /tcp/github.com/https/oridb/git9.git /9p/n/somewhere/perl.git /dump/2020/05/10/path/to/work/basesha1/dumpsha1 /packs /exclude <-- system-wide exclude paths under content/ dirs, (that is, .git/exclude). /log <-- read-only git reflog /pack/ 978e03944f5c581011e6998cd0e9e30000905586/idx /pack /tree/ /00/6457..../ /clone # echo /path/to/work >> clone # 'git branch -b work'; make working dir off of this tree. Sets '/message' to empty # echo /path/to/work other_tree > clone # 'git merge other_tree'; merge other_tree into this one, setting staged area at /path/to/work. If more than one commit between other_tree and the current tree, does a squash commit. If you want to do interactive merges, use git/merge. Sets '/message' to empty. print 'see /path/to/work/conflicts' on stderr if conflicts present. That file contains the paths relative to /path/to/work that require resolution. If any conflicts, you must resolve under /path/to/work. /path/to/work will resolve conflicted paths when doing 'cat /path/to/work/conflicts | sed 'add $_' > ctl'. If no conflicts, then ready to 'echo sync > ctl'. Uses contents of '/sym/' in tree which 'clone' opened from. /date /sha1 /sym/ # present if HEAD of branch(es) /tag/ # present if tag(s) /author /message /content/ /.git/ .... /.gitmodules <- submodules! *after* you gitfs to another mount point using the values from its files, simply 'bind -bc ./path/to/submodule. When syncing, gitfs will be aware. Conjecture: have gitfs -s /.gitsubmodules /file.txt /repo.rb /test.txt /dir1/ .... /sym/ /master/ /clone # echo /path/to/work > clone # 'git branch -b ...' (see above); working path, setting '/message' to empty. if 'echo /path/to/more/work > clone' done without /path/to/work being sync'd, then clones off of committed tree (e.g., 'master'), adding the contents of /path/to/work to /path/to/more/work /ctl # 'echo sync' to save changes. if only author/message changed, then it's 'amend', otherwise it's a new commit based off of existing master's sha1 # cat ctl # returns git's raw tree data # echo man > ctl [2]| cat # return man page # echo revert > ctl to revert all content changes # echo date > ctl [2]| cat # returns date # echo author > ctl [2]| cat # returns author # echo message > ctl [2]| cat returns author # echo committer > ctl [2]| cat returns committer # echo log > ctl [2]| cat returns commit log /sha1 # read-only /sym/ # present if HEAD of branch(es) /tag/ # present if tag(s) /v1.4/ /blah/ /author /message /committer /content/... # do some changes in content/, then echo sync > ctl # error if 'message' is empty or not changed. # child and parent commits are used. /mybranch/ /tag/..... # <--- tags of trees /blob/ref/00/6a07..../ /ctl /content /tag/.... # <--- tags of blobs /clone # cat foo > clone # create a blob, printing its ref on stderr /commit/ref/00/58483..../ <--- read-only, except for ctl file, and /parent/, /child/ hashes, which are just binds to their respective trees /ctl # echo sym .... > ctl # creates lightweight tag /author /committer /message /tag/ # present if tag(s) /log # returns commit log /parent/ hash1... hash2... /child/ hash1... hash2... /tag/ref/00/a4fd44.... /man /tree/ /blob/ /commit/ /v1.0/ /clone # echo /path/to/myblob > clone # staging area for annotated tag /myblob/ctl # echo sync > ctl # save tag. lightweight tag if {tagger,message,{commit,blob,tree}} empty. If one not empty and others are, an error. tagger, message, and {commit,blob,tree} create annotated tag /tagger /message # dircp ../../../{commit,blob,tree}/00/.... ./ # then echo sync > ctl # store, moving this dir itself to be under tree/tag & tag/tree, blob/tag & tag/blob, or commit/tag & tag/commit /ctl /ref /tagger /message /commit/ # EXAMPLE term% echo $home/src/work > /mnt/git/tree/sym/master/ctl term% cd $home/src/work term% lc clone ctl sym/ /tag/ sha1 author message committer content/ term% cat sha1 00495852568493... term% lc sym/ master branch1 branch2 term% cd $home/src/work/content # then do some changes term% diff -u /mnt/git/tree/sym/master/content . # shows diff term% cd .. # cd $home/src/work term% echo ' this is my commit message ' > $home/src/work/message term% rm sym/branch1 term% echo sync > ctl cat sha1 3b15643d..... term% lc sym master branch2 term% cd /mnt/git term% lc ctl dialstring path cachedir/ packs exclude log pack/ tree/ blob/ commit/ tag/ term% echo fetch > ctl # fetch new commits for dialstring & path term% echo fetch tree/sym/master > ctl # fetch only master branch term% echo sync > ctl # push all changes to remote term% rm -r tree/sym/mybranch # set to remove remote branch term% echo fetch > ctl # nevermind! adds back tree/sym/mybranch term% rm -r tree/sym/secondbranch # set to remove remote branch term% echo sync > ctl # removed remote branch If path is a local directory and ends in .git but does not have the structure expected, fail. If path is a local directory and does not end in .git, and has a .git subdirectory but does not have the expected structure, fail. If path is a local directory and does not end in .git, and does not have .git, 'git init' that directory. https://stackoverflow.com/a/18732276 <-- why storing in two directories, not one: Git switches from "loose objects" (in files named like 01/23456789abcdef0123456789abcdef01234567) to "packs" when the number of loose objects exceeds a magic constant (6700 by default but configurable, gc.auto). Since SHA-1 values tend to be well-distributed it can approximate total loose objects by looking in a single directory. If there are more than (6700 + 255) / 256 = 27 files in one of the object directories, it's time for a pack-file. Thus, there's no need for additional fan-out (01/23/4567...): it's unlikely that you will get that many objects in one directory. And in fact, greater fan-out would tend to make it harder to detect that it is time for an automatic packing, unless you set the threshold value higher (than 6700), because (27 + 255) / 256 is 1—so you'd want to count everything in 01/*/ instead of just 01/. One could use 0/1234567... and allow up to ~419 objects per directory to get the same behavior, but linear directory scans (on any system that still uses those) are O(n2), and 272 is a mere 729, while 4192 is 175561. [Edit: that only applies to file creation, where you have a two stage search, once to find that it's OK to create and a second to find a slot or append. Lookups are still O(n).] https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain https://git-scm.com/docs/gitrepository-layout#gitrepository-layout-objectsinfo TODO - how to deal with submodules?