##// END OF EJS Templates
tag: use filtered repo when creating new tags (issue5539)...
Denis Laxalde -
r34017:2d80e078 default
parent child Browse files
Show More
@@ -1,788 +1,788
1 # tags.py - read tag info from local repository
1 # tags.py - read tag info from local repository
2 #
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009 Matt Mackall <mpm@selenic.com>
4 # Copyright 2009 Greg Ward <greg@gerg.ca>
4 # Copyright 2009 Greg Ward <greg@gerg.ca>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 # Currently this module only deals with reading and caching tags.
9 # Currently this module only deals with reading and caching tags.
10 # Eventually, it could take care of updating (adding/removing/moving)
10 # Eventually, it could take care of updating (adding/removing/moving)
11 # tags too.
11 # tags too.
12
12
13 from __future__ import absolute_import
13 from __future__ import absolute_import
14
14
15 import errno
15 import errno
16
16
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 nullid,
20 nullid,
21 short,
21 short,
22 )
22 )
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 encoding,
25 encoding,
26 error,
26 error,
27 match as matchmod,
27 match as matchmod,
28 scmutil,
28 scmutil,
29 util,
29 util,
30 )
30 )
31
31
32 # Tags computation can be expensive and caches exist to make it fast in
32 # Tags computation can be expensive and caches exist to make it fast in
33 # the common case.
33 # the common case.
34 #
34 #
35 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
35 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
36 # each revision in the repository. The file is effectively an array of
36 # each revision in the repository. The file is effectively an array of
37 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
37 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
38 # details.
38 # details.
39 #
39 #
40 # The .hgtags filenode cache grows in proportion to the length of the
40 # The .hgtags filenode cache grows in proportion to the length of the
41 # changelog. The file is truncated when the # changelog is stripped.
41 # changelog. The file is truncated when the # changelog is stripped.
42 #
42 #
43 # The purpose of the filenode cache is to avoid the most expensive part
43 # The purpose of the filenode cache is to avoid the most expensive part
44 # of finding global tags, which is looking up the .hgtags filenode in the
44 # of finding global tags, which is looking up the .hgtags filenode in the
45 # manifest for each head. This can take dozens or over 100ms for
45 # manifest for each head. This can take dozens or over 100ms for
46 # repositories with very large manifests. Multiplied by dozens or even
46 # repositories with very large manifests. Multiplied by dozens or even
47 # hundreds of heads and there is a significant performance concern.
47 # hundreds of heads and there is a significant performance concern.
48 #
48 #
49 # There also exist a separate cache file for each repository filter.
49 # There also exist a separate cache file for each repository filter.
50 # These "tags-*" files store information about the history of tags.
50 # These "tags-*" files store information about the history of tags.
51 #
51 #
52 # The tags cache files consists of a cache validation line followed by
52 # The tags cache files consists of a cache validation line followed by
53 # a history of tags.
53 # a history of tags.
54 #
54 #
55 # The cache validation line has the format:
55 # The cache validation line has the format:
56 #
56 #
57 # <tiprev> <tipnode> [<filteredhash>]
57 # <tiprev> <tipnode> [<filteredhash>]
58 #
58 #
59 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
59 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
60 # node for that changeset. These redundantly identify the repository
60 # node for that changeset. These redundantly identify the repository
61 # tip from the time the cache was written. In addition, <filteredhash>,
61 # tip from the time the cache was written. In addition, <filteredhash>,
62 # if present, is a 40 character hex hash of the contents of the filtered
62 # if present, is a 40 character hex hash of the contents of the filtered
63 # revisions for this filter. If the set of filtered revs changes, the
63 # revisions for this filter. If the set of filtered revs changes, the
64 # hash will change and invalidate the cache.
64 # hash will change and invalidate the cache.
65 #
65 #
66 # The history part of the tags cache consists of lines of the form:
66 # The history part of the tags cache consists of lines of the form:
67 #
67 #
68 # <node> <tag>
68 # <node> <tag>
69 #
69 #
70 # (This format is identical to that of .hgtags files.)
70 # (This format is identical to that of .hgtags files.)
71 #
71 #
72 # <tag> is the tag name and <node> is the 40 character hex changeset
72 # <tag> is the tag name and <node> is the 40 character hex changeset
73 # the tag is associated with.
73 # the tag is associated with.
74 #
74 #
75 # Tags are written sorted by tag name.
75 # Tags are written sorted by tag name.
76 #
76 #
77 # Tags associated with multiple changesets have an entry for each changeset.
77 # Tags associated with multiple changesets have an entry for each changeset.
78 # The most recent changeset (in terms of revlog ordering for the head
78 # The most recent changeset (in terms of revlog ordering for the head
79 # setting it) for each tag is last.
79 # setting it) for each tag is last.
80
80
81 def fnoderevs(ui, repo, revs):
81 def fnoderevs(ui, repo, revs):
82 """return the list of '.hgtags' fnodes used in a set revisions
82 """return the list of '.hgtags' fnodes used in a set revisions
83
83
84 This is returned as list of unique fnodes. We use a list instead of a set
84 This is returned as list of unique fnodes. We use a list instead of a set
85 because order matters when it comes to tags."""
85 because order matters when it comes to tags."""
86 unfi = repo.unfiltered()
86 unfi = repo.unfiltered()
87 tonode = unfi.changelog.node
87 tonode = unfi.changelog.node
88 nodes = [tonode(r) for r in revs]
88 nodes = [tonode(r) for r in revs]
89 fnodes = _getfnodes(ui, repo, nodes[::-1]) # reversed help the cache
89 fnodes = _getfnodes(ui, repo, nodes[::-1]) # reversed help the cache
90 fnodes = _filterfnodes(fnodes, nodes)
90 fnodes = _filterfnodes(fnodes, nodes)
91 return fnodes
91 return fnodes
92
92
93 def _nulltonone(value):
93 def _nulltonone(value):
94 """convert nullid to None
94 """convert nullid to None
95
95
96 For tag value, nullid means "deleted". This small utility function helps
96 For tag value, nullid means "deleted". This small utility function helps
97 translating that to None."""
97 translating that to None."""
98 if value == nullid:
98 if value == nullid:
99 return None
99 return None
100 return value
100 return value
101
101
102 def difftags(ui, repo, oldfnodes, newfnodes):
102 def difftags(ui, repo, oldfnodes, newfnodes):
103 """list differences between tags expressed in two set of file-nodes
103 """list differences between tags expressed in two set of file-nodes
104
104
105 The list contains entries in the form: (tagname, oldvalue, new value).
105 The list contains entries in the form: (tagname, oldvalue, new value).
106 None is used to expressed missing value:
106 None is used to expressed missing value:
107 ('foo', None, 'abcd') is a new tag,
107 ('foo', None, 'abcd') is a new tag,
108 ('bar', 'ef01', None) is a deletion,
108 ('bar', 'ef01', None) is a deletion,
109 ('baz', 'abcd', 'ef01') is a tag movement.
109 ('baz', 'abcd', 'ef01') is a tag movement.
110 """
110 """
111 if oldfnodes == newfnodes:
111 if oldfnodes == newfnodes:
112 return []
112 return []
113 oldtags = _tagsfromfnodes(ui, repo, oldfnodes)
113 oldtags = _tagsfromfnodes(ui, repo, oldfnodes)
114 newtags = _tagsfromfnodes(ui, repo, newfnodes)
114 newtags = _tagsfromfnodes(ui, repo, newfnodes)
115
115
116 # list of (tag, old, new): None means missing
116 # list of (tag, old, new): None means missing
117 entries = []
117 entries = []
118 for tag, (new, __) in newtags.items():
118 for tag, (new, __) in newtags.items():
119 new = _nulltonone(new)
119 new = _nulltonone(new)
120 old, __ = oldtags.pop(tag, (None, None))
120 old, __ = oldtags.pop(tag, (None, None))
121 old = _nulltonone(old)
121 old = _nulltonone(old)
122 if old != new:
122 if old != new:
123 entries.append((tag, old, new))
123 entries.append((tag, old, new))
124 # handle deleted tags
124 # handle deleted tags
125 for tag, (old, __) in oldtags.items():
125 for tag, (old, __) in oldtags.items():
126 old = _nulltonone(old)
126 old = _nulltonone(old)
127 if old is not None:
127 if old is not None:
128 entries.append((tag, old, None))
128 entries.append((tag, old, None))
129 entries.sort()
129 entries.sort()
130 return entries
130 return entries
131
131
132 def writediff(fp, difflist):
132 def writediff(fp, difflist):
133 """write tags diff information to a file.
133 """write tags diff information to a file.
134
134
135 Data are stored with a line based format:
135 Data are stored with a line based format:
136
136
137 <action> <hex-node> <tag-name>\n
137 <action> <hex-node> <tag-name>\n
138
138
139 Action are defined as follow:
139 Action are defined as follow:
140 -R tag is removed,
140 -R tag is removed,
141 +A tag is added,
141 +A tag is added,
142 -M tag is moved (old value),
142 -M tag is moved (old value),
143 +M tag is moved (new value),
143 +M tag is moved (new value),
144
144
145 Example:
145 Example:
146
146
147 +A 875517b4806a848f942811a315a5bce30804ae85 t5
147 +A 875517b4806a848f942811a315a5bce30804ae85 t5
148
148
149 See documentation of difftags output for details about the input.
149 See documentation of difftags output for details about the input.
150 """
150 """
151 add = '+A %s %s\n'
151 add = '+A %s %s\n'
152 remove = '-R %s %s\n'
152 remove = '-R %s %s\n'
153 updateold = '-M %s %s\n'
153 updateold = '-M %s %s\n'
154 updatenew = '+M %s %s\n'
154 updatenew = '+M %s %s\n'
155 for tag, old, new in difflist:
155 for tag, old, new in difflist:
156 # translate to hex
156 # translate to hex
157 if old is not None:
157 if old is not None:
158 old = hex(old)
158 old = hex(old)
159 if new is not None:
159 if new is not None:
160 new = hex(new)
160 new = hex(new)
161 # write to file
161 # write to file
162 if old is None:
162 if old is None:
163 fp.write(add % (new, tag))
163 fp.write(add % (new, tag))
164 elif new is None:
164 elif new is None:
165 fp.write(remove % (old, tag))
165 fp.write(remove % (old, tag))
166 else:
166 else:
167 fp.write(updateold % (old, tag))
167 fp.write(updateold % (old, tag))
168 fp.write(updatenew % (new, tag))
168 fp.write(updatenew % (new, tag))
169
169
170 def findglobaltags(ui, repo):
170 def findglobaltags(ui, repo):
171 '''Find global tags in a repo: return a tagsmap
171 '''Find global tags in a repo: return a tagsmap
172
172
173 tagsmap: tag name to (node, hist) 2-tuples.
173 tagsmap: tag name to (node, hist) 2-tuples.
174
174
175 The tags cache is read and updated as a side-effect of calling.
175 The tags cache is read and updated as a side-effect of calling.
176 '''
176 '''
177 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
177 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
178 if cachetags is not None:
178 if cachetags is not None:
179 assert not shouldwrite
179 assert not shouldwrite
180 # XXX is this really 100% correct? are there oddball special
180 # XXX is this really 100% correct? are there oddball special
181 # cases where a global tag should outrank a local tag but won't,
181 # cases where a global tag should outrank a local tag but won't,
182 # because cachetags does not contain rank info?
182 # because cachetags does not contain rank info?
183 alltags = {}
183 alltags = {}
184 _updatetags(cachetags, alltags)
184 _updatetags(cachetags, alltags)
185 return alltags
185 return alltags
186
186
187 for head in reversed(heads): # oldest to newest
187 for head in reversed(heads): # oldest to newest
188 assert head in repo.changelog.nodemap, \
188 assert head in repo.changelog.nodemap, \
189 "tag cache returned bogus head %s" % short(head)
189 "tag cache returned bogus head %s" % short(head)
190 fnodes = _filterfnodes(tagfnode, reversed(heads))
190 fnodes = _filterfnodes(tagfnode, reversed(heads))
191 alltags = _tagsfromfnodes(ui, repo, fnodes)
191 alltags = _tagsfromfnodes(ui, repo, fnodes)
192
192
193 # and update the cache (if necessary)
193 # and update the cache (if necessary)
194 if shouldwrite:
194 if shouldwrite:
195 _writetagcache(ui, repo, valid, alltags)
195 _writetagcache(ui, repo, valid, alltags)
196 return alltags
196 return alltags
197
197
198 def _filterfnodes(tagfnode, nodes):
198 def _filterfnodes(tagfnode, nodes):
199 """return a list of unique fnodes
199 """return a list of unique fnodes
200
200
201 The order of this list matches the order of "nodes". Preserving this order
201 The order of this list matches the order of "nodes". Preserving this order
202 is important as reading tags in different order provides different
202 is important as reading tags in different order provides different
203 results."""
203 results."""
204 seen = set() # set of fnode
204 seen = set() # set of fnode
205 fnodes = []
205 fnodes = []
206 for no in nodes: # oldest to newest
206 for no in nodes: # oldest to newest
207 fnode = tagfnode.get(no)
207 fnode = tagfnode.get(no)
208 if fnode and fnode not in seen:
208 if fnode and fnode not in seen:
209 seen.add(fnode)
209 seen.add(fnode)
210 fnodes.append(fnode)
210 fnodes.append(fnode)
211 return fnodes
211 return fnodes
212
212
213 def _tagsfromfnodes(ui, repo, fnodes):
213 def _tagsfromfnodes(ui, repo, fnodes):
214 """return a tagsmap from a list of file-node
214 """return a tagsmap from a list of file-node
215
215
216 tagsmap: tag name to (node, hist) 2-tuples.
216 tagsmap: tag name to (node, hist) 2-tuples.
217
217
218 The order of the list matters."""
218 The order of the list matters."""
219 alltags = {}
219 alltags = {}
220 fctx = None
220 fctx = None
221 for fnode in fnodes:
221 for fnode in fnodes:
222 if fctx is None:
222 if fctx is None:
223 fctx = repo.filectx('.hgtags', fileid=fnode)
223 fctx = repo.filectx('.hgtags', fileid=fnode)
224 else:
224 else:
225 fctx = fctx.filectx(fnode)
225 fctx = fctx.filectx(fnode)
226 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
226 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
227 _updatetags(filetags, alltags)
227 _updatetags(filetags, alltags)
228 return alltags
228 return alltags
229
229
230 def readlocaltags(ui, repo, alltags, tagtypes):
230 def readlocaltags(ui, repo, alltags, tagtypes):
231 '''Read local tags in repo. Update alltags and tagtypes.'''
231 '''Read local tags in repo. Update alltags and tagtypes.'''
232 try:
232 try:
233 data = repo.vfs.read("localtags")
233 data = repo.vfs.read("localtags")
234 except IOError as inst:
234 except IOError as inst:
235 if inst.errno != errno.ENOENT:
235 if inst.errno != errno.ENOENT:
236 raise
236 raise
237 return
237 return
238
238
239 # localtags is in the local encoding; re-encode to UTF-8 on
239 # localtags is in the local encoding; re-encode to UTF-8 on
240 # input for consistency with the rest of this module.
240 # input for consistency with the rest of this module.
241 filetags = _readtags(
241 filetags = _readtags(
242 ui, repo, data.splitlines(), "localtags",
242 ui, repo, data.splitlines(), "localtags",
243 recode=encoding.fromlocal)
243 recode=encoding.fromlocal)
244
244
245 # remove tags pointing to invalid nodes
245 # remove tags pointing to invalid nodes
246 cl = repo.changelog
246 cl = repo.changelog
247 for t in filetags.keys():
247 for t in filetags.keys():
248 try:
248 try:
249 cl.rev(filetags[t][0])
249 cl.rev(filetags[t][0])
250 except (LookupError, ValueError):
250 except (LookupError, ValueError):
251 del filetags[t]
251 del filetags[t]
252
252
253 _updatetags(filetags, alltags, 'local', tagtypes)
253 _updatetags(filetags, alltags, 'local', tagtypes)
254
254
255 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
255 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
256 '''Read tag definitions from a file (or any source of lines).
256 '''Read tag definitions from a file (or any source of lines).
257
257
258 This function returns two sortdicts with similar information:
258 This function returns two sortdicts with similar information:
259
259
260 - the first dict, bintaghist, contains the tag information as expected by
260 - the first dict, bintaghist, contains the tag information as expected by
261 the _readtags function, i.e. a mapping from tag name to (node, hist):
261 the _readtags function, i.e. a mapping from tag name to (node, hist):
262 - node is the node id from the last line read for that name,
262 - node is the node id from the last line read for that name,
263 - hist is the list of node ids previously associated with it (in file
263 - hist is the list of node ids previously associated with it (in file
264 order). All node ids are binary, not hex.
264 order). All node ids are binary, not hex.
265
265
266 - the second dict, hextaglines, is a mapping from tag name to a list of
266 - the second dict, hextaglines, is a mapping from tag name to a list of
267 [hexnode, line number] pairs, ordered from the oldest to the newest node.
267 [hexnode, line number] pairs, ordered from the oldest to the newest node.
268
268
269 When calcnodelines is False the hextaglines dict is not calculated (an
269 When calcnodelines is False the hextaglines dict is not calculated (an
270 empty dict is returned). This is done to improve this function's
270 empty dict is returned). This is done to improve this function's
271 performance in cases where the line numbers are not needed.
271 performance in cases where the line numbers are not needed.
272 '''
272 '''
273
273
274 bintaghist = util.sortdict()
274 bintaghist = util.sortdict()
275 hextaglines = util.sortdict()
275 hextaglines = util.sortdict()
276 count = 0
276 count = 0
277
277
278 def dbg(msg):
278 def dbg(msg):
279 ui.debug("%s, line %s: %s\n" % (fn, count, msg))
279 ui.debug("%s, line %s: %s\n" % (fn, count, msg))
280
280
281 for nline, line in enumerate(lines):
281 for nline, line in enumerate(lines):
282 count += 1
282 count += 1
283 if not line:
283 if not line:
284 continue
284 continue
285 try:
285 try:
286 (nodehex, name) = line.split(" ", 1)
286 (nodehex, name) = line.split(" ", 1)
287 except ValueError:
287 except ValueError:
288 dbg("cannot parse entry")
288 dbg("cannot parse entry")
289 continue
289 continue
290 name = name.strip()
290 name = name.strip()
291 if recode:
291 if recode:
292 name = recode(name)
292 name = recode(name)
293 try:
293 try:
294 nodebin = bin(nodehex)
294 nodebin = bin(nodehex)
295 except TypeError:
295 except TypeError:
296 dbg("node '%s' is not well formed" % nodehex)
296 dbg("node '%s' is not well formed" % nodehex)
297 continue
297 continue
298
298
299 # update filetags
299 # update filetags
300 if calcnodelines:
300 if calcnodelines:
301 # map tag name to a list of line numbers
301 # map tag name to a list of line numbers
302 if name not in hextaglines:
302 if name not in hextaglines:
303 hextaglines[name] = []
303 hextaglines[name] = []
304 hextaglines[name].append([nodehex, nline])
304 hextaglines[name].append([nodehex, nline])
305 continue
305 continue
306 # map tag name to (node, hist)
306 # map tag name to (node, hist)
307 if name not in bintaghist:
307 if name not in bintaghist:
308 bintaghist[name] = []
308 bintaghist[name] = []
309 bintaghist[name].append(nodebin)
309 bintaghist[name].append(nodebin)
310 return bintaghist, hextaglines
310 return bintaghist, hextaglines
311
311
312 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
312 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
313 '''Read tag definitions from a file (or any source of lines).
313 '''Read tag definitions from a file (or any source of lines).
314
314
315 Returns a mapping from tag name to (node, hist).
315 Returns a mapping from tag name to (node, hist).
316
316
317 "node" is the node id from the last line read for that name. "hist"
317 "node" is the node id from the last line read for that name. "hist"
318 is the list of node ids previously associated with it (in file order).
318 is the list of node ids previously associated with it (in file order).
319 All node ids are binary, not hex.
319 All node ids are binary, not hex.
320 '''
320 '''
321 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode,
321 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode,
322 calcnodelines=calcnodelines)
322 calcnodelines=calcnodelines)
323 # util.sortdict().__setitem__ is much slower at replacing then inserting
323 # util.sortdict().__setitem__ is much slower at replacing then inserting
324 # new entries. The difference can matter if there are thousands of tags.
324 # new entries. The difference can matter if there are thousands of tags.
325 # Create a new sortdict to avoid the performance penalty.
325 # Create a new sortdict to avoid the performance penalty.
326 newtags = util.sortdict()
326 newtags = util.sortdict()
327 for tag, taghist in filetags.items():
327 for tag, taghist in filetags.items():
328 newtags[tag] = (taghist[-1], taghist[:-1])
328 newtags[tag] = (taghist[-1], taghist[:-1])
329 return newtags
329 return newtags
330
330
331 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
331 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
332 """Incorporate the tag info read from one file into dictionnaries
332 """Incorporate the tag info read from one file into dictionnaries
333
333
334 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
334 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
335
335
336 The second one, 'tagtypes', is optional and will be updated to track the
336 The second one, 'tagtypes', is optional and will be updated to track the
337 "tagtype" of entries in the tagmaps. When set, the 'tagtype' argument also
337 "tagtype" of entries in the tagmaps. When set, the 'tagtype' argument also
338 needs to be set."""
338 needs to be set."""
339 if tagtype is None:
339 if tagtype is None:
340 assert tagtypes is None
340 assert tagtypes is None
341
341
342 for name, nodehist in filetags.iteritems():
342 for name, nodehist in filetags.iteritems():
343 if name not in alltags:
343 if name not in alltags:
344 alltags[name] = nodehist
344 alltags[name] = nodehist
345 if tagtype is not None:
345 if tagtype is not None:
346 tagtypes[name] = tagtype
346 tagtypes[name] = tagtype
347 continue
347 continue
348
348
349 # we prefer alltags[name] if:
349 # we prefer alltags[name] if:
350 # it supersedes us OR
350 # it supersedes us OR
351 # mutual supersedes and it has a higher rank
351 # mutual supersedes and it has a higher rank
352 # otherwise we win because we're tip-most
352 # otherwise we win because we're tip-most
353 anode, ahist = nodehist
353 anode, ahist = nodehist
354 bnode, bhist = alltags[name]
354 bnode, bhist = alltags[name]
355 if (bnode != anode and anode in bhist and
355 if (bnode != anode and anode in bhist and
356 (bnode not in ahist or len(bhist) > len(ahist))):
356 (bnode not in ahist or len(bhist) > len(ahist))):
357 anode = bnode
357 anode = bnode
358 elif tagtype is not None:
358 elif tagtype is not None:
359 tagtypes[name] = tagtype
359 tagtypes[name] = tagtype
360 ahist.extend([n for n in bhist if n not in ahist])
360 ahist.extend([n for n in bhist if n not in ahist])
361 alltags[name] = anode, ahist
361 alltags[name] = anode, ahist
362
362
363 def _filename(repo):
363 def _filename(repo):
364 """name of a tagcache file for a given repo or repoview"""
364 """name of a tagcache file for a given repo or repoview"""
365 filename = 'tags2'
365 filename = 'tags2'
366 if repo.filtername:
366 if repo.filtername:
367 filename = '%s-%s' % (filename, repo.filtername)
367 filename = '%s-%s' % (filename, repo.filtername)
368 return filename
368 return filename
369
369
370 def _readtagcache(ui, repo):
370 def _readtagcache(ui, repo):
371 '''Read the tag cache.
371 '''Read the tag cache.
372
372
373 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
373 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
374
374
375 If the cache is completely up-to-date, "cachetags" is a dict of the
375 If the cache is completely up-to-date, "cachetags" is a dict of the
376 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
376 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
377 None and "shouldwrite" is False.
377 None and "shouldwrite" is False.
378
378
379 If the cache is not up to date, "cachetags" is None. "heads" is a list
379 If the cache is not up to date, "cachetags" is None. "heads" is a list
380 of all heads currently in the repository, ordered from tip to oldest.
380 of all heads currently in the repository, ordered from tip to oldest.
381 "validinfo" is a tuple describing cache validation info. This is used
381 "validinfo" is a tuple describing cache validation info. This is used
382 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
382 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
383 filenode. "shouldwrite" is True.
383 filenode. "shouldwrite" is True.
384
384
385 If the cache is not up to date, the caller is responsible for reading tag
385 If the cache is not up to date, the caller is responsible for reading tag
386 info from each returned head. (See findglobaltags().)
386 info from each returned head. (See findglobaltags().)
387 '''
387 '''
388 try:
388 try:
389 cachefile = repo.cachevfs(_filename(repo), 'r')
389 cachefile = repo.cachevfs(_filename(repo), 'r')
390 # force reading the file for static-http
390 # force reading the file for static-http
391 cachelines = iter(cachefile)
391 cachelines = iter(cachefile)
392 except IOError:
392 except IOError:
393 cachefile = None
393 cachefile = None
394
394
395 cacherev = None
395 cacherev = None
396 cachenode = None
396 cachenode = None
397 cachehash = None
397 cachehash = None
398 if cachefile:
398 if cachefile:
399 try:
399 try:
400 validline = next(cachelines)
400 validline = next(cachelines)
401 validline = validline.split()
401 validline = validline.split()
402 cacherev = int(validline[0])
402 cacherev = int(validline[0])
403 cachenode = bin(validline[1])
403 cachenode = bin(validline[1])
404 if len(validline) > 2:
404 if len(validline) > 2:
405 cachehash = bin(validline[2])
405 cachehash = bin(validline[2])
406 except Exception:
406 except Exception:
407 # corruption of the cache, just recompute it.
407 # corruption of the cache, just recompute it.
408 pass
408 pass
409
409
410 tipnode = repo.changelog.tip()
410 tipnode = repo.changelog.tip()
411 tiprev = len(repo.changelog) - 1
411 tiprev = len(repo.changelog) - 1
412
412
413 # Case 1 (common): tip is the same, so nothing has changed.
413 # Case 1 (common): tip is the same, so nothing has changed.
414 # (Unchanged tip trivially means no changesets have been added.
414 # (Unchanged tip trivially means no changesets have been added.
415 # But, thanks to localrepository.destroyed(), it also means none
415 # But, thanks to localrepository.destroyed(), it also means none
416 # have been destroyed by strip or rollback.)
416 # have been destroyed by strip or rollback.)
417 if (cacherev == tiprev
417 if (cacherev == tiprev
418 and cachenode == tipnode
418 and cachenode == tipnode
419 and cachehash == scmutil.filteredhash(repo, tiprev)):
419 and cachehash == scmutil.filteredhash(repo, tiprev)):
420 tags = _readtags(ui, repo, cachelines, cachefile.name)
420 tags = _readtags(ui, repo, cachelines, cachefile.name)
421 cachefile.close()
421 cachefile.close()
422 return (None, None, None, tags, False)
422 return (None, None, None, tags, False)
423 if cachefile:
423 if cachefile:
424 cachefile.close() # ignore rest of file
424 cachefile.close() # ignore rest of file
425
425
426 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
426 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
427
427
428 repoheads = repo.heads()
428 repoheads = repo.heads()
429 # Case 2 (uncommon): empty repo; get out quickly and don't bother
429 # Case 2 (uncommon): empty repo; get out quickly and don't bother
430 # writing an empty cache.
430 # writing an empty cache.
431 if repoheads == [nullid]:
431 if repoheads == [nullid]:
432 return ([], {}, valid, {}, False)
432 return ([], {}, valid, {}, False)
433
433
434 # Case 3 (uncommon): cache file missing or empty.
434 # Case 3 (uncommon): cache file missing or empty.
435
435
436 # Case 4 (uncommon): tip rev decreased. This should only happen
436 # Case 4 (uncommon): tip rev decreased. This should only happen
437 # when we're called from localrepository.destroyed(). Refresh the
437 # when we're called from localrepository.destroyed(). Refresh the
438 # cache so future invocations will not see disappeared heads in the
438 # cache so future invocations will not see disappeared heads in the
439 # cache.
439 # cache.
440
440
441 # Case 5 (common): tip has changed, so we've added/replaced heads.
441 # Case 5 (common): tip has changed, so we've added/replaced heads.
442
442
443 # As it happens, the code to handle cases 3, 4, 5 is the same.
443 # As it happens, the code to handle cases 3, 4, 5 is the same.
444
444
445 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
445 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
446 # exposed".
446 # exposed".
447 if not len(repo.file('.hgtags')):
447 if not len(repo.file('.hgtags')):
448 # No tags have ever been committed, so we can avoid a
448 # No tags have ever been committed, so we can avoid a
449 # potentially expensive search.
449 # potentially expensive search.
450 return ([], {}, valid, None, True)
450 return ([], {}, valid, None, True)
451
451
452
452
453 # Now we have to lookup the .hgtags filenode for every new head.
453 # Now we have to lookup the .hgtags filenode for every new head.
454 # This is the most expensive part of finding tags, so performance
454 # This is the most expensive part of finding tags, so performance
455 # depends primarily on the size of newheads. Worst case: no cache
455 # depends primarily on the size of newheads. Worst case: no cache
456 # file, so newheads == repoheads.
456 # file, so newheads == repoheads.
457 cachefnode = _getfnodes(ui, repo, repoheads)
457 cachefnode = _getfnodes(ui, repo, repoheads)
458
458
459 # Caller has to iterate over all heads, but can use the filenodes in
459 # Caller has to iterate over all heads, but can use the filenodes in
460 # cachefnode to get to each .hgtags revision quickly.
460 # cachefnode to get to each .hgtags revision quickly.
461 return (repoheads, cachefnode, valid, None, True)
461 return (repoheads, cachefnode, valid, None, True)
462
462
463 def _getfnodes(ui, repo, nodes):
463 def _getfnodes(ui, repo, nodes):
464 """return .hgtags fnodes for a list of changeset nodes
464 """return .hgtags fnodes for a list of changeset nodes
465
465
466 Return value is a {node: fnode} mapping. There will be no entry for nodes
466 Return value is a {node: fnode} mapping. There will be no entry for nodes
467 without a '.hgtags' file.
467 without a '.hgtags' file.
468 """
468 """
469 starttime = util.timer()
469 starttime = util.timer()
470 fnodescache = hgtagsfnodescache(repo.unfiltered())
470 fnodescache = hgtagsfnodescache(repo.unfiltered())
471 cachefnode = {}
471 cachefnode = {}
472 for node in reversed(nodes):
472 for node in reversed(nodes):
473 fnode = fnodescache.getfnode(node)
473 fnode = fnodescache.getfnode(node)
474 if fnode != nullid:
474 if fnode != nullid:
475 cachefnode[node] = fnode
475 cachefnode[node] = fnode
476
476
477 fnodescache.write()
477 fnodescache.write()
478
478
479 duration = util.timer() - starttime
479 duration = util.timer() - starttime
480 ui.log('tagscache',
480 ui.log('tagscache',
481 '%d/%d cache hits/lookups in %0.4f '
481 '%d/%d cache hits/lookups in %0.4f '
482 'seconds\n',
482 'seconds\n',
483 fnodescache.hitcount, fnodescache.lookupcount, duration)
483 fnodescache.hitcount, fnodescache.lookupcount, duration)
484 return cachefnode
484 return cachefnode
485
485
486 def _writetagcache(ui, repo, valid, cachetags):
486 def _writetagcache(ui, repo, valid, cachetags):
487 filename = _filename(repo)
487 filename = _filename(repo)
488 try:
488 try:
489 cachefile = repo.cachevfs(filename, 'w', atomictemp=True)
489 cachefile = repo.cachevfs(filename, 'w', atomictemp=True)
490 except (OSError, IOError):
490 except (OSError, IOError):
491 return
491 return
492
492
493 ui.log('tagscache', 'writing .hg/cache/%s with %d tags\n',
493 ui.log('tagscache', 'writing .hg/cache/%s with %d tags\n',
494 filename, len(cachetags))
494 filename, len(cachetags))
495
495
496 if valid[2]:
496 if valid[2]:
497 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
497 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
498 else:
498 else:
499 cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
499 cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
500
500
501 # Tag names in the cache are in UTF-8 -- which is the whole reason
501 # Tag names in the cache are in UTF-8 -- which is the whole reason
502 # we keep them in UTF-8 throughout this module. If we converted
502 # we keep them in UTF-8 throughout this module. If we converted
503 # them local encoding on input, we would lose info writing them to
503 # them local encoding on input, we would lose info writing them to
504 # the cache.
504 # the cache.
505 for (name, (node, hist)) in sorted(cachetags.iteritems()):
505 for (name, (node, hist)) in sorted(cachetags.iteritems()):
506 for n in hist:
506 for n in hist:
507 cachefile.write("%s %s\n" % (hex(n), name))
507 cachefile.write("%s %s\n" % (hex(n), name))
508 cachefile.write("%s %s\n" % (hex(node), name))
508 cachefile.write("%s %s\n" % (hex(node), name))
509
509
510 try:
510 try:
511 cachefile.close()
511 cachefile.close()
512 except (OSError, IOError):
512 except (OSError, IOError):
513 pass
513 pass
514
514
515 def tag(repo, names, node, message, local, user, date, editor=False):
515 def tag(repo, names, node, message, local, user, date, editor=False):
516 '''tag a revision with one or more symbolic names.
516 '''tag a revision with one or more symbolic names.
517
517
518 names is a list of strings or, when adding a single tag, names may be a
518 names is a list of strings or, when adding a single tag, names may be a
519 string.
519 string.
520
520
521 if local is True, the tags are stored in a per-repository file.
521 if local is True, the tags are stored in a per-repository file.
522 otherwise, they are stored in the .hgtags file, and a new
522 otherwise, they are stored in the .hgtags file, and a new
523 changeset is committed with the change.
523 changeset is committed with the change.
524
524
525 keyword arguments:
525 keyword arguments:
526
526
527 local: whether to store tags in non-version-controlled file
527 local: whether to store tags in non-version-controlled file
528 (default False)
528 (default False)
529
529
530 message: commit message to use if committing
530 message: commit message to use if committing
531
531
532 user: name of user to use if committing
532 user: name of user to use if committing
533
533
534 date: date tuple to use if committing'''
534 date: date tuple to use if committing'''
535
535
536 if not local:
536 if not local:
537 m = matchmod.exact(repo.root, '', ['.hgtags'])
537 m = matchmod.exact(repo.root, '', ['.hgtags'])
538 if any(repo.status(match=m, unknown=True, ignored=True)):
538 if any(repo.status(match=m, unknown=True, ignored=True)):
539 raise error.Abort(_('working copy of .hgtags is changed'),
539 raise error.Abort(_('working copy of .hgtags is changed'),
540 hint=_('please commit .hgtags manually'))
540 hint=_('please commit .hgtags manually'))
541
541
542 with repo.wlock():
542 with repo.wlock():
543 repo.tags() # instantiate the cache
543 repo.tags() # instantiate the cache
544 _tag(repo.unfiltered(), names, node, message, local, user, date,
544 _tag(repo, names, node, message, local, user, date,
545 editor=editor)
545 editor=editor)
546
546
547 def _tag(repo, names, node, message, local, user, date, extra=None,
547 def _tag(repo, names, node, message, local, user, date, extra=None,
548 editor=False):
548 editor=False):
549 if isinstance(names, str):
549 if isinstance(names, str):
550 names = (names,)
550 names = (names,)
551
551
552 branches = repo.branchmap()
552 branches = repo.branchmap()
553 for name in names:
553 for name in names:
554 repo.hook('pretag', throw=True, node=hex(node), tag=name,
554 repo.hook('pretag', throw=True, node=hex(node), tag=name,
555 local=local)
555 local=local)
556 if name in branches:
556 if name in branches:
557 repo.ui.warn(_("warning: tag %s conflicts with existing"
557 repo.ui.warn(_("warning: tag %s conflicts with existing"
558 " branch name\n") % name)
558 " branch name\n") % name)
559
559
560 def writetags(fp, names, munge, prevtags):
560 def writetags(fp, names, munge, prevtags):
561 fp.seek(0, 2)
561 fp.seek(0, 2)
562 if prevtags and prevtags[-1] != '\n':
562 if prevtags and prevtags[-1] != '\n':
563 fp.write('\n')
563 fp.write('\n')
564 for name in names:
564 for name in names:
565 if munge:
565 if munge:
566 m = munge(name)
566 m = munge(name)
567 else:
567 else:
568 m = name
568 m = name
569
569
570 if (repo._tagscache.tagtypes and
570 if (repo._tagscache.tagtypes and
571 name in repo._tagscache.tagtypes):
571 name in repo._tagscache.tagtypes):
572 old = repo.tags().get(name, nullid)
572 old = repo.tags().get(name, nullid)
573 fp.write('%s %s\n' % (hex(old), m))
573 fp.write('%s %s\n' % (hex(old), m))
574 fp.write('%s %s\n' % (hex(node), m))
574 fp.write('%s %s\n' % (hex(node), m))
575 fp.close()
575 fp.close()
576
576
577 prevtags = ''
577 prevtags = ''
578 if local:
578 if local:
579 try:
579 try:
580 fp = repo.vfs('localtags', 'r+')
580 fp = repo.vfs('localtags', 'r+')
581 except IOError:
581 except IOError:
582 fp = repo.vfs('localtags', 'a')
582 fp = repo.vfs('localtags', 'a')
583 else:
583 else:
584 prevtags = fp.read()
584 prevtags = fp.read()
585
585
586 # local tags are stored in the current charset
586 # local tags are stored in the current charset
587 writetags(fp, names, None, prevtags)
587 writetags(fp, names, None, prevtags)
588 for name in names:
588 for name in names:
589 repo.hook('tag', node=hex(node), tag=name, local=local)
589 repo.hook('tag', node=hex(node), tag=name, local=local)
590 return
590 return
591
591
592 try:
592 try:
593 fp = repo.wvfs('.hgtags', 'rb+')
593 fp = repo.wvfs('.hgtags', 'rb+')
594 except IOError as e:
594 except IOError as e:
595 if e.errno != errno.ENOENT:
595 if e.errno != errno.ENOENT:
596 raise
596 raise
597 fp = repo.wvfs('.hgtags', 'ab')
597 fp = repo.wvfs('.hgtags', 'ab')
598 else:
598 else:
599 prevtags = fp.read()
599 prevtags = fp.read()
600
600
601 # committed tags are stored in UTF-8
601 # committed tags are stored in UTF-8
602 writetags(fp, names, encoding.fromlocal, prevtags)
602 writetags(fp, names, encoding.fromlocal, prevtags)
603
603
604 fp.close()
604 fp.close()
605
605
606 repo.invalidatecaches()
606 repo.invalidatecaches()
607
607
608 if '.hgtags' not in repo.dirstate:
608 if '.hgtags' not in repo.dirstate:
609 repo[None].add(['.hgtags'])
609 repo[None].add(['.hgtags'])
610
610
611 m = matchmod.exact(repo.root, '', ['.hgtags'])
611 m = matchmod.exact(repo.root, '', ['.hgtags'])
612 tagnode = repo.commit(message, user, date, extra=extra, match=m,
612 tagnode = repo.commit(message, user, date, extra=extra, match=m,
613 editor=editor)
613 editor=editor)
614
614
615 for name in names:
615 for name in names:
616 repo.hook('tag', node=hex(node), tag=name, local=local)
616 repo.hook('tag', node=hex(node), tag=name, local=local)
617
617
618 return tagnode
618 return tagnode
619
619
620 _fnodescachefile = 'hgtagsfnodes1'
620 _fnodescachefile = 'hgtagsfnodes1'
621 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
621 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
622 _fnodesmissingrec = '\xff' * 24
622 _fnodesmissingrec = '\xff' * 24
623
623
624 class hgtagsfnodescache(object):
624 class hgtagsfnodescache(object):
625 """Persistent cache mapping revisions to .hgtags filenodes.
625 """Persistent cache mapping revisions to .hgtags filenodes.
626
626
627 The cache is an array of records. Each item in the array corresponds to
627 The cache is an array of records. Each item in the array corresponds to
628 a changelog revision. Values in the array contain the first 4 bytes of
628 a changelog revision. Values in the array contain the first 4 bytes of
629 the node hash and the 20 bytes .hgtags filenode for that revision.
629 the node hash and the 20 bytes .hgtags filenode for that revision.
630
630
631 The first 4 bytes are present as a form of verification. Repository
631 The first 4 bytes are present as a form of verification. Repository
632 stripping and rewriting may change the node at a numeric revision in the
632 stripping and rewriting may change the node at a numeric revision in the
633 changelog. The changeset fragment serves as a verifier to detect
633 changelog. The changeset fragment serves as a verifier to detect
634 rewriting. This logic is shared with the rev branch cache (see
634 rewriting. This logic is shared with the rev branch cache (see
635 branchmap.py).
635 branchmap.py).
636
636
637 The instance holds in memory the full cache content but entries are
637 The instance holds in memory the full cache content but entries are
638 only parsed on read.
638 only parsed on read.
639
639
640 Instances behave like lists. ``c[i]`` works where i is a rev or
640 Instances behave like lists. ``c[i]`` works where i is a rev or
641 changeset node. Missing indexes are populated automatically on access.
641 changeset node. Missing indexes are populated automatically on access.
642 """
642 """
643 def __init__(self, repo):
643 def __init__(self, repo):
644 assert repo.filtername is None
644 assert repo.filtername is None
645
645
646 self._repo = repo
646 self._repo = repo
647
647
648 # Only for reporting purposes.
648 # Only for reporting purposes.
649 self.lookupcount = 0
649 self.lookupcount = 0
650 self.hitcount = 0
650 self.hitcount = 0
651
651
652
652
653 try:
653 try:
654 data = repo.cachevfs.read(_fnodescachefile)
654 data = repo.cachevfs.read(_fnodescachefile)
655 except (OSError, IOError):
655 except (OSError, IOError):
656 data = ""
656 data = ""
657 self._raw = bytearray(data)
657 self._raw = bytearray(data)
658
658
659 # The end state of self._raw is an array that is of the exact length
659 # The end state of self._raw is an array that is of the exact length
660 # required to hold a record for every revision in the repository.
660 # required to hold a record for every revision in the repository.
661 # We truncate or extend the array as necessary. self._dirtyoffset is
661 # We truncate or extend the array as necessary. self._dirtyoffset is
662 # defined to be the start offset at which we need to write the output
662 # defined to be the start offset at which we need to write the output
663 # file. This offset is also adjusted when new entries are calculated
663 # file. This offset is also adjusted when new entries are calculated
664 # for array members.
664 # for array members.
665 cllen = len(repo.changelog)
665 cllen = len(repo.changelog)
666 wantedlen = cllen * _fnodesrecsize
666 wantedlen = cllen * _fnodesrecsize
667 rawlen = len(self._raw)
667 rawlen = len(self._raw)
668
668
669 self._dirtyoffset = None
669 self._dirtyoffset = None
670
670
671 if rawlen < wantedlen:
671 if rawlen < wantedlen:
672 self._dirtyoffset = rawlen
672 self._dirtyoffset = rawlen
673 self._raw.extend('\xff' * (wantedlen - rawlen))
673 self._raw.extend('\xff' * (wantedlen - rawlen))
674 elif rawlen > wantedlen:
674 elif rawlen > wantedlen:
675 # There's no easy way to truncate array instances. This seems
675 # There's no easy way to truncate array instances. This seems
676 # slightly less evil than copying a potentially large array slice.
676 # slightly less evil than copying a potentially large array slice.
677 for i in range(rawlen - wantedlen):
677 for i in range(rawlen - wantedlen):
678 self._raw.pop()
678 self._raw.pop()
679 self._dirtyoffset = len(self._raw)
679 self._dirtyoffset = len(self._raw)
680
680
681 def getfnode(self, node, computemissing=True):
681 def getfnode(self, node, computemissing=True):
682 """Obtain the filenode of the .hgtags file at a specified revision.
682 """Obtain the filenode of the .hgtags file at a specified revision.
683
683
684 If the value is in the cache, the entry will be validated and returned.
684 If the value is in the cache, the entry will be validated and returned.
685 Otherwise, the filenode will be computed and returned unless
685 Otherwise, the filenode will be computed and returned unless
686 "computemissing" is False, in which case None will be returned without
686 "computemissing" is False, in which case None will be returned without
687 any potentially expensive computation being performed.
687 any potentially expensive computation being performed.
688
688
689 If an .hgtags does not exist at the specified revision, nullid is
689 If an .hgtags does not exist at the specified revision, nullid is
690 returned.
690 returned.
691 """
691 """
692 ctx = self._repo[node]
692 ctx = self._repo[node]
693 rev = ctx.rev()
693 rev = ctx.rev()
694
694
695 self.lookupcount += 1
695 self.lookupcount += 1
696
696
697 offset = rev * _fnodesrecsize
697 offset = rev * _fnodesrecsize
698 record = '%s' % self._raw[offset:offset + _fnodesrecsize]
698 record = '%s' % self._raw[offset:offset + _fnodesrecsize]
699 properprefix = node[0:4]
699 properprefix = node[0:4]
700
700
701 # Validate and return existing entry.
701 # Validate and return existing entry.
702 if record != _fnodesmissingrec:
702 if record != _fnodesmissingrec:
703 fileprefix = record[0:4]
703 fileprefix = record[0:4]
704
704
705 if fileprefix == properprefix:
705 if fileprefix == properprefix:
706 self.hitcount += 1
706 self.hitcount += 1
707 return record[4:]
707 return record[4:]
708
708
709 # Fall through.
709 # Fall through.
710
710
711 # If we get here, the entry is either missing or invalid.
711 # If we get here, the entry is either missing or invalid.
712
712
713 if not computemissing:
713 if not computemissing:
714 return None
714 return None
715
715
716 # Populate missing entry.
716 # Populate missing entry.
717 try:
717 try:
718 fnode = ctx.filenode('.hgtags')
718 fnode = ctx.filenode('.hgtags')
719 except error.LookupError:
719 except error.LookupError:
720 # No .hgtags file on this revision.
720 # No .hgtags file on this revision.
721 fnode = nullid
721 fnode = nullid
722
722
723 self._writeentry(offset, properprefix, fnode)
723 self._writeentry(offset, properprefix, fnode)
724 return fnode
724 return fnode
725
725
726 def setfnode(self, node, fnode):
726 def setfnode(self, node, fnode):
727 """Set the .hgtags filenode for a given changeset."""
727 """Set the .hgtags filenode for a given changeset."""
728 assert len(fnode) == 20
728 assert len(fnode) == 20
729 ctx = self._repo[node]
729 ctx = self._repo[node]
730
730
731 # Do a lookup first to avoid writing if nothing has changed.
731 # Do a lookup first to avoid writing if nothing has changed.
732 if self.getfnode(ctx.node(), computemissing=False) == fnode:
732 if self.getfnode(ctx.node(), computemissing=False) == fnode:
733 return
733 return
734
734
735 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
735 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
736
736
737 def _writeentry(self, offset, prefix, fnode):
737 def _writeentry(self, offset, prefix, fnode):
738 # Slices on array instances only accept other array.
738 # Slices on array instances only accept other array.
739 entry = bytearray(prefix + fnode)
739 entry = bytearray(prefix + fnode)
740 self._raw[offset:offset + _fnodesrecsize] = entry
740 self._raw[offset:offset + _fnodesrecsize] = entry
741 # self._dirtyoffset could be None.
741 # self._dirtyoffset could be None.
742 self._dirtyoffset = min(self._dirtyoffset, offset) or 0
742 self._dirtyoffset = min(self._dirtyoffset, offset) or 0
743
743
744 def write(self):
744 def write(self):
745 """Perform all necessary writes to cache file.
745 """Perform all necessary writes to cache file.
746
746
747 This may no-op if no writes are needed or if a write lock could
747 This may no-op if no writes are needed or if a write lock could
748 not be obtained.
748 not be obtained.
749 """
749 """
750 if self._dirtyoffset is None:
750 if self._dirtyoffset is None:
751 return
751 return
752
752
753 data = self._raw[self._dirtyoffset:]
753 data = self._raw[self._dirtyoffset:]
754 if not data:
754 if not data:
755 return
755 return
756
756
757 repo = self._repo
757 repo = self._repo
758
758
759 try:
759 try:
760 lock = repo.wlock(wait=False)
760 lock = repo.wlock(wait=False)
761 except error.LockError:
761 except error.LockError:
762 repo.ui.log('tagscache', 'not writing .hg/cache/%s because '
762 repo.ui.log('tagscache', 'not writing .hg/cache/%s because '
763 'lock cannot be acquired\n' % (_fnodescachefile))
763 'lock cannot be acquired\n' % (_fnodescachefile))
764 return
764 return
765
765
766 try:
766 try:
767 f = repo.cachevfs.open(_fnodescachefile, 'ab')
767 f = repo.cachevfs.open(_fnodescachefile, 'ab')
768 try:
768 try:
769 # if the file has been truncated
769 # if the file has been truncated
770 actualoffset = f.tell()
770 actualoffset = f.tell()
771 if actualoffset < self._dirtyoffset:
771 if actualoffset < self._dirtyoffset:
772 self._dirtyoffset = actualoffset
772 self._dirtyoffset = actualoffset
773 data = self._raw[self._dirtyoffset:]
773 data = self._raw[self._dirtyoffset:]
774 f.seek(self._dirtyoffset)
774 f.seek(self._dirtyoffset)
775 f.truncate()
775 f.truncate()
776 repo.ui.log('tagscache',
776 repo.ui.log('tagscache',
777 'writing %d bytes to cache/%s\n' % (
777 'writing %d bytes to cache/%s\n' % (
778 len(data), _fnodescachefile))
778 len(data), _fnodescachefile))
779 f.write(data)
779 f.write(data)
780 self._dirtyoffset = None
780 self._dirtyoffset = None
781 finally:
781 finally:
782 f.close()
782 f.close()
783 except (IOError, OSError) as inst:
783 except (IOError, OSError) as inst:
784 repo.ui.log('tagscache',
784 repo.ui.log('tagscache',
785 "couldn't write cache/%s: %s\n" % (
785 "couldn't write cache/%s: %s\n" % (
786 _fnodescachefile, inst))
786 _fnodescachefile, inst))
787 finally:
787 finally:
788 lock.release()
788 lock.release()
@@ -1,749 +1,802
1 $ cat >> $HGRCPATH << EOF
1 $ cat >> $HGRCPATH << EOF
2 > [experimental]
2 > [experimental]
3 > hook-track-tags=1
3 > hook-track-tags=1
4 > [hooks]
4 > [hooks]
5 > txnclose.track-tag=sh ${TESTTMP}/taghook.sh
5 > txnclose.track-tag=sh ${TESTTMP}/taghook.sh
6 > EOF
6 > EOF
7
7
8 $ cat << EOF > taghook.sh
8 $ cat << EOF > taghook.sh
9 > #!/bin/sh
9 > #!/bin/sh
10 > # escape the "$" otherwise the test runner interpret it when writting the
10 > # escape the "$" otherwise the test runner interpret it when writting the
11 > # file...
11 > # file...
12 > if [ -n "\$HG_TAG_MOVED" ]; then
12 > if [ -n "\$HG_TAG_MOVED" ]; then
13 > echo 'hook: tag changes detected'
13 > echo 'hook: tag changes detected'
14 > sed 's/^/hook: /' .hg/changes/tags.changes
14 > sed 's/^/hook: /' .hg/changes/tags.changes
15 > fi
15 > fi
16 > EOF
16 > EOF
17 $ hg init test
17 $ hg init test
18 $ cd test
18 $ cd test
19
19
20 $ echo a > a
20 $ echo a > a
21 $ hg add a
21 $ hg add a
22 $ hg commit -m "test"
22 $ hg commit -m "test"
23 $ hg history
23 $ hg history
24 changeset: 0:acb14030fe0a
24 changeset: 0:acb14030fe0a
25 tag: tip
25 tag: tip
26 user: test
26 user: test
27 date: Thu Jan 01 00:00:00 1970 +0000
27 date: Thu Jan 01 00:00:00 1970 +0000
28 summary: test
28 summary: test
29
29
30
30
31 $ hg tag ' '
31 $ hg tag ' '
32 abort: tag names cannot consist entirely of whitespace
32 abort: tag names cannot consist entirely of whitespace
33 [255]
33 [255]
34
34
35 (this tests also that editor is not invoked, if '--edit' is not
35 (this tests also that editor is not invoked, if '--edit' is not
36 specified)
36 specified)
37
37
38 $ HGEDITOR=cat hg tag "bleah"
38 $ HGEDITOR=cat hg tag "bleah"
39 hook: tag changes detected
39 hook: tag changes detected
40 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
40 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
41 $ hg history
41 $ hg history
42 changeset: 1:d4f0d2909abc
42 changeset: 1:d4f0d2909abc
43 tag: tip
43 tag: tip
44 user: test
44 user: test
45 date: Thu Jan 01 00:00:00 1970 +0000
45 date: Thu Jan 01 00:00:00 1970 +0000
46 summary: Added tag bleah for changeset acb14030fe0a
46 summary: Added tag bleah for changeset acb14030fe0a
47
47
48 changeset: 0:acb14030fe0a
48 changeset: 0:acb14030fe0a
49 tag: bleah
49 tag: bleah
50 user: test
50 user: test
51 date: Thu Jan 01 00:00:00 1970 +0000
51 date: Thu Jan 01 00:00:00 1970 +0000
52 summary: test
52 summary: test
53
53
54
54
55 $ echo foo >> .hgtags
55 $ echo foo >> .hgtags
56 $ hg tag "bleah2"
56 $ hg tag "bleah2"
57 abort: working copy of .hgtags is changed
57 abort: working copy of .hgtags is changed
58 (please commit .hgtags manually)
58 (please commit .hgtags manually)
59 [255]
59 [255]
60
60
61 $ hg revert .hgtags
61 $ hg revert .hgtags
62 $ hg tag -r 0 x y z y y z
62 $ hg tag -r 0 x y z y y z
63 abort: tag names must be unique
63 abort: tag names must be unique
64 [255]
64 [255]
65 $ hg tag tap nada dot tip
65 $ hg tag tap nada dot tip
66 abort: the name 'tip' is reserved
66 abort: the name 'tip' is reserved
67 [255]
67 [255]
68 $ hg tag .
68 $ hg tag .
69 abort: the name '.' is reserved
69 abort: the name '.' is reserved
70 [255]
70 [255]
71 $ hg tag null
71 $ hg tag null
72 abort: the name 'null' is reserved
72 abort: the name 'null' is reserved
73 [255]
73 [255]
74 $ hg tag "bleah"
74 $ hg tag "bleah"
75 abort: tag 'bleah' already exists (use -f to force)
75 abort: tag 'bleah' already exists (use -f to force)
76 [255]
76 [255]
77 $ hg tag "blecch" "bleah"
77 $ hg tag "blecch" "bleah"
78 abort: tag 'bleah' already exists (use -f to force)
78 abort: tag 'bleah' already exists (use -f to force)
79 [255]
79 [255]
80
80
81 $ hg tag --remove "blecch"
81 $ hg tag --remove "blecch"
82 abort: tag 'blecch' does not exist
82 abort: tag 'blecch' does not exist
83 [255]
83 [255]
84 $ hg tag --remove "bleah" "blecch" "blough"
84 $ hg tag --remove "bleah" "blecch" "blough"
85 abort: tag 'blecch' does not exist
85 abort: tag 'blecch' does not exist
86 [255]
86 [255]
87
87
88 $ hg tag -r 0 "bleah0"
88 $ hg tag -r 0 "bleah0"
89 hook: tag changes detected
89 hook: tag changes detected
90 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
90 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
91 $ hg tag -l -r 1 "bleah1"
91 $ hg tag -l -r 1 "bleah1"
92 $ hg tag gack gawk gorp
92 $ hg tag gack gawk gorp
93 hook: tag changes detected
93 hook: tag changes detected
94 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gack
94 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gack
95 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gawk
95 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gawk
96 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gorp
96 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gorp
97 $ hg tag -f gack
97 $ hg tag -f gack
98 hook: tag changes detected
98 hook: tag changes detected
99 hook: -M 336fccc858a4eb69609a291105009e484a6b6b8d gack
99 hook: -M 336fccc858a4eb69609a291105009e484a6b6b8d gack
100 hook: +M 799667b6f2d9b957f73fa644a918c2df22bab58f gack
100 hook: +M 799667b6f2d9b957f73fa644a918c2df22bab58f gack
101 $ hg tag --remove gack gorp
101 $ hg tag --remove gack gorp
102 hook: tag changes detected
102 hook: tag changes detected
103 hook: -R 799667b6f2d9b957f73fa644a918c2df22bab58f gack
103 hook: -R 799667b6f2d9b957f73fa644a918c2df22bab58f gack
104 hook: -R 336fccc858a4eb69609a291105009e484a6b6b8d gorp
104 hook: -R 336fccc858a4eb69609a291105009e484a6b6b8d gorp
105
105
106 $ hg tag "bleah "
106 $ hg tag "bleah "
107 abort: tag 'bleah' already exists (use -f to force)
107 abort: tag 'bleah' already exists (use -f to force)
108 [255]
108 [255]
109 $ hg tag " bleah"
109 $ hg tag " bleah"
110 abort: tag 'bleah' already exists (use -f to force)
110 abort: tag 'bleah' already exists (use -f to force)
111 [255]
111 [255]
112 $ hg tag " bleah"
112 $ hg tag " bleah"
113 abort: tag 'bleah' already exists (use -f to force)
113 abort: tag 'bleah' already exists (use -f to force)
114 [255]
114 [255]
115 $ hg tag -r 0 " bleahbleah "
115 $ hg tag -r 0 " bleahbleah "
116 hook: tag changes detected
116 hook: tag changes detected
117 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
117 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
118 $ hg tag -r 0 " bleah bleah "
118 $ hg tag -r 0 " bleah bleah "
119 hook: tag changes detected
119 hook: tag changes detected
120 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
120 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
121
121
122 $ cat .hgtags
122 $ cat .hgtags
123 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
123 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
124 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
124 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
125 336fccc858a4eb69609a291105009e484a6b6b8d gack
125 336fccc858a4eb69609a291105009e484a6b6b8d gack
126 336fccc858a4eb69609a291105009e484a6b6b8d gawk
126 336fccc858a4eb69609a291105009e484a6b6b8d gawk
127 336fccc858a4eb69609a291105009e484a6b6b8d gorp
127 336fccc858a4eb69609a291105009e484a6b6b8d gorp
128 336fccc858a4eb69609a291105009e484a6b6b8d gack
128 336fccc858a4eb69609a291105009e484a6b6b8d gack
129 799667b6f2d9b957f73fa644a918c2df22bab58f gack
129 799667b6f2d9b957f73fa644a918c2df22bab58f gack
130 799667b6f2d9b957f73fa644a918c2df22bab58f gack
130 799667b6f2d9b957f73fa644a918c2df22bab58f gack
131 0000000000000000000000000000000000000000 gack
131 0000000000000000000000000000000000000000 gack
132 336fccc858a4eb69609a291105009e484a6b6b8d gorp
132 336fccc858a4eb69609a291105009e484a6b6b8d gorp
133 0000000000000000000000000000000000000000 gorp
133 0000000000000000000000000000000000000000 gorp
134 acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
134 acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
135 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
135 acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
136
136
137 $ cat .hg/localtags
137 $ cat .hg/localtags
138 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
138 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
139
139
140 tagging on a non-head revision
140 tagging on a non-head revision
141
141
142 $ hg update 0
142 $ hg update 0
143 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
143 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
144 $ hg tag -l localblah
144 $ hg tag -l localblah
145 $ hg tag "foobar"
145 $ hg tag "foobar"
146 abort: working directory is not at a branch head (use -f to force)
146 abort: working directory is not at a branch head (use -f to force)
147 [255]
147 [255]
148 $ hg tag -f "foobar"
148 $ hg tag -f "foobar"
149 hook: tag changes detected
149 hook: tag changes detected
150 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
150 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
151 $ cat .hgtags
151 $ cat .hgtags
152 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
152 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
153 $ cat .hg/localtags
153 $ cat .hg/localtags
154 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
154 d4f0d2909abc9290e2773c08837d70c1794e3f5a bleah1
155 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
155 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
156
156
157 $ hg tag -l 'xx
157 $ hg tag -l 'xx
158 > newline'
158 > newline'
159 abort: '\n' cannot be used in a name
159 abort: '\n' cannot be used in a name
160 [255]
160 [255]
161 $ hg tag -l 'xx:xx'
161 $ hg tag -l 'xx:xx'
162 abort: ':' cannot be used in a name
162 abort: ':' cannot be used in a name
163 [255]
163 [255]
164
164
165 cloning local tags
165 cloning local tags
166
166
167 $ cd ..
167 $ cd ..
168 $ hg -R test log -r0:5
168 $ hg -R test log -r0:5
169 changeset: 0:acb14030fe0a
169 changeset: 0:acb14030fe0a
170 tag: bleah
170 tag: bleah
171 tag: bleah bleah
171 tag: bleah bleah
172 tag: bleah0
172 tag: bleah0
173 tag: bleahbleah
173 tag: bleahbleah
174 tag: foobar
174 tag: foobar
175 tag: localblah
175 tag: localblah
176 user: test
176 user: test
177 date: Thu Jan 01 00:00:00 1970 +0000
177 date: Thu Jan 01 00:00:00 1970 +0000
178 summary: test
178 summary: test
179
179
180 changeset: 1:d4f0d2909abc
180 changeset: 1:d4f0d2909abc
181 tag: bleah1
181 tag: bleah1
182 user: test
182 user: test
183 date: Thu Jan 01 00:00:00 1970 +0000
183 date: Thu Jan 01 00:00:00 1970 +0000
184 summary: Added tag bleah for changeset acb14030fe0a
184 summary: Added tag bleah for changeset acb14030fe0a
185
185
186 changeset: 2:336fccc858a4
186 changeset: 2:336fccc858a4
187 tag: gawk
187 tag: gawk
188 user: test
188 user: test
189 date: Thu Jan 01 00:00:00 1970 +0000
189 date: Thu Jan 01 00:00:00 1970 +0000
190 summary: Added tag bleah0 for changeset acb14030fe0a
190 summary: Added tag bleah0 for changeset acb14030fe0a
191
191
192 changeset: 3:799667b6f2d9
192 changeset: 3:799667b6f2d9
193 user: test
193 user: test
194 date: Thu Jan 01 00:00:00 1970 +0000
194 date: Thu Jan 01 00:00:00 1970 +0000
195 summary: Added tag gack, gawk, gorp for changeset 336fccc858a4
195 summary: Added tag gack, gawk, gorp for changeset 336fccc858a4
196
196
197 changeset: 4:154eeb7c0138
197 changeset: 4:154eeb7c0138
198 user: test
198 user: test
199 date: Thu Jan 01 00:00:00 1970 +0000
199 date: Thu Jan 01 00:00:00 1970 +0000
200 summary: Added tag gack for changeset 799667b6f2d9
200 summary: Added tag gack for changeset 799667b6f2d9
201
201
202 changeset: 5:b4bb47aaff09
202 changeset: 5:b4bb47aaff09
203 user: test
203 user: test
204 date: Thu Jan 01 00:00:00 1970 +0000
204 date: Thu Jan 01 00:00:00 1970 +0000
205 summary: Removed tag gack, gorp
205 summary: Removed tag gack, gorp
206
206
207 $ hg clone -q -rbleah1 test test1
207 $ hg clone -q -rbleah1 test test1
208 hook: tag changes detected
208 hook: tag changes detected
209 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
209 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
210 $ hg -R test1 parents --style=compact
210 $ hg -R test1 parents --style=compact
211 1[tip] d4f0d2909abc 1970-01-01 00:00 +0000 test
211 1[tip] d4f0d2909abc 1970-01-01 00:00 +0000 test
212 Added tag bleah for changeset acb14030fe0a
212 Added tag bleah for changeset acb14030fe0a
213
213
214 $ hg clone -q -r5 test#bleah1 test2
214 $ hg clone -q -r5 test#bleah1 test2
215 hook: tag changes detected
215 hook: tag changes detected
216 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
216 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
217 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
217 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
218 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gawk
218 hook: +A 336fccc858a4eb69609a291105009e484a6b6b8d gawk
219 $ hg -R test2 parents --style=compact
219 $ hg -R test2 parents --style=compact
220 5[tip] b4bb47aaff09 1970-01-01 00:00 +0000 test
220 5[tip] b4bb47aaff09 1970-01-01 00:00 +0000 test
221 Removed tag gack, gorp
221 Removed tag gack, gorp
222
222
223 $ hg clone -q -U test#bleah1 test3
223 $ hg clone -q -U test#bleah1 test3
224 hook: tag changes detected
224 hook: tag changes detected
225 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
225 hook: +A acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
226 $ hg -R test3 parents --style=compact
226 $ hg -R test3 parents --style=compact
227
227
228 $ cd test
228 $ cd test
229
229
230 Issue601: hg tag doesn't do the right thing if .hgtags or localtags
230 Issue601: hg tag doesn't do the right thing if .hgtags or localtags
231 doesn't end with EOL
231 doesn't end with EOL
232
232
233 $ $PYTHON << EOF
233 $ $PYTHON << EOF
234 > f = file('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close()
234 > f = file('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close()
235 > f = file('.hg/localtags', 'w'); f.write(last); f.close()
235 > f = file('.hg/localtags', 'w'); f.write(last); f.close()
236 > EOF
236 > EOF
237 $ cat .hg/localtags; echo
237 $ cat .hg/localtags; echo
238 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
238 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
239 $ hg tag -l localnewline
239 $ hg tag -l localnewline
240 $ cat .hg/localtags; echo
240 $ cat .hg/localtags; echo
241 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
241 acb14030fe0a21b60322c440ad2d20cf7685a376 localblah
242 c2899151f4e76890c602a2597a650a72666681bf localnewline
242 c2899151f4e76890c602a2597a650a72666681bf localnewline
243
243
244
244
245 $ $PYTHON << EOF
245 $ $PYTHON << EOF
246 > f = file('.hgtags'); last = f.readlines()[-1][:-1]; f.close()
246 > f = file('.hgtags'); last = f.readlines()[-1][:-1]; f.close()
247 > f = file('.hgtags', 'w'); f.write(last); f.close()
247 > f = file('.hgtags', 'w'); f.write(last); f.close()
248 > EOF
248 > EOF
249 $ hg ci -m'broken manual edit of .hgtags'
249 $ hg ci -m'broken manual edit of .hgtags'
250 $ cat .hgtags; echo
250 $ cat .hgtags; echo
251 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
251 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
252 $ hg tag newline
252 $ hg tag newline
253 hook: tag changes detected
253 hook: tag changes detected
254 hook: +A a0eea09de1eeec777b46f2085260a373b2fbc293 newline
254 hook: +A a0eea09de1eeec777b46f2085260a373b2fbc293 newline
255 $ cat .hgtags; echo
255 $ cat .hgtags; echo
256 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
256 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
257 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
257 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
258
258
259
259
260 tag and branch using same name
260 tag and branch using same name
261
261
262 $ hg branch tag-and-branch-same-name
262 $ hg branch tag-and-branch-same-name
263 marked working directory as branch tag-and-branch-same-name
263 marked working directory as branch tag-and-branch-same-name
264 (branches are permanent and global, did you want a bookmark?)
264 (branches are permanent and global, did you want a bookmark?)
265 $ hg ci -m"discouraged"
265 $ hg ci -m"discouraged"
266 $ hg tag tag-and-branch-same-name
266 $ hg tag tag-and-branch-same-name
267 warning: tag tag-and-branch-same-name conflicts with existing branch name
267 warning: tag tag-and-branch-same-name conflicts with existing branch name
268 hook: tag changes detected
268 hook: tag changes detected
269 hook: +A fc93d2ea1cd78e91216c6cfbbf26747c10ce11ae tag-and-branch-same-name
269 hook: +A fc93d2ea1cd78e91216c6cfbbf26747c10ce11ae tag-and-branch-same-name
270
270
271 test custom commit messages
271 test custom commit messages
272
272
273 $ cat > editor.sh << '__EOF__'
273 $ cat > editor.sh << '__EOF__'
274 > echo "==== before editing"
274 > echo "==== before editing"
275 > cat "$1"
275 > cat "$1"
276 > echo "===="
276 > echo "===="
277 > echo "custom tag message" > "$1"
277 > echo "custom tag message" > "$1"
278 > echo "second line" >> "$1"
278 > echo "second line" >> "$1"
279 > __EOF__
279 > __EOF__
280
280
281 at first, test saving last-message.txt
281 at first, test saving last-message.txt
282
282
283 (test that editor is not invoked before transaction starting)
283 (test that editor is not invoked before transaction starting)
284
284
285 $ cat > .hg/hgrc << '__EOF__'
285 $ cat > .hg/hgrc << '__EOF__'
286 > [hooks]
286 > [hooks]
287 > # this failure occurs before editor invocation
287 > # this failure occurs before editor invocation
288 > pretag.test-saving-lastmessage = false
288 > pretag.test-saving-lastmessage = false
289 > __EOF__
289 > __EOF__
290 $ rm -f .hg/last-message.txt
290 $ rm -f .hg/last-message.txt
291 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
291 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
292 abort: pretag.test-saving-lastmessage hook exited with status 1
292 abort: pretag.test-saving-lastmessage hook exited with status 1
293 [255]
293 [255]
294 $ test -f .hg/last-message.txt
294 $ test -f .hg/last-message.txt
295 [1]
295 [1]
296
296
297 (test that editor is invoked and commit message is saved into
297 (test that editor is invoked and commit message is saved into
298 "last-message.txt")
298 "last-message.txt")
299
299
300 $ cat >> .hg/hgrc << '__EOF__'
300 $ cat >> .hg/hgrc << '__EOF__'
301 > [hooks]
301 > [hooks]
302 > pretag.test-saving-lastmessage =
302 > pretag.test-saving-lastmessage =
303 > # this failure occurs after editor invocation
303 > # this failure occurs after editor invocation
304 > pretxncommit.unexpectedabort = false
304 > pretxncommit.unexpectedabort = false
305 > __EOF__
305 > __EOF__
306
306
307 (this tests also that editor is invoked, if '--edit' is specified,
307 (this tests also that editor is invoked, if '--edit' is specified,
308 regardless of '--message')
308 regardless of '--message')
309
309
310 $ rm -f .hg/last-message.txt
310 $ rm -f .hg/last-message.txt
311 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e -m "foo bar"
311 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e -m "foo bar"
312 ==== before editing
312 ==== before editing
313 foo bar
313 foo bar
314
314
315
315
316 HG: Enter commit message. Lines beginning with 'HG:' are removed.
316 HG: Enter commit message. Lines beginning with 'HG:' are removed.
317 HG: Leave message empty to abort commit.
317 HG: Leave message empty to abort commit.
318 HG: --
318 HG: --
319 HG: user: test
319 HG: user: test
320 HG: branch 'tag-and-branch-same-name'
320 HG: branch 'tag-and-branch-same-name'
321 HG: changed .hgtags
321 HG: changed .hgtags
322 ====
322 ====
323 note: commit message saved in .hg/last-message.txt
323 note: commit message saved in .hg/last-message.txt
324 transaction abort!
324 transaction abort!
325 rollback completed
325 rollback completed
326 abort: pretxncommit.unexpectedabort hook exited with status 1
326 abort: pretxncommit.unexpectedabort hook exited with status 1
327 [255]
327 [255]
328 $ cat .hg/last-message.txt
328 $ cat .hg/last-message.txt
329 custom tag message
329 custom tag message
330 second line
330 second line
331
331
332 $ cat >> .hg/hgrc << '__EOF__'
332 $ cat >> .hg/hgrc << '__EOF__'
333 > [hooks]
333 > [hooks]
334 > pretxncommit.unexpectedabort =
334 > pretxncommit.unexpectedabort =
335 > __EOF__
335 > __EOF__
336 $ hg status .hgtags
336 $ hg status .hgtags
337 M .hgtags
337 M .hgtags
338 $ hg revert --no-backup -q .hgtags
338 $ hg revert --no-backup -q .hgtags
339
339
340 then, test custom commit message itself
340 then, test custom commit message itself
341
341
342 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
342 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg tag custom-tag -e
343 ==== before editing
343 ==== before editing
344 Added tag custom-tag for changeset 75a534207be6
344 Added tag custom-tag for changeset 75a534207be6
345
345
346
346
347 HG: Enter commit message. Lines beginning with 'HG:' are removed.
347 HG: Enter commit message. Lines beginning with 'HG:' are removed.
348 HG: Leave message empty to abort commit.
348 HG: Leave message empty to abort commit.
349 HG: --
349 HG: --
350 HG: user: test
350 HG: user: test
351 HG: branch 'tag-and-branch-same-name'
351 HG: branch 'tag-and-branch-same-name'
352 HG: changed .hgtags
352 HG: changed .hgtags
353 ====
353 ====
354 hook: tag changes detected
354 hook: tag changes detected
355 hook: +A 75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
355 hook: +A 75a534207be6b03576e0c7a4fa5708d045f1c876 custom-tag
356 $ hg log -l1 --template "{desc}\n"
356 $ hg log -l1 --template "{desc}\n"
357 custom tag message
357 custom tag message
358 second line
358 second line
359
359
360
360
361 local tag with .hgtags modified
361 local tag with .hgtags modified
362
362
363 $ hg tag hgtags-modified
363 $ hg tag hgtags-modified
364 hook: tag changes detected
364 hook: tag changes detected
365 hook: +A 0f26aaea6f74c3ed6c4aad8844403c9ba128d23a hgtags-modified
365 hook: +A 0f26aaea6f74c3ed6c4aad8844403c9ba128d23a hgtags-modified
366 $ hg rollback
366 $ hg rollback
367 repository tip rolled back to revision 13 (undo commit)
367 repository tip rolled back to revision 13 (undo commit)
368 working directory now based on revision 13
368 working directory now based on revision 13
369 $ hg st
369 $ hg st
370 M .hgtags
370 M .hgtags
371 ? .hgtags.orig
371 ? .hgtags.orig
372 ? editor.sh
372 ? editor.sh
373 $ hg tag --local baz
373 $ hg tag --local baz
374 $ hg revert --no-backup .hgtags
374 $ hg revert --no-backup .hgtags
375
375
376
376
377 tagging when at named-branch-head that's not a topo-head
377 tagging when at named-branch-head that's not a topo-head
378
378
379 $ hg up default
379 $ hg up default
380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 $ hg merge -t internal:local
381 $ hg merge -t internal:local
382 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
382 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
383 (branch merge, don't forget to commit)
383 (branch merge, don't forget to commit)
384 $ hg ci -m 'merge named branch'
384 $ hg ci -m 'merge named branch'
385 hook: tag changes detected
385 hook: tag changes detected
386 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
386 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah
387 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
387 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah bleah
388 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
388 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleah0
389 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
389 hook: -R acb14030fe0a21b60322c440ad2d20cf7685a376 bleahbleah
390 hook: -R 336fccc858a4eb69609a291105009e484a6b6b8d gawk
390 hook: -R 336fccc858a4eb69609a291105009e484a6b6b8d gawk
391 $ hg up 13
391 $ hg up 13
392 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
392 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 $ hg tag new-topo-head
393 $ hg tag new-topo-head
394 hook: tag changes detected
394 hook: tag changes detected
395 hook: +A 0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head
395 hook: +A 0f26aaea6f74c3ed6c4aad8844403c9ba128d23a new-topo-head
396
396
397 tagging on null rev
397 tagging on null rev
398
398
399 $ hg up null
399 $ hg up null
400 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
400 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
401 $ hg tag nullrev
401 $ hg tag nullrev
402 abort: working directory is not at a branch head (use -f to force)
402 abort: working directory is not at a branch head (use -f to force)
403 [255]
403 [255]
404
404
405 $ hg init empty
405 $ hg init empty
406 $ hg tag -R empty nullrev
406 $ hg tag -R empty nullrev
407 abort: cannot tag null revision
407 abort: cannot tag null revision
408 [255]
408 [255]
409
409
410 $ hg tag -R empty -r 00000000000 -f nulltag
410 $ hg tag -R empty -r 00000000000 -f nulltag
411 abort: cannot tag null revision
411 abort: cannot tag null revision
412 [255]
412 [255]
413
413
414 issue5539: pruned tags do not appear in .hgtags
415
416 $ cat >> $HGRCPATH << EOF
417 > [experimental]
418 > stabilization=createmarkers,exchange
419 > EOF
420 $ hg up e4d483960b9b --quiet
421 $ echo aaa >>a
422 $ hg ci -maaa
423 $ hg log -r . -T "{node}\n"
424 743b3afd5aa69f130c246806e48ad2e699f490d2
425 $ hg tag issue5539
426 hook: tag changes detected
427 hook: +A 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
428 $ cat .hgtags
429 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
430 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
431 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
432 $ hg log -r . -T "{node}\n"
433 abeb261f0508ecebcd345ce21e7a25112df417aa
434 (mimic 'hg prune' command by obsoleting current changeset and then moving to its parent)
435 $ hg debugobsolete abeb261f0508ecebcd345ce21e7a25112df417aa --record-parents
436 obsoleted 1 changesets
437 $ hg up ".^" --quiet
438 $ cat .hgtags
439 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
440 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
441 $ echo bbb >>a
442 $ hg ci -mbbb
443 $ hg log -r . -T "{node}\n"
444 089dd20da58cae34165c37b064539c6ac0c6a0dd
445 $ hg tag issue5539
446 hook: tag changes detected
447 hook: -M 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
448 hook: +M 089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
449 $ hg id
450 0accf560a709 tip
451 $ cat .hgtags
452 acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
453 a0eea09de1eeec777b46f2085260a373b2fbc293 newline
454 089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
455 $ hg tags
456 tip 19:0accf560a709
457 issue5539 18:089dd20da58c
458 new-topo-head 13:0f26aaea6f74
459 baz 13:0f26aaea6f74
460 custom-tag 12:75a534207be6
461 tag-and-branch-same-name 11:fc93d2ea1cd7
462 newline 9:a0eea09de1ee
463 localnewline 8:c2899151f4e7
464 localblah 0:acb14030fe0a
465 foobar 0:acb14030fe0a
466
414 $ cd ..
467 $ cd ..
415
468
416 tagging on an uncommitted merge (issue2542)
469 tagging on an uncommitted merge (issue2542)
417
470
418 $ hg init repo-tag-uncommitted-merge
471 $ hg init repo-tag-uncommitted-merge
419 $ cd repo-tag-uncommitted-merge
472 $ cd repo-tag-uncommitted-merge
420 $ echo c1 > f1
473 $ echo c1 > f1
421 $ hg ci -Am0
474 $ hg ci -Am0
422 adding f1
475 adding f1
423 $ echo c2 > f2
476 $ echo c2 > f2
424 $ hg ci -Am1
477 $ hg ci -Am1
425 adding f2
478 adding f2
426 $ hg co -q 0
479 $ hg co -q 0
427 $ hg branch b1
480 $ hg branch b1
428 marked working directory as branch b1
481 marked working directory as branch b1
429 (branches are permanent and global, did you want a bookmark?)
482 (branches are permanent and global, did you want a bookmark?)
430 $ hg ci -m2
483 $ hg ci -m2
431 $ hg up default
484 $ hg up default
432 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
485 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 $ hg merge b1
486 $ hg merge b1
434 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
487 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 (branch merge, don't forget to commit)
488 (branch merge, don't forget to commit)
436
489
437 $ hg tag t1
490 $ hg tag t1
438 abort: uncommitted merge
491 abort: uncommitted merge
439 [255]
492 [255]
440 $ hg status
493 $ hg status
441 $ hg tag --rev 1 t2
494 $ hg tag --rev 1 t2
442 abort: uncommitted merge
495 abort: uncommitted merge
443 [255]
496 [255]
444 $ hg tag --rev 1 --local t3
497 $ hg tag --rev 1 --local t3
445 $ hg tags -v
498 $ hg tags -v
446 tip 2:2a156e8887cc
499 tip 2:2a156e8887cc
447 t3 1:c3adabd1a5f4 local
500 t3 1:c3adabd1a5f4 local
448
501
449 $ cd ..
502 $ cd ..
450
503
451 commit hook on tag used to be run without write lock - issue3344
504 commit hook on tag used to be run without write lock - issue3344
452
505
453 $ hg init repo-tag
506 $ hg init repo-tag
454 $ touch repo-tag/test
507 $ touch repo-tag/test
455 $ hg -R repo-tag commit -A -m "test"
508 $ hg -R repo-tag commit -A -m "test"
456 adding test
509 adding test
457 $ hg init repo-tag-target
510 $ hg init repo-tag-target
458 $ cat > "$TESTTMP/issue3344.sh" <<EOF
511 $ cat > "$TESTTMP/issue3344.sh" <<EOF
459 > hg push "$TESTTMP/repo-tag-target"
512 > hg push "$TESTTMP/repo-tag-target"
460 > EOF
513 > EOF
461 $ hg -R repo-tag --config hooks.commit="sh ../issue3344.sh" tag tag
514 $ hg -R repo-tag --config hooks.commit="sh ../issue3344.sh" tag tag
462 hook: tag changes detected
515 hook: tag changes detected
463 hook: +A be090ea6625635128e90f7d89df8beeb2bcc1653 tag
516 hook: +A be090ea6625635128e90f7d89df8beeb2bcc1653 tag
464 pushing to $TESTTMP/repo-tag-target (glob)
517 pushing to $TESTTMP/repo-tag-target (glob)
465 searching for changes
518 searching for changes
466 adding changesets
519 adding changesets
467 adding manifests
520 adding manifests
468 adding file changes
521 adding file changes
469 added 2 changesets with 2 changes to 2 files
522 added 2 changesets with 2 changes to 2 files
470 hook: tag changes detected
523 hook: tag changes detected
471 hook: +A be090ea6625635128e90f7d89df8beeb2bcc1653 tag
524 hook: +A be090ea6625635128e90f7d89df8beeb2bcc1653 tag
472
525
473 automatically merge resolvable tag conflicts (i.e. tags that differ in rank)
526 automatically merge resolvable tag conflicts (i.e. tags that differ in rank)
474 create two clones with some different tags as well as some common tags
527 create two clones with some different tags as well as some common tags
475 check that we can merge tags that differ in rank
528 check that we can merge tags that differ in rank
476
529
477 $ hg init repo-automatic-tag-merge
530 $ hg init repo-automatic-tag-merge
478 $ cd repo-automatic-tag-merge
531 $ cd repo-automatic-tag-merge
479 $ echo c0 > f0
532 $ echo c0 > f0
480 $ hg ci -A -m0
533 $ hg ci -A -m0
481 adding f0
534 adding f0
482 $ hg tag tbase
535 $ hg tag tbase
483 hook: tag changes detected
536 hook: tag changes detected
484 hook: +A 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
537 hook: +A 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
485 $ hg up -qr '.^'
538 $ hg up -qr '.^'
486 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
539 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
487 1
540 1
488 $ hg up -q
541 $ hg up -q
489 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
542 $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
490 2
543 2
491 $ cd ..
544 $ cd ..
492 $ hg clone repo-automatic-tag-merge repo-automatic-tag-merge-clone
545 $ hg clone repo-automatic-tag-merge repo-automatic-tag-merge-clone
493 updating to branch default
546 updating to branch default
494 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
547 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
495 $ cd repo-automatic-tag-merge-clone
548 $ cd repo-automatic-tag-merge-clone
496 $ echo c1 > f1
549 $ echo c1 > f1
497 $ hg ci -A -m1
550 $ hg ci -A -m1
498 adding f1
551 adding f1
499 $ hg tag t1 t2 t3
552 $ hg tag t1 t2 t3
500 hook: tag changes detected
553 hook: tag changes detected
501 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
554 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
502 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
555 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
503 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
556 hook: +A 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
504 $ hg tag --remove t2
557 $ hg tag --remove t2
505 hook: tag changes detected
558 hook: tag changes detected
506 hook: -R 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
559 hook: -R 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
507 $ hg tag t5
560 $ hg tag t5
508 hook: tag changes detected
561 hook: tag changes detected
509 hook: +A 875517b4806a848f942811a315a5bce30804ae85 t5
562 hook: +A 875517b4806a848f942811a315a5bce30804ae85 t5
510 $ echo c2 > f2
563 $ echo c2 > f2
511 $ hg ci -A -m2
564 $ hg ci -A -m2
512 adding f2
565 adding f2
513 $ hg tag -f t3
566 $ hg tag -f t3
514 hook: tag changes detected
567 hook: tag changes detected
515 hook: -M 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
568 hook: -M 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
516 hook: +M 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
569 hook: +M 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
517
570
518 $ cd ../repo-automatic-tag-merge
571 $ cd ../repo-automatic-tag-merge
519 $ echo c3 > f3
572 $ echo c3 > f3
520 $ hg ci -A -m3
573 $ hg ci -A -m3
521 adding f3
574 adding f3
522 $ hg tag -f t4 t5 t6
575 $ hg tag -f t4 t5 t6
523 hook: tag changes detected
576 hook: tag changes detected
524 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
577 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
525 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
578 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
526 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
579 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
527
580
528 $ hg up -q '.^'
581 $ hg up -q '.^'
529 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
582 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
530 1 changes since t4:t5:t6
583 1 changes since t4:t5:t6
531 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
584 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
532 0 changes since t4:t5:t6
585 0 changes since t4:t5:t6
533 $ echo c5 > f3
586 $ echo c5 > f3
534 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
587 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
535 1 changes since t4:t5:t6
588 1 changes since t4:t5:t6
536 $ hg up -qC
589 $ hg up -qC
537
590
538 $ hg tag --remove t5
591 $ hg tag --remove t5
539 hook: tag changes detected
592 hook: tag changes detected
540 hook: -R 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
593 hook: -R 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
541 $ echo c4 > f4
594 $ echo c4 > f4
542 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
595 $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
543 2 changes since t4:t6
596 2 changes since t4:t6
544 $ hg log -r '.' -T "{latesttag % '{latesttag}\n'}"
597 $ hg log -r '.' -T "{latesttag % '{latesttag}\n'}"
545 t4
598 t4
546 t6
599 t6
547 $ hg log -r '.' -T "{latesttag('t4') % 'T: {tag}, C: {changes}, D: {distance}\n'}"
600 $ hg log -r '.' -T "{latesttag('t4') % 'T: {tag}, C: {changes}, D: {distance}\n'}"
548 T: t4, C: 2, D: 2
601 T: t4, C: 2, D: 2
549 $ hg log -r '.' -T "{latesttag('re:\d') % 'T: {tag}, C: {changes}, D: {distance}\n'}"
602 $ hg log -r '.' -T "{latesttag('re:\d') % 'T: {tag}, C: {changes}, D: {distance}\n'}"
550 T: t4, C: 2, D: 2
603 T: t4, C: 2, D: 2
551 T: t6, C: 2, D: 2
604 T: t6, C: 2, D: 2
552 $ hg log -r . -T '{join(latesttag(), "*")}\n'
605 $ hg log -r . -T '{join(latesttag(), "*")}\n'
553 t4*t6
606 t4*t6
554 $ hg ci -A -m4
607 $ hg ci -A -m4
555 adding f4
608 adding f4
556 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
609 $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
557 4 changes since t4:t6
610 4 changes since t4:t6
558 $ hg tag t2
611 $ hg tag t2
559 hook: tag changes detected
612 hook: tag changes detected
560 hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
613 hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
561 $ hg tag -f t6
614 $ hg tag -f t6
562 hook: tag changes detected
615 hook: tag changes detected
563 hook: -M 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
616 hook: -M 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
564 hook: +M 09af2ce14077a94effef208b49a718f4836d4338 t6
617 hook: +M 09af2ce14077a94effef208b49a718f4836d4338 t6
565
618
566 $ cd ../repo-automatic-tag-merge-clone
619 $ cd ../repo-automatic-tag-merge-clone
567 $ hg pull
620 $ hg pull
568 pulling from $TESTTMP/repo-automatic-tag-merge (glob)
621 pulling from $TESTTMP/repo-automatic-tag-merge (glob)
569 searching for changes
622 searching for changes
570 adding changesets
623 adding changesets
571 adding manifests
624 adding manifests
572 adding file changes
625 adding file changes
573 added 6 changesets with 6 changes to 3 files (+1 heads)
626 added 6 changesets with 6 changes to 3 files (+1 heads)
574 hook: tag changes detected
627 hook: tag changes detected
575 hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
628 hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
576 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
629 hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
577 hook: -R 875517b4806a848f942811a315a5bce30804ae85 t5
630 hook: -R 875517b4806a848f942811a315a5bce30804ae85 t5
578 hook: +A 09af2ce14077a94effef208b49a718f4836d4338 t6
631 hook: +A 09af2ce14077a94effef208b49a718f4836d4338 t6
579 (run 'hg heads' to see heads, 'hg merge' to merge)
632 (run 'hg heads' to see heads, 'hg merge' to merge)
580 $ hg merge --tool internal:tagmerge
633 $ hg merge --tool internal:tagmerge
581 merging .hgtags
634 merging .hgtags
582 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
635 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
583 (branch merge, don't forget to commit)
636 (branch merge, don't forget to commit)
584 $ hg status
637 $ hg status
585 M .hgtags
638 M .hgtags
586 M f3
639 M f3
587 M f4
640 M f4
588 $ hg resolve -l
641 $ hg resolve -l
589 R .hgtags
642 R .hgtags
590 $ cat .hgtags
643 $ cat .hgtags
591 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
644 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
592 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
645 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
593 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
646 9aa4e1292a27a248f8d07339bed9931d54907be7 t6
594 09af2ce14077a94effef208b49a718f4836d4338 t6
647 09af2ce14077a94effef208b49a718f4836d4338 t6
595 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
648 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
596 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
649 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
597 929bca7b18d067cbf3844c3896319a940059d748 t2
650 929bca7b18d067cbf3844c3896319a940059d748 t2
598 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
651 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
599 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
652 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
600 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
653 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
601 0000000000000000000000000000000000000000 t2
654 0000000000000000000000000000000000000000 t2
602 875517b4806a848f942811a315a5bce30804ae85 t5
655 875517b4806a848f942811a315a5bce30804ae85 t5
603 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
656 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
604 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
657 9aa4e1292a27a248f8d07339bed9931d54907be7 t5
605 0000000000000000000000000000000000000000 t5
658 0000000000000000000000000000000000000000 t5
606 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
659 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
607 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
660 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
608
661
609 check that the merge tried to minimize the diff with the first merge parent
662 check that the merge tried to minimize the diff with the first merge parent
610
663
611 $ hg diff --git -r 'p1()' .hgtags
664 $ hg diff --git -r 'p1()' .hgtags
612 diff --git a/.hgtags b/.hgtags
665 diff --git a/.hgtags b/.hgtags
613 --- a/.hgtags
666 --- a/.hgtags
614 +++ b/.hgtags
667 +++ b/.hgtags
615 @@ -1,9 +1,17 @@
668 @@ -1,9 +1,17 @@
616 +9aa4e1292a27a248f8d07339bed9931d54907be7 t4
669 +9aa4e1292a27a248f8d07339bed9931d54907be7 t4
617 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
670 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
618 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
671 +9aa4e1292a27a248f8d07339bed9931d54907be7 t6
619 +09af2ce14077a94effef208b49a718f4836d4338 t6
672 +09af2ce14077a94effef208b49a718f4836d4338 t6
620 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
673 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
621 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
674 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
622 +929bca7b18d067cbf3844c3896319a940059d748 t2
675 +929bca7b18d067cbf3844c3896319a940059d748 t2
623 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
676 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
624 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
677 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
625 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
678 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
626 0000000000000000000000000000000000000000 t2
679 0000000000000000000000000000000000000000 t2
627 875517b4806a848f942811a315a5bce30804ae85 t5
680 875517b4806a848f942811a315a5bce30804ae85 t5
628 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
681 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
629 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
682 +9aa4e1292a27a248f8d07339bed9931d54907be7 t5
630 +0000000000000000000000000000000000000000 t5
683 +0000000000000000000000000000000000000000 t5
631 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
684 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
632 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
685 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
633
686
634 detect merge tag conflicts
687 detect merge tag conflicts
635
688
636 $ hg update -C -r tip
689 $ hg update -C -r tip
637 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
690 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
638 $ hg tag t7
691 $ hg tag t7
639 hook: tag changes detected
692 hook: tag changes detected
640 hook: +A b325cc5b642c5b465bdbe8c09627cb372de3d47d t7
693 hook: +A b325cc5b642c5b465bdbe8c09627cb372de3d47d t7
641 $ hg update -C -r 'first(sort(head()))'
694 $ hg update -C -r 'first(sort(head()))'
642 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
695 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
643 $ printf "%s %s\n" `hg log -r . --template "{node} t7"` >> .hgtags
696 $ printf "%s %s\n" `hg log -r . --template "{node} t7"` >> .hgtags
644 $ hg commit -m "manually add conflicting t7 tag"
697 $ hg commit -m "manually add conflicting t7 tag"
645 hook: tag changes detected
698 hook: tag changes detected
646 hook: -R 929bca7b18d067cbf3844c3896319a940059d748 t2
699 hook: -R 929bca7b18d067cbf3844c3896319a940059d748 t2
647 hook: +A 875517b4806a848f942811a315a5bce30804ae85 t5
700 hook: +A 875517b4806a848f942811a315a5bce30804ae85 t5
648 hook: -M b325cc5b642c5b465bdbe8c09627cb372de3d47d t7
701 hook: -M b325cc5b642c5b465bdbe8c09627cb372de3d47d t7
649 hook: +M ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
702 hook: +M ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
650 $ hg merge --tool internal:tagmerge
703 $ hg merge --tool internal:tagmerge
651 merging .hgtags
704 merging .hgtags
652 automatic .hgtags merge failed
705 automatic .hgtags merge failed
653 the following 1 tags are in conflict: t7
706 the following 1 tags are in conflict: t7
654 automatic tag merging of .hgtags failed! (use 'hg resolve --tool :merge' or another merge tool of your choice)
707 automatic tag merging of .hgtags failed! (use 'hg resolve --tool :merge' or another merge tool of your choice)
655 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
708 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
656 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
709 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
657 [1]
710 [1]
658 $ hg resolve -l
711 $ hg resolve -l
659 U .hgtags
712 U .hgtags
660 $ cat .hgtags
713 $ cat .hgtags
661 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
714 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
662 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
715 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
663 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
716 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
664 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
717 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
665 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
718 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
666 0000000000000000000000000000000000000000 t2
719 0000000000000000000000000000000000000000 t2
667 875517b4806a848f942811a315a5bce30804ae85 t5
720 875517b4806a848f942811a315a5bce30804ae85 t5
668 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
721 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
669 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
722 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
670 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
723 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
671
724
672 $ cd ..
725 $ cd ..
673
726
674 handle the loss of tags
727 handle the loss of tags
675
728
676 $ hg clone repo-automatic-tag-merge-clone repo-merge-lost-tags
729 $ hg clone repo-automatic-tag-merge-clone repo-merge-lost-tags
677 updating to branch default
730 updating to branch default
678 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
731 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
679 $ cd repo-merge-lost-tags
732 $ cd repo-merge-lost-tags
680 $ echo c5 > f5
733 $ echo c5 > f5
681 $ hg ci -A -m5
734 $ hg ci -A -m5
682 adding f5
735 adding f5
683 $ hg tag -f t7
736 $ hg tag -f t7
684 hook: tag changes detected
737 hook: tag changes detected
685 hook: -M ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
738 hook: -M ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
686 hook: +M fd3a9e394ce3afb354a496323bf68ac1755a30de t7
739 hook: +M fd3a9e394ce3afb354a496323bf68ac1755a30de t7
687 $ hg update -r 'p1(t7)'
740 $ hg update -r 'p1(t7)'
688 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
741 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
689 $ printf '' > .hgtags
742 $ printf '' > .hgtags
690 $ hg commit -m 'delete all tags'
743 $ hg commit -m 'delete all tags'
691 created new head
744 created new head
692 $ hg log -r 'max(t7::)'
745 $ hg log -r 'max(t7::)'
693 changeset: 17:ffe462b50880
746 changeset: 17:ffe462b50880
694 user: test
747 user: test
695 date: Thu Jan 01 00:00:00 1970 +0000
748 date: Thu Jan 01 00:00:00 1970 +0000
696 summary: Added tag t7 for changeset fd3a9e394ce3
749 summary: Added tag t7 for changeset fd3a9e394ce3
697
750
698 $ hg update -r 'max(t7::)'
751 $ hg update -r 'max(t7::)'
699 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
752 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
700 $ hg merge -r tip --tool internal:tagmerge
753 $ hg merge -r tip --tool internal:tagmerge
701 merging .hgtags
754 merging .hgtags
702 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
755 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
703 (branch merge, don't forget to commit)
756 (branch merge, don't forget to commit)
704 $ hg resolve -l
757 $ hg resolve -l
705 R .hgtags
758 R .hgtags
706 $ cat .hgtags
759 $ cat .hgtags
707 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
760 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
708 0000000000000000000000000000000000000000 tbase
761 0000000000000000000000000000000000000000 tbase
709 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
762 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
710 0000000000000000000000000000000000000000 t1
763 0000000000000000000000000000000000000000 t1
711 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
764 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
712 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
765 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
713 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
766 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
714 0000000000000000000000000000000000000000 t2
767 0000000000000000000000000000000000000000 t2
715 875517b4806a848f942811a315a5bce30804ae85 t5
768 875517b4806a848f942811a315a5bce30804ae85 t5
716 0000000000000000000000000000000000000000 t5
769 0000000000000000000000000000000000000000 t5
717 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
770 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
718 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
771 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
719 0000000000000000000000000000000000000000 t3
772 0000000000000000000000000000000000000000 t3
720 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
773 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
721 0000000000000000000000000000000000000000 t7
774 0000000000000000000000000000000000000000 t7
722 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
775 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
723 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
776 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
724
777
725 also check that we minimize the diff with the 1st merge parent
778 also check that we minimize the diff with the 1st merge parent
726
779
727 $ hg diff --git -r 'p1()' .hgtags
780 $ hg diff --git -r 'p1()' .hgtags
728 diff --git a/.hgtags b/.hgtags
781 diff --git a/.hgtags b/.hgtags
729 --- a/.hgtags
782 --- a/.hgtags
730 +++ b/.hgtags
783 +++ b/.hgtags
731 @@ -1,12 +1,17 @@
784 @@ -1,12 +1,17 @@
732 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
785 6cee5c8f3e5b4ae1a3996d2f6489c3e08eb5aea7 tbase
733 +0000000000000000000000000000000000000000 tbase
786 +0000000000000000000000000000000000000000 tbase
734 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
787 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t1
735 +0000000000000000000000000000000000000000 t1
788 +0000000000000000000000000000000000000000 t1
736 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
789 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
737 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
790 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
738 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
791 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t2
739 0000000000000000000000000000000000000000 t2
792 0000000000000000000000000000000000000000 t2
740 875517b4806a848f942811a315a5bce30804ae85 t5
793 875517b4806a848f942811a315a5bce30804ae85 t5
741 +0000000000000000000000000000000000000000 t5
794 +0000000000000000000000000000000000000000 t5
742 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
795 4f3e9b90005b68b4d8a3f4355cedc302a8364f5c t3
743 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
796 79505d5360b07e3e79d1052e347e73c02b8afa5b t3
744 +0000000000000000000000000000000000000000 t3
797 +0000000000000000000000000000000000000000 t3
745 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
798 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
746 +0000000000000000000000000000000000000000 t7
799 +0000000000000000000000000000000000000000 t7
747 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
800 ea918d56be86a4afc5a95312e8b6750e1428d9d2 t7
748 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
801 fd3a9e394ce3afb354a496323bf68ac1755a30de t7
749
802
General Comments 0
You need to be logged in to leave comments. Login now