Skip to content

Commit 75866e6

Browse files
committed
Merge branch 'ss/howto-manage-trunk'
* ss/howto-manage-trunk: How to keep a project's canonical history correct.
2 parents eb5398a + 0678b64 commit 75866e6

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed

Documentation/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ SP_ARTICLES += howto/recover-corrupted-blob-object
5959
SP_ARTICLES += howto/recover-corrupted-object-harder
6060
SP_ARTICLES += howto/rebuild-from-update-hook
6161
SP_ARTICLES += howto/rebase-from-internal-branch
62+
SP_ARTICLES += howto/keep-canonical-history-correct
6263
SP_ARTICLES += howto/maintain-git
6364
API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
6465
SP_ARTICLES += $(API_DOCS)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
From: Junio C Hamano <[email protected]>
2+
Date: Wed, 07 May 2014 13:15:39 -0700
3+
Subject: Beginner question on "Pull is mostly evil"
4+
Abstract: This how-to explains a method for keeping a
5+
project's history correct when using git pull.
6+
Content-type: text/asciidoc
7+
8+
Keep authoritative canonical history correct with git pull
9+
==========================================================
10+
11+
Sometimes a new project integrator will end up with project history
12+
that appears to be "backwards" from what other project developers
13+
expect. This howto presents a suggested integration workflow for
14+
maintaining a central repository.
15+
16+
Suppose that that central repository has this history:
17+
18+
------------
19+
---o---o---A
20+
------------
21+
22+
which ends at commit `A` (time flows from left to right and each node
23+
in the graph is a commit, lines between them indicating parent-child
24+
relationship).
25+
26+
Then you clone it and work on your own commits, which leads you to
27+
have this history in *your* repository:
28+
29+
------------
30+
---o---o---A---B---C
31+
------------
32+
33+
Imagine your coworker did the same and built on top of `A` in *his*
34+
repository in the meantime, and then pushed it to the
35+
central repository:
36+
37+
------------
38+
---o---o---A---X---Y---Z
39+
------------
40+
41+
Now, if you `git push` at this point, because your history that leads
42+
to `C` lacks `X`, `Y` and `Z`, it will fail. You need to somehow make
43+
the tip of your history a descendant of `Z`.
44+
45+
One suggested way to solve the problem is "fetch and then merge", aka
46+
`git pull`. When you fetch, your repository will have a history like
47+
this:
48+
49+
------------
50+
---o---o---A---B---C
51+
\
52+
X---Y---Z
53+
------------
54+
55+
Once you run merge after that, while still on *your* branch, i.e. `C`,
56+
you will create a merge `M` and make the history look like this:
57+
58+
------------
59+
---o---o---A---B---C---M
60+
\ /
61+
X---Y---Z
62+
------------
63+
64+
`M` is a descendant of `Z`, so you can push to update the central
65+
repository. Such a merge `M` does not lose any commit in both
66+
histories, so in that sense it may not be wrong, but when people want
67+
to talk about "the authoritative canonical history that is shared
68+
among the project participants", i.e. "the trunk", they often view
69+
it as "commits you see by following the first-parent chain", and use
70+
this command to view it:
71+
72+
------------
73+
$ git log --first-parent
74+
------------
75+
76+
For all other people who observed the central repository after your
77+
coworker pushed `Z` but before you pushed `M`, the commit on the trunk
78+
used to be `o-o-A-X-Y-Z`. But because you made `M` while you were on
79+
`C`, `M`'s first parent is `C`, so by pushing `M` to advance the
80+
central repository, you made `X-Y-Z` a side branch, not on the trunk.
81+
82+
You would rather want to have a history of this shape:
83+
84+
------------
85+
---o---o---A---X---Y---Z---M'
86+
\ /
87+
B-----------C
88+
------------
89+
90+
so that in the first-parent chain, it is clear that the project first
91+
did `X` and then `Y` and then `Z` and merged a change that consists of
92+
two commits `B` and `C` that achieves a single goal. You may have
93+
worked on fixing the bug #12345 with these two patches, and the merge
94+
`M'` with swapped parents can say in its log message "Merge
95+
fix-bug-12345". Having a way to tell `git pull` to create a merge
96+
but record the parents in reverse order may be a way to do so.
97+
98+
Note that I said "achieves a single goal" above, because this is
99+
important. "Swapping the merge order" only covers a special case
100+
where the project does not care too much about having unrelated
101+
things done on a single merge but cares a lot about first-parent
102+
chain.
103+
104+
There are multiple schools of thought about the "trunk" management.
105+
106+
1. Some projects want to keep a completely linear history without any
107+
merges. Obviously, swapping the merge order would not match their
108+
taste. You would need to flatten your history on top of the
109+
updated upstream to result in a history of this shape instead:
110+
+
111+
------------
112+
---o---o---A---X---Y---Z---B---C
113+
------------
114+
+
115+
with `git pull --rebase` or something.
116+
117+
2. Some projects tolerate merges in their history, but do not worry
118+
too much about the first-parent order, and allow fast-forward
119+
merges. To them, swapping the merge order does not hurt, but
120+
it is unnecessary.
121+
122+
3. Some projects want each commit on the "trunk" to do one single
123+
thing. The output of `git log --first-parent` in such a project
124+
would show either a merge of a side branch that completes a single
125+
theme, or a single commit that completes a single theme by itself.
126+
If your two commits `B` and `C` (or they may even be two groups of
127+
commits) were solving two independent issues, then the merge `M'`
128+
we made in the earlier example by swapping the merge order is
129+
still not up to the project standard. It merges two unrelated
130+
efforts `B` and `C` at the same time.
131+
132+
For projects in the last category (Git itself is one of them),
133+
individual developers would want to prepare a history more like
134+
this:
135+
136+
------------
137+
C0--C1--C2 topic-c
138+
/
139+
---o---o---A master
140+
\
141+
B0--B1--B2 topic-b
142+
------------
143+
144+
That is, keeping separate topics on separate branches, perhaps like
145+
so:
146+
147+
------------
148+
$ git clone $URL work && cd work
149+
$ git checkout -b topic-b master
150+
$ ... work to create B0, B1 and B2 to complete one theme
151+
$ git checkout -b topic-c master
152+
$ ... same for the theme of topic-c
153+
------------
154+
155+
And then
156+
157+
------------
158+
$ git checkout master
159+
$ git pull --ff-only
160+
------------
161+
162+
would grab `X`, `Y` and `Z` from the upstream and advance your master
163+
branch:
164+
165+
------------
166+
C0--C1--C2 topic-c
167+
/
168+
---o---o---A---X---Y---Z master
169+
\
170+
B0--B1--B2 topic-b
171+
------------
172+
173+
And then you would merge these two branches separately:
174+
175+
------------
176+
$ git merge topic-b
177+
$ git merge topic-c
178+
------------
179+
180+
to result in
181+
182+
------------
183+
C0--C1---------C2
184+
/ \
185+
---o---o---A---X---Y---Z---M---N
186+
\ /
187+
B0--B1-----B2
188+
------------
189+
190+
and push it back to the central repository.
191+
192+
It is very much possible that while you are merging topic-b and
193+
topic-c, somebody again advanced the history in the central repository
194+
to put `W` on top of `Z`, and make your `git push` fail.
195+
196+
In such a case, you would rewind to discard `M` and `N`, update the
197+
tip of your 'master' again and redo the two merges:
198+
199+
------------
200+
$ git reset --hard origin/master
201+
$ git pull --ff-only
202+
$ git merge topic-b
203+
$ git merge topic-c
204+
------------
205+
206+
The procedure will result in a history that looks like this:
207+
208+
------------
209+
C0--C1--------------C2
210+
/ \
211+
---o---o---A---X---Y---Z---W---M'--N'
212+
\ /
213+
B0--B1---------B2
214+
------------
215+
216+
See also http://git-blame.blogspot.com/2013/09/fun-with-first-parent-history.html

0 commit comments

Comments
 (0)