##// END OF EJS Templates
tagmerge: use absolute_import
Gregory Szorc -
r25981:fa91c49a default
parent child Browse files
Show More
@@ -1,265 +1,274
1 1 # tagmerge.py - merge .hgtags files
2 2 #
3 3 # Copyright 2014 Angel Ezquerra <angel.ezquerra@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # This module implements an automatic merge algorithm for mercurial's tag files
9 9 #
10 10 # The tagmerge algorithm implemented in this module is able to resolve most
11 11 # merge conflicts that currently would trigger a .hgtags merge conflict. The
12 12 # only case that it does not (and cannot) handle is that in which two tags point
13 13 # to different revisions on each merge parent _and_ their corresponding tag
14 14 # histories have the same rank (i.e. the same length). In all other cases the
15 15 # merge algorithm will choose the revision belonging to the parent with the
16 16 # highest ranked tag history. The merged tag history is the combination of both
17 17 # tag histories (special care is taken to try to combine common tag histories
18 18 # where possible).
19 19 #
20 20 # In addition to actually merging the tags from two parents, taking into
21 21 # account the base, the algorithm also tries to minimize the difference
22 22 # between the merged tag file and the first parent's tag file (i.e. it tries to
23 23 # make the merged tag order as as similar as possible to the first parent's tag
24 24 # file order).
25 25 #
26 26 # The algorithm works as follows:
27 27 # 1. read the tags from p1, p2 and the base
28 28 # - when reading the p1 tags, also get the line numbers associated to each
29 29 # tag node (these will be used to sort the merged tags in a way that
30 30 # minimizes the diff to p1). Ignore the file numbers when reading p2 and
31 31 # the base
32 32 # 2. recover the "lost tags" (i.e. those that are found in the base but not on
33 33 # p1 or p2) and add them back to p1 and/or p2
34 34 # - at this point the only tags that are on p1 but not on p2 are those new
35 35 # tags that were introduced in p1. Same thing for the tags that are on p2
36 36 # but not on p2
37 37 # 3. take all tags that are only on p1 or only on p2 (but not on the base)
38 38 # - Note that these are the tags that were introduced between base and p1
39 39 # and between base and p2, possibly on separate clones
40 40 # 4. for each tag found both on p1 and p2 perform the following merge algorithm:
41 41 # - the tags conflict if their tag "histories" have the same "rank" (i.e.
42 42 # length) AND the last (current) tag is NOT the same
43 43 # - for non conflicting tags:
44 44 # - choose which are the high and the low ranking nodes
45 45 # - the high ranking list of nodes is the one that is longer.
46 46 # In case of draw favor p1
47 47 # - the merged node list is made of 3 parts:
48 48 # - first the nodes that are common to the beginning of both
49 49 # the low and the high ranking nodes
50 50 # - second the non common low ranking nodes
51 51 # - finally the non common high ranking nodes (with the last
52 52 # one being the merged tag node)
53 53 # - note that this is equivalent to putting the whole low ranking
54 54 # node list first, followed by the non common high ranking nodes
55 55 # - note that during the merge we keep the "node line numbers", which will
56 56 # be used when writing the merged tags to the tag file
57 57 # 5. write the merged tags taking into account to their positions in the first
58 58 # parent (i.e. try to keep the relative ordering of the nodes that come
59 59 # from p1). This minimizes the diff between the merged and the p1 tag files
60 60 # This is done by using the following algorithm
61 61 # - group the nodes for a given tag that must be written next to each other
62 62 # - A: nodes that come from consecutive lines on p1
63 63 # - B: nodes that come from p2 (i.e. whose associated line number is
64 64 # None) and are next to one of the a nodes in A
65 65 # - each group is associated with a line number coming from p1
66 66 # - generate a "tag block" for each of the groups
67 67 # - a tag block is a set of consecutive "node tag" lines belonging to
68 68 # the same tag and which will be written next to each other on the
69 69 # merged tags file
70 70 # - sort the "tag blocks" according to their associated number line
71 71 # - put blocks whose nodes come all from p2 first
72 72 # - write the tag blocks in the sorted order
73 73
74 import tags as tagsmod
75 import util
76 from node import nullid, hex
77 from i18n import _
74 from __future__ import absolute_import
75
78 76 import operator
77
78 from .i18n import _
79 from .node import (
80 hex,
81 nullid,
82 )
83 from .import (
84 tags as tagsmod,
85 util,
86 )
87
79 88 hexnullid = hex(nullid)
80 89
81 90 def readtagsformerge(ui, repo, lines, fn='', keeplinenums=False):
82 91 '''read the .hgtags file into a structure that is suitable for merging
83 92
84 93 Depending on the keeplinenums flag, clear the line numbers associated
85 94 with each tag. This is done because only the line numbers of the first
86 95 parent are useful for merging.
87 96 '''
88 97 filetags = tagsmod._readtaghist(ui, repo, lines, fn=fn, recode=None,
89 98 calcnodelines=True)[1]
90 99 for tagname, taginfo in filetags.items():
91 100 if not keeplinenums:
92 101 for el in taginfo:
93 102 el[1] = None
94 103 return filetags
95 104
96 105 def grouptagnodesbyline(tagnodes):
97 106 '''
98 107 Group nearby nodes (i.e. those that must be written next to each other)
99 108
100 109 The input is a list of [node, position] pairs, corresponding to a given tag
101 110 The position is the line number where the node was found on the first parent
102 111 .hgtags file, or None for those nodes that came from the base or the second
103 112 parent .hgtags files.
104 113
105 114 This function groups those [node, position] pairs, returning a list of
106 115 groups of nodes that must be written next to each other because their
107 116 positions are consecutive or have no position preference (because their
108 117 position is None).
109 118
110 119 The result is a list of [position, [consecutive node list]]
111 120 '''
112 121 firstlinenum = None
113 122 for hexnode, linenum in tagnodes:
114 123 firstlinenum = linenum
115 124 if firstlinenum is not None:
116 125 break
117 126 if firstlinenum is None:
118 127 return [[None, [el[0] for el in tagnodes]]]
119 128 tagnodes[0][1] = firstlinenum
120 129 groupednodes = [[firstlinenum, []]]
121 130 prevlinenum = firstlinenum
122 131 for hexnode, linenum in tagnodes:
123 132 if linenum is not None and linenum - prevlinenum > 1:
124 133 groupednodes.append([linenum, []])
125 134 groupednodes[-1][1].append(hexnode)
126 135 if linenum is not None:
127 136 prevlinenum = linenum
128 137 return groupednodes
129 138
130 139 def writemergedtags(repo, mergedtags):
131 140 '''
132 141 write the merged tags while trying to minimize the diff to the first parent
133 142
134 143 This function uses the ordering info stored on the merged tags dict to
135 144 generate an .hgtags file which is correct (in the sense that its contents
136 145 correspond to the result of the tag merge) while also being as close as
137 146 possible to the first parent's .hgtags file.
138 147 '''
139 148 # group the node-tag pairs that must be written next to each other
140 149 for tname, taglist in mergedtags.items():
141 150 mergedtags[tname] = grouptagnodesbyline(taglist)
142 151
143 152 # convert the grouped merged tags dict into a format that resembles the
144 153 # final .hgtags file (i.e. a list of blocks of 'node tag' pairs)
145 154 def taglist2string(tlist, tname):
146 155 return '\n'.join(['%s %s' % (hexnode, tname) for hexnode in tlist])
147 156
148 157 finaltags = []
149 158 for tname, tags in mergedtags.items():
150 159 for block in tags:
151 160 block[1] = taglist2string(block[1], tname)
152 161 finaltags += tags
153 162
154 163 # the tag groups are linked to a "position" that can be used to sort them
155 164 # before writing them
156 165 # the position is calculated to ensure that the diff of the merged .hgtags
157 166 # file to the first parent's .hgtags file is as small as possible
158 167 finaltags.sort(key=operator.itemgetter(0))
159 168
160 169 # finally we can join the sorted groups to get the final contents of the
161 170 # merged .hgtags file, and then write it to disk
162 171 mergedtagstring = '\n'.join([tags for rank, tags in finaltags if tags])
163 172 fp = repo.wfile('.hgtags', 'wb')
164 173 fp.write(mergedtagstring + '\n')
165 174 fp.close()
166 175
167 176 def singletagmerge(p1nodes, p2nodes):
168 177 '''
169 178 merge the nodes corresponding to a single tag
170 179
171 180 Note that the inputs are lists of node-linenum pairs (i.e. not just lists
172 181 of nodes)
173 182 '''
174 183 if not p2nodes:
175 184 return p1nodes
176 185 if not p1nodes:
177 186 return p2nodes
178 187
179 188 # there is no conflict unless both tags point to different revisions
180 189 # and have a non identical tag history
181 190 p1currentnode = p1nodes[-1][0]
182 191 p2currentnode = p2nodes[-1][0]
183 192 if p1currentnode != p2currentnode and len(p1nodes) == len(p2nodes):
184 193 # cannot merge two tags with same rank pointing to different nodes
185 194 return None
186 195
187 196 # which are the highest ranking (hr) / lowest ranking (lr) nodes?
188 197 if len(p1nodes) >= len(p2nodes):
189 198 hrnodes, lrnodes = p1nodes, p2nodes
190 199 else:
191 200 hrnodes, lrnodes = p2nodes, p1nodes
192 201
193 202 # the lowest ranking nodes will be written first, followed by the highest
194 203 # ranking nodes
195 204 # to avoid unwanted tag rank explosion we try to see if there are some
196 205 # common nodes that can be written only once
197 206 commonidx = len(lrnodes)
198 207 for n in range(len(lrnodes)):
199 208 if hrnodes[n][0] != lrnodes[n][0]:
200 209 commonidx = n
201 210 break
202 211 lrnodes[n][1] = p1nodes[n][1]
203 212
204 213 # the merged node list has 3 parts:
205 214 # - common nodes
206 215 # - non common lowest ranking nodes
207 216 # - non common highest ranking nodes
208 217 # note that the common nodes plus the non common lowest ranking nodes is the
209 218 # whole list of lr nodes
210 219 return lrnodes + hrnodes[commonidx:]
211 220
212 221 def merge(repo, fcd, fco, fca):
213 222 '''
214 223 Merge the tags of two revisions, taking into account the base tags
215 224 Try to minimize the diff between the merged tags and the first parent tags
216 225 '''
217 226 ui = repo.ui
218 227 # read the p1, p2 and base tags
219 228 # only keep the line numbers for the p1 tags
220 229 p1tags = readtagsformerge(
221 230 ui, repo, fcd.data().splitlines(), fn="p1 tags",
222 231 keeplinenums=True)
223 232 p2tags = readtagsformerge(
224 233 ui, repo, fco.data().splitlines(), fn="p2 tags",
225 234 keeplinenums=False)
226 235 basetags = readtagsformerge(
227 236 ui, repo, fca.data().splitlines(), fn="base tags",
228 237 keeplinenums=False)
229 238
230 239 # recover the list of "lost tags" (i.e. those that were found on the base
231 240 # revision but not on one of the revisions being merged)
232 241 basetagset = set(basetags)
233 242 for n, pntags in enumerate((p1tags, p2tags)):
234 243 pntagset = set(pntags)
235 244 pnlosttagset = basetagset - pntagset
236 245 for t in pnlosttagset:
237 246 pntags[t] = basetags[t]
238 247 if pntags[t][-1][0] != hexnullid:
239 248 pntags[t].append([hexnullid, None])
240 249
241 250 conflictedtags = [] # for reporting purposes
242 251 mergedtags = util.sortdict(p1tags)
243 252 # sortdict does not implement iteritems()
244 253 for tname, p2nodes in p2tags.items():
245 254 if tname not in mergedtags:
246 255 mergedtags[tname] = p2nodes
247 256 continue
248 257 p1nodes = mergedtags[tname]
249 258 mergednodes = singletagmerge(p1nodes, p2nodes)
250 259 if mergednodes is None:
251 260 conflictedtags.append(tname)
252 261 continue
253 262 mergedtags[tname] = mergednodes
254 263
255 264 if conflictedtags:
256 265 numconflicts = len(conflictedtags)
257 266 ui.warn(_('automatic .hgtags merge failed\n'
258 267 'the following %d tags are in conflict: %s\n')
259 268 % (numconflicts, ', '.join(sorted(conflictedtags))))
260 269 return True, 1
261 270
262 271 writemergedtags(repo, mergedtags)
263 272 ui.note(_('.hgtags merged successfully\n'))
264 273 return False, 0
265 274
General Comments 0
You need to be logged in to leave comments. Login now