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