How to keep a project's canonical history correct.
During the mail thread about "Pull is mostly evil" a user asked how the first parent could become reversed. This howto explains how the first parent can get reversed when viewed by the project and then explains a method to keep the history correct. Signed-off-by: Stephen P. Smith <ischis2@cox.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Stephen P. Smith committed
May 8, 2014 at 23:08 UTC
0678b649a19586daa5ec45344376187b91dd7328
2 files changed
+217
Documentation/Makefile
+1
@@ -59,6 +59,7 @@ SP_ARTICLES += howto/recover-corrupted-blob-object
59
SP_ARTICLES += howto/recover-corrupted-object-harder
60
SP_ARTICLES += howto/rebuild-from-update-hook
61
SP_ARTICLES += howto/rebase-from-internal-branch
62
+SP_ARTICLES += howto/keep-canonical-history-correct
63
SP_ARTICLES += howto/maintain-git
64
API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
65
SP_ARTICLES += $(API_DOCS)
Documentation/howto/keep-canonical-history-correct.txt
new
+216
@@ -0,0 +1,216 @@
1
+From: Junio C Hamano <gitster@pobox.com>
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