git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* A design for subrepositories
@ 2012-10-13 13:33 Lauri Alanko
  2012-10-13 17:30 ` Junio C Hamano
  2012-10-13 21:20 ` perryh
  0 siblings, 2 replies; 17+ messages in thread
From: Lauri Alanko @ 2012-10-13 13:33 UTC (permalink / raw)
  To: git

Hello.

I intend to work on a "subrepository" tool for git, but before I  
embark on the actual programming, I thought to first invite comments  
on the general design.

Some background first. I know that there are several existing  
approaches already for managing nested repositories, but none of them  
quite seems to fit my purposes. My primary goal is to use git for home  
directory backup and mirroring, while the home directory itself may of  
course contain repositories.

Git-subtree doesn't quite fit the bill. It allows merging a subtree  
into a larger tree and then again splitting it out for exporting, but  
this is tedious. More importantly, a merged tree gets branched along  
with the containing tree, whereas I want to have subrepositories  
precisely because the subtrees need to be branched independently of  
the container.

Submodules are a bit closer to what I want, but they have clearly been  
designed for a different purpose: a repository with submodules is only  
supposed to collate existing repositories, not act as a source for  
them. So they aren't really faithful to the distributed nature of git:  
there's no easy way to completely clone a repository and its submodules.

Moreover, submodules have some other annoyances like not supporting  
bare repositories and checking out the submodules in detached heads.

Now, in other circumstances I might just patch git-submodule to add  
the features I want, but it turns out that it is written in shell. I  
know that is a git tradition, but I'm going to get a bit religious  
here: anything longer than a screenful shouldn't be written in shell,  
and I'm certainly not going to add more lines to an already overlong  
script. Hence I'm going to write a separate tool using something a bit  
more... structured. Probably Python with Dulwich.

So here are some preliminary thoughts on how the tool should work.


* Repository layout

Every subrepository has a unique identifier. The heads of  
subrepository <subname> are simply stored as heads in a subdirectory  
of the main repository: e.g.  
refs/heads/subrepos/<subname>/<branchname>. Likewise for tags:  
refs/tags/subrepos/<subname>/<tagname>.

Rationale: if we had fully independent repositories under the main  
repository directory, like what git-submodule uses, there would be no  
easy way to enumerate all the existing subrepositories to copy them.  
Since the only thing we can directly list from a remote repository are  
references, it makes sense to store the subrepositories just as a  
bunch of them.

The reason for storing the subrepo references under refs/heads/ and  
refs/tags/ (instead of, say, refs/subrepos/) is simply that this way  
everything is directly compatible with standard git tools: one can do  
a normal git clone/push/pull for mirroring and backup purposes without  
any need for special tools. You only need tools once you operate on a  
working tree.


* Tree layout

A tree can mount references of subrepositories. There are two  
components to a mount: a gitlink under <path> to a particular commit  
of a subrepo, and an entry in .gitrepos. This is very similar to how  
git-submodule works.

The entry in .gitrepos specifies two things: the name of the  
subrepository mounted under <path>, and the active branch in that  
mount at the time of commit. So .gitrepos would look like this:

[mount "<path>"]
    subrepo = <subname>
    branch = <branchname>

Rationale: by storing the active branch name we can cater for the very  
common case where we check out a gitlink pointing to the current head  
of the branch. Then, when we check out the subrepository at the mount  
point, we can adjust HEAD to point to the correct branch.

By associating from a path to a subrepository (instead of the other  
way, as git-submodule does), we can have multiple mount points for the  
same subrepository, presumably with different active branches.  
Sometimes we want to have separate working trees for various branches,  
and it's good to be able to store this configuration in the containing  
tree.


* Working tree layout

When a tree containing mount points is checked out, a repository is  
created at each of those mount points. For every <path> specified in  
.gitrepos with subrepo <subname> and active branch <branchname>, and a  
gitlink in <path> pointing to <commit>, we do the following:

- Create a repository under <path>/.git

- Add the object store of the containing repository to  
<path>/.git/objects/info/alternates

