Remove old subproject design notes.

Junio C Hamano committed Mar 4, 2007 at 22:56 UTC 71b785f8e26191ff928988d245d4b22e89118ba1
2 files changed -493
Makefile deleted
-11
@@ -1,11 +0,0 @@
1 -all:
2 -
3 -clean:
4 - rm -f Subpro.html
5 -
6 -
7 -all: Subpro.html
8 -
9 -%.html: %.txt
10 - asciidoc -bxhtml11 $*.txt
11 -
Subpro.txt deleted
-482
@@ -1,482 +0,0 @@
1 -Notes on Subproject Support
2 -===========================
3 -Junio C Hamano
4 -
5 -Scenario
6 ---------
7 -
8 -The examples in the following discussion show how this proposal
9 -plans to help this:
10 -
11 -. A project to build an embedded Linux appliance "gadget" is
12 - maintained with git.
13 -
14 -. The project uses linux-2.6 kernel as its subcomponent. It
15 - starts from a particular version of the mainline kernel, but
16 - adds its own code and build infrastructure to fit the
17 - appliance's needs.
18 -
19 -. The working tree of the project is laid out this way:
20 -+
21 -------------
22 - Makefile - Builds the whole thing.
23 - linux-2.6/ - The kernel, perhaps modified for the project.
24 - appliance/ - Applications that run on the appliance, and
25 - other bits.
26 -------------
27 -
28 -. The project is willing to maintain its own changes out of tree
29 - of the Linux kernel project, but would want to be able to feed
30 - the changes upstream, and incorporate upstream changes to its
31 - own tree, taking advantage of the fact that both itself and
32 - the Linux kernel project are version controlled with git.
33 -
34 -. To make the story a bit more interesting, later in the history
35 - of development, `linux-2.6/` and `appliance/` directories will
36 - be renamed to `kernel/` and `gadget/`.
37 -
38 -The idea here is to:
39 -
40 -. Keep `linux-2.6/` part as an independent project. The work by
41 - the project on the kernel part can be naturally exchanged with
42 - the other kernel developers this way. Specifically, a tree
43 - object contained in commit objects belonging to this sub-project
44 - does *not* have `linux-2.6/` directory at the top.
45 -
46 -. Keep the `appliance/` part as another independent project.
47 - Applications are supposed to be more or less independent from
48 - the kernel version, but some other bits might be tied to a
49 - specific kernel version. Again, a tree object contained in
50 - commit objects belonging to this sub-project does *not* have
51 - `appliance/` directory at the top.
52 -
53 -. Have another project that combines the whole thing together,
54 - so that the project can keep track of which versions of the
55 - parts are built together. The Makefile is illustrated above,
56 - but there might be other files and directories.
57 -
58 -We will call the project that binds things together the
59 -'toplevel project'. Other projects that hold `linux-2.6/` part
60 -and `appliance/` part are called 'subprojects'.
61 -
62 -
63 -Setting up
64 -----------
65 -
66 -Let's say we have been working on the appliance software,
67 -independently version controlled with git. Also the kernel part
68 -has been version controlled separately, like this:
69 -------------
70 -$ ls -dF current/*/.git current/*
71 -current/Makefile current/appliance/.git/ current/linux-2.6/.git/
72 -current/appliance/ current/linux-2.6/
73 -------------
74 -
75 -Now we would want to get a combined project. First we would
76 -clone from these repositories (which is not strictly needed --
77 -we could use `$GIT_ALTERNATE_OBJECT_DIRECTORIES` instead):
78 -
79 -------------
80 -$ mkdir combined && cd combined
81 -$ cp ../current/Makefile .
82 -$ git init-db
83 -$ mkdir -p .git/refs/subs/{kernel,gadget}/{heads,tags}
84 -$ git clone-pack ../current/linux-2.6/ master | read kernel_commit junk
85 -$ git clone-pack ../current/appliance/ master | read gadget_commit junk
86 -------------
87 -
88 -We will introduce a new command to set up a combined project:
89 -
90 -------------
91 -$ git bind-projects \
92 - $kernel_commit linux-2.6/ \
93 - $gadget_commit appliance/
94 -------------
95 -
96 -This would probably do an equivalent of:
97 -
98 -------------
99 -$ rm -f "$GIT_DIR/index"
100 -$ git read-tree --prefix=linux-2.6/ $kernel_commit
101 -$ git read-tree --prefix=appliance/ $gadget_commit
102 -$ git update-index --bind linux-2.6/ $kernel_commit
103 -$ git update-index --bind appliance/ $gadget_commit
104 -------------
105 -[NOTE]
106 -============
107 -Earlier outlines sent to the git mailing list talked
108 -about `$GIT_DIR/bind` to record what subproject are bound to
109 -which subtree in the current working tree and index. This
110 -proposal instead records that information in the index file
111 -with `update-index --bind` command.
112 -
113 -Also note that in this round of proposal, there is no separate
114 -branches that keep track of heads of subprojects.
115 -
116 -`update-index --bind` is not implemented on the core side yet;
117 -it would involve backward incompatible changes to the index
118 -format.
119 -============
120 -
121 -Let's not forget to add the `Makefile`, and check the whole
122 -thing out from the index file.
123 -------------
124 -$ git add Makefile
125 -$ git checkout-index -f -u -q -a
126 -------------
127 -
128 -Now our directory should be identical with the `current`
129 -directory. After making sure of that, we should be able to
130 -commit the whole thing:
131 -
132 -------------
133 -$ diff -x .git -r ../current ../combined
134 -$ git commit -m 'Initial toplevel project commit'
135 -------------
136 -
137 -Which should create a new commit object that records what is in
138 -the index file as its tree, with `bind` lines to record which
139 -subproject commit objects are bound at what subdirectory, and
140 -updates the `$GIT_DIR/refs/heads/master`. Such a commit object
141 -might look like this:
142 -------------
143 -tree 04803b09c300c8325258ccf2744115acc4c57067
144 -bind 5b2bcc7b2d546c636f79490655b3347acc91d17f linux-2.6/
145 -bind 0bdd79af62e8621359af08f0afca0ce977348ac7 appliance/
146 -author Junio C Hamano <junio@kernel.org> 1137965565 -0800
147 -committer Junio C Hamano <junio@kernel.org> 1137965565 -0800
148 -
149 -Initial toplevel project commit
150 -------------
151 -
152 -Notice that `Makefile` at the top is part of the toplevel
153 -project in this example, but it is not necessary. We could
154 -instead have the appliance subproject include this file. In
155 -such a setup, the appliance subproject would have had `Makefile`
156 -and `appliance/` directory at the toplevel. The `bind` line for
157 -that project would have said "the rest is bound at `/`" and
158 -`write-tree \--exclude=linux-2.6/` would have been used to write
159 -the tree for that subproject out of the combined index.
160 -
161 -
162 -Making further commits
163 -----------------------
164 -
165 -The easiest case is when you updated the Makefile without
166 -changing anything in the subprojects. In such a case, we just
167 -need to create a new commmit object that records the new tree
168 -with the current `HEAD` as its parent, and with the same set of
169 -`bind` lines.
170 -
171 -When we have changes to the subproject part, we would make a
172 -separate commit to the subproject part and then record the whole
173 -thing by making a commit to the toplevel project. The user
174 -interaction might go this way:
175 -------------
176 -$ git commit
177 -error: you have changes to the subproject bound at linux-2.6/.
178 -$ git commit --subproject linux-2.6/
179 -$ git commit
180 -------------
181 -
182 -With the new `\--subproject` option, the directory structure
183 -rooted at `linux-2.6/` part is written out as a tree, and a new
184 -commit object that records that tree object with the commit
185 -bound to that portion of the tree (`5b2bcc7b` in the above
186 -example) as its parent is created. Then the final `git commit`
187 -would record the whole tree with updated `bind` line for the
188 -`linux-2.6/` part.
189 -
190 -
191 -Checking out
192 -------------
193 -
194 -After cloning such a toplevel project, `git clone` without `-n`
195 -option would check out the working tree. This is done by
196 -reading the tree object recorded in the commit object (which
197 -records the whole thing), and adding the information from the
198 -"bind" line to the index file.
199 -
200 -------------
201 -$ cd ..
202 -$ git clone -n combined cloned ;# clone the one we created earlier
203 -$ cd cloned
204 -$ git checkout
205 -------------
206 -
207 -This round of proposal does not maintain separate branch heads
208 -for subprojects. The bound commits and their subdirectories
209 -are recorded in the index file from the commit object, so there
210 -is no need to do anything other than updating the index and the
211 -working tree.
212 -
213 -
214 -Switching branches
215 -------------------
216 -
217 -Along with the traditional two-way merge by `read-tree -m -u`,
218 -we would need to look at:
219 -
220 -. `bind` lines in the current `HEAD` commit.
221 -
222 -. `bind` lines in the commit we are switching to.
223 -
224 -. subproject binding information in the index file.
225 -
226 -to make sure we do sensible things.
227 -
228 -Just like until very recently we did not allow switching
229 -branches when two-way merge would lose local changes, we can
230 -start by refusing to switch branches when the subprojects bound
231 -in the index do not match what is recorded in the `HEAD` commit.
232 -
233 -Because in this round of the proposal we do not use the
234 -`$GIT_DIR/bind` file nor separate branches to keep track of
235 -heads of the subprojects, there is nothing else other than the
236 -working tree and the index file that needs to be updated when
237 -switching branches.
238 -
239 -
240 -Merging
241 --------
242 -
243 -Merging two branches of the toplevel projects can use the
244 -traditional merging mechanism mostly unchanged. The merge base
245 -computation can be done using the `parent` ancestry information
246 -taken from the two toplevel project branch heads being merged,
247 -and merging of the whole tree can be done with a three-way merge
248 -of the whole tree using the merge base and two head commits.
249 -For reasons described later, we would not merge the subproject
250 -parts of the trees during this step, though.
251 -
252 -When the two branch heads use different versions of subproject,
253 -things get a bit tricky. First, let's forget for a moment about
254 -the case where they bind the same project at different location.
255 -We would refuse if they do not have the same number of `bind`
256 -lines that bind something at the same subdirectories.
257 -
258 -------------
259 -$ git merge 'Merge in a side branch' HEAD side
260 -error: the merged heads have subprojects bound at different places.
261 - ours:
262 - linux-2.6/
263 - appliance/
264 - theirs:
265 - kernel/
266 - gadget/
267 - manual/
268 -------------
269 -
270 -Such renaming can be handled by first moving the bind points in
271 -our branch, and redoing the merge (this is a rare operation
272 -anyway). It might go like this:
273 -
274 -------------
275 -$ git reset
276 -$ git update-index --unbind linux-2.6/
277 -$ git update-index --unbind appliance/
278 -$ git update-index --bind $kernel_commit kernel/
279 -$ git update-index --bind $gadget_commit gadget/
280 -$ git commit -m 'Prepare for merge with side branch'
281 -$ git merge 'Merge in a side branch' HEAD side
282 -error: the merged heads have subprojects bound at different places.
283 - ours:
284 - kernel/
285 - gadget/
286 - theirs:
287 - kernel/
288 - gadget/
289 - manual/
290 -------------
291 -[NOTE]
292 -============
293 -Again, `update-index --unbind` is not implemented yet
294 -on the core side.
295 -============
296 -
297 -Their branch added another subproject, so this did not work (or
298 -it could be the other way around -- we might have been the one
299 -with `manual/` subproject while they didn't). This suggests
300 -that we may want an option to `git merge` to allow taking a
301 -union of subprojects. Again, this is a rare operation, and
302 -always taking a union would have created a toplevel project that
303 -had both `kernel/` and `linux-2.6/` bound to the same Linux
304 -kernel project from possibly different vintage, so it would be
305 -prudent to require the set of bound subprojects to exactly match
306 -and give the user an option to take a union.
307 -
308 -------------
309 -$ git merge --union-subprojects 'Merge in a side branch HEAD side
310 -error: the subproject at 'kernel/' needs to be merged first.
311 -------------
312 -
313 -Here, the version of the Linux kernel project in the `side`
314 -branch was different from what our branch had on our `bind`
315 -line. On what kind of difference should we give this error?
316 -Initially, I think we could require one is the fast forward of
317 -the other (ours might be ahead of theirs, or the other way
318 -around), and take the descendant.
319 -
320 -Or we could do an independent merge of subprojects heads, using
321 -the `parent` ancestry of the bound subproject heads to find
322 -their merge-base and doing a three-way merge. This would leave
323 -the merge result in the subproject part of the working tree and
324 -the index.
325 -
326 -[NOTE]
327 -This is the reason we did not do the whole-tree three way merge
328 -earlier. The subproject commit bound to the merge base commit
329 -used for the toplevel project may not be the merge base between
330 -the subproject commits bound to the two toplevel project
331 -commits.
332 -
333 -So let's deal with the case to merge only a subproject part into
334 -our tree first.
335 -
336 -
337 -Merging subprojects
338 --------------------
339 -
340 -An operation of more practical importance is to be able to merge
341 -in changes done outside to the projects bound to our toplevel
342 -project.
343 -
344 -------------
345 -$ git pull --subproject=kernel/ git://git.kernel.org/.../linux-2.6/
346 -------------
347 -
348 -might do:
349 -
350 -. fetch the current `HEAD` commit from Linus.
351 -. find the subproject commit bound at kernel/ subtree.
352 -. perform the usual three-way merge of these two commits, in
353 - `kernel/` part of the working tree.
354 -
355 -After that, `git commit \--subproject` option would be needed to
356 -make a commit.
357 -
358 -[NOTE]
359 -This suggests that we would need to have something similar to
360 -`MERGE_HEAD` for merging the subproject part. In the case of
361 -merging two toplevel project commits, we probably can read the
362 -`bind` lines from the `MERGE_HEAD` commit and either our `HEAD`
363 -commit or our index file. Further, we probably would require
364 -that the latter two must match, just as we currently require the
365 -index file matches our `HEAD` commit before `git merge`.
366 -
367 -Just like the current `pull = fetch + merge` semantics, the
368 -subproject aware version `git pull \--subproject=frotz/` would be
369 -a `git fetch \--subproject=frotz/` followed by a `git merge
370 -\--subproject=frotz/`. So the above would be:
371 -
372 -. Fetch the head.
373 -+
374 -------------
375 -$ git fetch --subproject=kernel/ git://git.kernel.org/.../linux-2.6/
376 -------------
377 -+
378 -which would fetch the commit chain from the remote repository, and
379 -write something like this to `FETCH_HEAD`:
380 -+
381 -------------
382 -3ee68c4...\tfor-merge-into kernel/\tbranch 'master' of git://.../linux-2.6
383 -------------
384 -
385 -. Run `git merge`.
386 -+
387 -------------
388 -$ git merge --subproject=kernel/ \
389 - 'Merge git://.../linux-2.6 into kernel/' HEAD 3ee68c4...
390 -------------
391 -
392 -. In case it does not cleanly automerge, `git merge` would write
393 -the necessary information for a later `git commit` to use in
394 -`MERGE_HEAD`. It may look like this:
395 -+
396 -------------
397 -3ee68c4af3fd7228c1be63254b9f884614f9ebb2 kernel/
398 -------------
399 -+
400 -Similarly, `MERGE_MSG` file will hold the merge message.
401 -
402 -With this, a later invocation of `git commit` to record the
403 -result of hand resolving would be able to notice that:
404 -
405 -. We should be first resolving `kernel/` subproject, not the
406 - whole thing.
407 -. The remote `HEAD` is `3ee68c4\...` commit.
408 -. The merge message is `Merge git://\.../linux-2.6 into kernel/`.
409 -
410 -and would make a merge commit, and register that resulting
411 -commit in the index file using `update-index \--bind` instead of
412 -updating *any* branch head.
413 -
414 -
415 -Management of Subprojects
416 --------------------------
417 -
418 -While the above as a mechanism would support version controlling
419 -of subprojects as a part of *one* larger toplevel project, it
420 -probably is worth pointing out that having a separate repository
421 -to manage the subproject independently would be a good idea.
422 -The same subproject can be incorporated into more than one
423 -toplevel projects, and after all, a subproject should be
424 -something that can stand on its own. In our example scenario,
425 -the `kernel/` project is used as a subproject for the "gadget"
426 -product, but at the same time, the organizaton that runs the
427 -"gadget" project may use Linux on their development machines,
428 -and have their own kernel hackers, not necessarily related to
429 -the use of the kernel in the "gadget" product.
430 -
431 -What this suggests is that not just we need to be able to pull
432 -the kernel development history *into* the subproject of the
433 -"gadget" project, but also we need to be able to push the
434 -development history of the kernel part alone *out* *of* the
435 -"gadget" project to another repository that deals only with the
436 -kernel part.
437 -
438 -It might go this way. First the setup:
439 -
440 -------------
441 -$ git clone git://git.kernel.org/.../linux-2.6 Linux
442 -$ ls -dF *
443 -cloned/ combined/ current/ Linux/
444 -------------
445 -
446 -That is, in addition to the `combined/` which we have been using
447 -to develop the "gadget" product in, we now have a repository for
448 -the kernel, cloned from Linus. In the previous section, we have
449 -outlined how we update the kernel subproject part of `combined/`
450 -repository from the `kernel.org` repository. The same procedure
451 -would work for pulling from `Linux/` repository here.
452 -
453 -We are now going the other way; propagate the kernel work done
454 -in the "gadget" project repository `combined/` back to `Linux/`.
455 -We might do this at the lowest level:
456 -
457 -------------
458 -$ cd combined
459 -$ git cat-file commit HEAD |
460 - sed -ne 's|^bind \([0-9a-f]*\) kernel/$|\1|p' >.git/refs/heads/linux26
461 -$ git push ../Linux linux26:master
462 -------------
463 -
464 -Or, more realistically, since the `Linux` project might already
465 -have their own commits on its `master`:
466 -
467 -------------
468 -$ cd Linux
469 -$ git pull ../combined linux26
470 -------------
471 -
472 -Either way we would need an easy way to maintain the `linux26`
473 -branch in the above example, and that will have to be part of
474 -the wrapper scripts like `git commit` (more likely, that would
475 -be a job for `git commit \--subproject`) for the usability's
476 -sake; in other words, the `cat-file commit` piped to `sed` above
477 -is not something the end user would do, but something that is
478 -done by the wrapper scripts.
479 -
480 -Hopefully the people who work in `Linux/` repository would run
481 -`format-patch` and feed their changes back to the kernel
482 -community.