- Pull (just copy, really) the containing repository's references to  
the subrepository as follows:

  - refs/heads/subrepos/<subname>/* -> refs/heads/*
  - refs/tags/subrepos/<subname>/* -> refs/tags/*
  - refs/remotes/<remote>/subrepos/<subname>/* -> refs/remotes/<remote>/*

- If now in the subrepository refs/heads/<branchname> points to  
<commit>, set HEAD as a symref to it. Otherwise set a detached HEAD  
directly to <commit>.

- Check out HEAD in the subrepository.

Rationale: it was a tempting idea to make refs/heads and refs/tags to  
be symlinks directly to the correct subdirectories in the containing  
repository, and likewise make objects/ directly a symlink to the  
containing repository's object store. However, this is not really  
feasible due to packed-refs, and it would make symlinks a requirement,  
something that git tries to avoid. (Of course "directory symrefs"  
would be a simple addition to the core.)

More importantly, a symlink to the object store would break git-gc.  
Also, it would be ugly to have ref manipulations under the mount point  
directly affect the refs in the containing repository. It's better  
that none of the changes under the mount point affect the containing  
repository in any way before an explicit add and check-in. At this  
point the refs are pulled back in the reverse direction.


* Basic commands


** git subrepo add <path> [<subname>]

Add a subrepository to the containing repository, or add the changes  
in a subrepository to the index.

If <path> is not yet found in .gitrepos, <subname> must be specified.  
Otherwise <subname> is looked up from .gitrepos.

The command performs the following:

- Add or update the gitlink to the index: git add <path>
- Add or change an entry in .gitmodules, setting mount.<path>.subname  
to <subname> and mount.<path>.branch to the active branch under <path>  
(if any).
- git add .gitmodules


** git subrepo checkin [-f] [<path>...]

Update the subrepo references in the containing repository to the  
references in the mount points. This is meant to be run as a  
pre-commit hook with no arguments.

If no paths are given, <path>... defaults to every mount path in  
.gitrepos that has been changed in the index. For each <path> mounting  
<subname>, perform the following:

- git fetch [-f] <path> refs/heads/*:refs/heads/subrepos/<subname>/
- git fetch [-f] <path> refs/tags/*:refs/tags/subrepos/<subname>/

If [-f] is given, it is passed to git fetch.

The operation can fail in the unlikely case that there are multiple  
mount points for the same subrepository, and a branch has diverged  
between those mount points.

Note: after this operation, any new objects that were added under the  
mount point are now duplicated in the containing repository. A git gc  
in the containing repository followed by a git gc in the mount point  
should remove the now-redundant objects from the mount point.

Note: the default paths overlook the spurious case where have modified  
the head of a non-active branch under the mount point, but the active  
branch (and hence the commit in the gitlink) have remained unchanged.  
I don't know if there's a reasonable way to make "git subrepo add"  
somehow stage even these kinds of changes.


** git subrepo checkout [<path>...]

Check out the subrepositories at mount points <path>..., or at all the  
mount points if none are specified. This is meant to be run as a  
post-checkout hook with no arguments.

This is described above in "Working tree layout". If this is not an  
initial checkout, then the first two steps are skipped and just the  
refs and working tree are updated.


** git subrepo mv <path> <path>

Move a mount point: git mv the actual directory and adjust the path in  
.gitrepos and possibly the relative path in  
<path>/.git/objects/info/alternates. (An absolute path would fix the  
latter, but then we couldn't move the entire containing repository.  
This is the lesser evil, IMHO.)

Gripe: why doesn't git support arbitrary metadata for tree entries?  
Then we wouldn't need to worry about syncing various path attributes  
that are stored in separate files, but a simple git mv could  
automatically move everything associated with the path.


** git subrepo rm <path>

Remove the mount point and its entry in .gitrepos.


* A variant design

The above design is straightforward to implement, but it has a bit of  
an ad-hoc feel in that we have these magic commands that transfer refs  
between the containing repository and the mount points. But there are  
already standard tools for transferring refs: push and pull/fetch. It  
would be more "git-like" to use these directly, and make the  
containing repository be simply a remote for the mount point. We need  
a special remote for this purpose: git-remote-subrepo gives a "view"  
of the refs of a particular subrepo within the ref tree of the  
containing repository. It just makes the following translations for  
push and fetch:

subrepo://<URL>/<subname> refs/heads/<branchname>
-> <URL> heads/subrepos/<subname>/<branchname>

subrepo://<URL>/<subname> refs/tags/<tagname>
-> <URL> tags/subrepos/<subname>/<tagname>

subrepo://<URL>/<subname>/<remote> refs/heads/<branchname> ->
-> <URL> remotes/<remote>/heads/subrepos/<subname>/<branchname>

subrepo://<URL>/<subname>/<remote> refs/heads/<branchname> ->
-> <URL> remotes/<remote>/heads/subrepos/<subname>/<branchname>

Then subrepo://<containingrepo>/<subname> is set as the origin in the  
mount point, so one can just do a normal git push to push the changes  
to the containing repository. Likewise, for all the remotes in the  
containing repository, a remote with the same name is created under  
the mount point with the url  
subrepo://<containingrepo>/<subname>/<remote>. Or it can be set to  
directly access the actual remote:  
subrepo://<url-of-remote>/<subname>. It's a matter of taste.

The problem with explicit pushing to the containing repository is that  
then changes to the refs happen completely independently of changes to  
the gitlinks, and ideally these should be synchronized in a single  
commit. So I'm not quite sure if the additional complexity of a remote  
helper is warranted.


I hope I managed to make some sense of what this is about. Questions  
and comments are appreciated.

Cheers,


Lauri

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2012-10-19 20:09 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-10-13 13:33 A design for subrepositories Lauri Alanko
2012-10-13 17:30 ` Junio C Hamano
2012-10-13 21:23   ` Lauri Alanko
2012-10-14  4:36     ` Junio C Hamano
2012-10-14 10:19       ` Lauri Alanko
2012-10-14 13:28         ` Jens Lehmann
2012-10-14 15:27           ` Lauri Alanko
2012-10-14 16:10             ` Jens Lehmann
2012-10-14 16:15             ` Jens Lehmann
2012-10-14 16:25             ` Jens Lehmann
2012-10-14 18:04               ` Junio C Hamano
2012-10-14 19:32                 ` Jens Lehmann
2012-10-19  0:31                 ` A design for distributed submodules Lauri Alanko
2012-10-19 20:09                   ` Jens Lehmann
2012-10-14 22:59               ` A design for subrepositories Lauri Alanko
2012-10-15 17:10                 ` Jens Lehmann
2012-10-13 21:20 ` perryh

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).