##// END OF EJS Templates
tags: fix typo in fast path detection of fnode resolution (issue6673)...
Yuya Nishihara -
r49846:d4b66dc5 stable
parent child Browse files
Show More
@@ -1,915 +1,915 b''
1 # tags.py - read tag info from local repository
1 # tags.py - read tag info from local repository
2 #
2 #
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2009 Olivia Mackall <olivia@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 import io
16 import io
17
17
18 from .node import (
18 from .node import (
19 bin,
19 bin,
20 hex,
20 hex,
21 nullrev,
21 nullrev,
22 short,
22 short,
23 )
23 )
24 from .i18n import _
24 from .i18n import _
25 from . import (
25 from . import (
26 encoding,
26 encoding,
27 error,
27 error,
28 match as matchmod,
28 match as matchmod,
29 pycompat,
29 pycompat,
30 scmutil,
30 scmutil,
31 util,
31 util,
32 )
32 )
33 from .utils import stringutil
33 from .utils import stringutil
34
34
35 # Tags computation can be expensive and caches exist to make it fast in
35 # Tags computation can be expensive and caches exist to make it fast in
36 # the common case.
36 # the common case.
37 #
37 #
38 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
38 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
39 # each revision in the repository. The file is effectively an array of
39 # each revision in the repository. The file is effectively an array of
40 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
40 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
41 # details.
41 # details.
42 #
42 #
43 # The .hgtags filenode cache grows in proportion to the length of the
43 # The .hgtags filenode cache grows in proportion to the length of the
44 # changelog. The file is truncated when the # changelog is stripped.
44 # changelog. The file is truncated when the # changelog is stripped.
45 #
45 #
46 # The purpose of the filenode cache is to avoid the most expensive part
46 # The purpose of the filenode cache is to avoid the most expensive part
47 # of finding global tags, which is looking up the .hgtags filenode in the
47 # of finding global tags, which is looking up the .hgtags filenode in the
48 # manifest for each head. This can take dozens or over 100ms for
48 # manifest for each head. This can take dozens or over 100ms for
49 # repositories with very large manifests. Multiplied by dozens or even
49 # repositories with very large manifests. Multiplied by dozens or even
50 # hundreds of heads and there is a significant performance concern.
50 # hundreds of heads and there is a significant performance concern.
51 #
51 #
52 # There also exist a separate cache file for each repository filter.
52 # There also exist a separate cache file for each repository filter.
53 # These "tags-*" files store information about the history of tags.
53 # These "tags-*" files store information about the history of tags.
54 #
54 #
55 # The tags cache files consists of a cache validation line followed by
55 # The tags cache files consists of a cache validation line followed by
56 # a history of tags.
56 # a history of tags.
57 #
57 #
58 # The cache validation line has the format:
58 # The cache validation line has the format:
59 #
59 #
60 # <tiprev> <tipnode> [<filteredhash>]
60 # <tiprev> <tipnode> [<filteredhash>]
61 #
61 #
62 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
62 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
63 # node for that changeset. These redundantly identify the repository
63 # node for that changeset. These redundantly identify the repository
64 # tip from the time the cache was written. In addition, <filteredhash>,
64 # tip from the time the cache was written. In addition, <filteredhash>,
65 # if present, is a 40 character hex hash of the contents of the filtered
65 # if present, is a 40 character hex hash of the contents of the filtered
66 # revisions for this filter. If the set of filtered revs changes, the
66 # revisions for this filter. If the set of filtered revs changes, the
67 # hash will change and invalidate the cache.
67 # hash will change and invalidate the cache.
68 #
68 #
69 # The history part of the tags cache consists of lines of the form:
69 # The history part of the tags cache consists of lines of the form:
70 #
70 #
71 # <node> <tag>
71 # <node> <tag>
72 #
72 #
73 # (This format is identical to that of .hgtags files.)
73 # (This format is identical to that of .hgtags files.)
74 #
74 #
75 # <tag> is the tag name and <node> is the 40 character hex changeset
75 # <tag> is the tag name and <node> is the 40 character hex changeset
76 # the tag is associated with.
76 # the tag is associated with.
77 #
77 #
78 # Tags are written sorted by tag name.
78 # Tags are written sorted by tag name.
79 #
79 #
80 # Tags associated with multiple changesets have an entry for each changeset.
80 # Tags associated with multiple changesets have an entry for each changeset.
81 # The most recent changeset (in terms of revlog ordering for the head
81 # The most recent changeset (in terms of revlog ordering for the head
82 # setting it) for each tag is last.
82 # setting it) for each tag is last.
83
83
84
84
85 def fnoderevs(ui, repo, revs):
85 def fnoderevs(ui, repo, revs):
86 """return the list of '.hgtags' fnodes used in a set revisions
86 """return the list of '.hgtags' fnodes used in a set revisions
87
87
88 This is returned as list of unique fnodes. We use a list instead of a set
88 This is returned as list of unique fnodes. We use a list instead of a set
89 because order matters when it comes to tags."""
89 because order matters when it comes to tags."""
90 unfi = repo.unfiltered()
90 unfi = repo.unfiltered()
91 tonode = unfi.changelog.node
91 tonode = unfi.changelog.node
92 nodes = [tonode(r) for r in revs]
92 nodes = [tonode(r) for r in revs]
93 fnodes = _getfnodes(ui, repo, nodes)
93 fnodes = _getfnodes(ui, repo, nodes)
94 fnodes = _filterfnodes(fnodes, nodes)
94 fnodes = _filterfnodes(fnodes, nodes)
95 return fnodes
95 return fnodes
96
96
97
97
98 def _nulltonone(repo, value):
98 def _nulltonone(repo, value):
99 """convert nullid to None
99 """convert nullid to None
100
100
101 For tag value, nullid means "deleted". This small utility function helps
101 For tag value, nullid means "deleted". This small utility function helps
102 translating that to None."""
102 translating that to None."""
103 if value == repo.nullid:
103 if value == repo.nullid:
104 return None
104 return None
105 return value
105 return value
106
106
107
107
108 def difftags(ui, repo, oldfnodes, newfnodes):
108 def difftags(ui, repo, oldfnodes, newfnodes):
109 """list differences between tags expressed in two set of file-nodes
109 """list differences between tags expressed in two set of file-nodes
110
110
111 The list contains entries in the form: (tagname, oldvalue, new value).
111 The list contains entries in the form: (tagname, oldvalue, new value).
112 None is used to expressed missing value:
112 None is used to expressed missing value:
113 ('foo', None, 'abcd') is a new tag,
113 ('foo', None, 'abcd') is a new tag,
114 ('bar', 'ef01', None) is a deletion,
114 ('bar', 'ef01', None) is a deletion,
115 ('baz', 'abcd', 'ef01') is a tag movement.
115 ('baz', 'abcd', 'ef01') is a tag movement.
116 """
116 """
117 if oldfnodes == newfnodes:
117 if oldfnodes == newfnodes:
118 return []
118 return []
119 oldtags = _tagsfromfnodes(ui, repo, oldfnodes)
119 oldtags = _tagsfromfnodes(ui, repo, oldfnodes)
120 newtags = _tagsfromfnodes(ui, repo, newfnodes)
120 newtags = _tagsfromfnodes(ui, repo, newfnodes)
121
121
122 # list of (tag, old, new): None means missing
122 # list of (tag, old, new): None means missing
123 entries = []
123 entries = []
124 for tag, (new, __) in newtags.items():
124 for tag, (new, __) in newtags.items():
125 new = _nulltonone(repo, new)
125 new = _nulltonone(repo, new)
126 old, __ = oldtags.pop(tag, (None, None))
126 old, __ = oldtags.pop(tag, (None, None))
127 old = _nulltonone(repo, old)
127 old = _nulltonone(repo, old)
128 if old != new:
128 if old != new:
129 entries.append((tag, old, new))
129 entries.append((tag, old, new))
130 # handle deleted tags
130 # handle deleted tags
131 for tag, (old, __) in oldtags.items():
131 for tag, (old, __) in oldtags.items():
132 old = _nulltonone(repo, old)
132 old = _nulltonone(repo, old)
133 if old is not None:
133 if old is not None:
134 entries.append((tag, old, None))
134 entries.append((tag, old, None))
135 entries.sort()
135 entries.sort()
136 return entries
136 return entries
137
137
138
138
139 def writediff(fp, difflist):
139 def writediff(fp, difflist):
140 """write tags diff information to a file.
140 """write tags diff information to a file.
141
141
142 Data are stored with a line based format:
142 Data are stored with a line based format:
143
143
144 <action> <hex-node> <tag-name>\n
144 <action> <hex-node> <tag-name>\n
145
145
146 Action are defined as follow:
146 Action are defined as follow:
147 -R tag is removed,
147 -R tag is removed,
148 +A tag is added,
148 +A tag is added,
149 -M tag is moved (old value),
149 -M tag is moved (old value),
150 +M tag is moved (new value),
150 +M tag is moved (new value),
151
151
152 Example:
152 Example:
153
153
154 +A 875517b4806a848f942811a315a5bce30804ae85 t5
154 +A 875517b4806a848f942811a315a5bce30804ae85 t5
155
155
156 See documentation of difftags output for details about the input.
156 See documentation of difftags output for details about the input.
157 """
157 """
158 add = b'+A %s %s\n'
158 add = b'+A %s %s\n'
159 remove = b'-R %s %s\n'
159 remove = b'-R %s %s\n'
160 updateold = b'-M %s %s\n'
160 updateold = b'-M %s %s\n'
161 updatenew = b'+M %s %s\n'
161 updatenew = b'+M %s %s\n'
162 for tag, old, new in difflist:
162 for tag, old, new in difflist:
163 # translate to hex
163 # translate to hex
164 if old is not None:
164 if old is not None:
165 old = hex(old)
165 old = hex(old)
166 if new is not None:
166 if new is not None:
167 new = hex(new)
167 new = hex(new)
168 # write to file
168 # write to file
169 if old is None:
169 if old is None:
170 fp.write(add % (new, tag))
170 fp.write(add % (new, tag))
171 elif new is None:
171 elif new is None:
172 fp.write(remove % (old, tag))
172 fp.write(remove % (old, tag))
173 else:
173 else:
174 fp.write(updateold % (old, tag))
174 fp.write(updateold % (old, tag))
175 fp.write(updatenew % (new, tag))
175 fp.write(updatenew % (new, tag))
176
176
177
177
178 def findglobaltags(ui, repo):
178 def findglobaltags(ui, repo):
179 """Find global tags in a repo: return a tagsmap
179 """Find global tags in a repo: return a tagsmap
180
180
181 tagsmap: tag name to (node, hist) 2-tuples.
181 tagsmap: tag name to (node, hist) 2-tuples.
182
182
183 The tags cache is read and updated as a side-effect of calling.
183 The tags cache is read and updated as a side-effect of calling.
184 """
184 """
185 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
185 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
186 if cachetags is not None:
186 if cachetags is not None:
187 assert not shouldwrite
187 assert not shouldwrite
188 # XXX is this really 100% correct? are there oddball special
188 # XXX is this really 100% correct? are there oddball special
189 # cases where a global tag should outrank a local tag but won't,
189 # cases where a global tag should outrank a local tag but won't,
190 # because cachetags does not contain rank info?
190 # because cachetags does not contain rank info?
191 alltags = {}
191 alltags = {}
192 _updatetags(cachetags, alltags)
192 _updatetags(cachetags, alltags)
193 return alltags
193 return alltags
194
194
195 for head in reversed(heads): # oldest to newest
195 for head in reversed(heads): # oldest to newest
196 assert repo.changelog.index.has_node(
196 assert repo.changelog.index.has_node(
197 head
197 head
198 ), b"tag cache returned bogus head %s" % short(head)
198 ), b"tag cache returned bogus head %s" % short(head)
199 fnodes = _filterfnodes(tagfnode, reversed(heads))
199 fnodes = _filterfnodes(tagfnode, reversed(heads))
200 alltags = _tagsfromfnodes(ui, repo, fnodes)
200 alltags = _tagsfromfnodes(ui, repo, fnodes)
201
201
202 # and update the cache (if necessary)
202 # and update the cache (if necessary)
203 if shouldwrite:
203 if shouldwrite:
204 _writetagcache(ui, repo, valid, alltags)
204 _writetagcache(ui, repo, valid, alltags)
205 return alltags
205 return alltags
206
206
207
207
208 def _filterfnodes(tagfnode, nodes):
208 def _filterfnodes(tagfnode, nodes):
209 """return a list of unique fnodes
209 """return a list of unique fnodes
210
210
211 The order of this list matches the order of "nodes". Preserving this order
211 The order of this list matches the order of "nodes". Preserving this order
212 is important as reading tags in different order provides different
212 is important as reading tags in different order provides different
213 results."""
213 results."""
214 seen = set() # set of fnode
214 seen = set() # set of fnode
215 fnodes = []
215 fnodes = []
216 for no in nodes: # oldest to newest
216 for no in nodes: # oldest to newest
217 fnode = tagfnode.get(no)
217 fnode = tagfnode.get(no)
218 if fnode and fnode not in seen:
218 if fnode and fnode not in seen:
219 seen.add(fnode)
219 seen.add(fnode)
220 fnodes.append(fnode)
220 fnodes.append(fnode)
221 return fnodes
221 return fnodes
222
222
223
223
224 def _tagsfromfnodes(ui, repo, fnodes):
224 def _tagsfromfnodes(ui, repo, fnodes):
225 """return a tagsmap from a list of file-node
225 """return a tagsmap from a list of file-node
226
226
227 tagsmap: tag name to (node, hist) 2-tuples.
227 tagsmap: tag name to (node, hist) 2-tuples.
228
228
229 The order of the list matters."""
229 The order of the list matters."""
230 alltags = {}
230 alltags = {}
231 fctx = None
231 fctx = None
232 for fnode in fnodes:
232 for fnode in fnodes:
233 if fctx is None:
233 if fctx is None:
234 fctx = repo.filectx(b'.hgtags', fileid=fnode)
234 fctx = repo.filectx(b'.hgtags', fileid=fnode)
235 else:
235 else:
236 fctx = fctx.filectx(fnode)
236 fctx = fctx.filectx(fnode)
237 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
237 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
238 _updatetags(filetags, alltags)
238 _updatetags(filetags, alltags)
239 return alltags
239 return alltags
240
240
241
241
242 def readlocaltags(ui, repo, alltags, tagtypes):
242 def readlocaltags(ui, repo, alltags, tagtypes):
243 '''Read local tags in repo. Update alltags and tagtypes.'''
243 '''Read local tags in repo. Update alltags and tagtypes.'''
244 try:
244 try:
245 data = repo.vfs.read(b"localtags")
245 data = repo.vfs.read(b"localtags")
246 except IOError as inst:
246 except IOError as inst:
247 if inst.errno != errno.ENOENT:
247 if inst.errno != errno.ENOENT:
248 raise
248 raise
249 return
249 return
250
250
251 # localtags is in the local encoding; re-encode to UTF-8 on
251 # localtags is in the local encoding; re-encode to UTF-8 on
252 # input for consistency with the rest of this module.
252 # input for consistency with the rest of this module.
253 filetags = _readtags(
253 filetags = _readtags(
254 ui, repo, data.splitlines(), b"localtags", recode=encoding.fromlocal
254 ui, repo, data.splitlines(), b"localtags", recode=encoding.fromlocal
255 )
255 )
256
256
257 # remove tags pointing to invalid nodes
257 # remove tags pointing to invalid nodes
258 cl = repo.changelog
258 cl = repo.changelog
259 for t in list(filetags):
259 for t in list(filetags):
260 try:
260 try:
261 cl.rev(filetags[t][0])
261 cl.rev(filetags[t][0])
262 except (LookupError, ValueError):
262 except (LookupError, ValueError):
263 del filetags[t]
263 del filetags[t]
264
264
265 _updatetags(filetags, alltags, b'local', tagtypes)
265 _updatetags(filetags, alltags, b'local', tagtypes)
266
266
267
267
268 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
268 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
269 """Read tag definitions from a file (or any source of lines).
269 """Read tag definitions from a file (or any source of lines).
270
270
271 This function returns two sortdicts with similar information:
271 This function returns two sortdicts with similar information:
272
272
273 - the first dict, bintaghist, contains the tag information as expected by
273 - the first dict, bintaghist, contains the tag information as expected by
274 the _readtags function, i.e. a mapping from tag name to (node, hist):
274 the _readtags function, i.e. a mapping from tag name to (node, hist):
275 - node is the node id from the last line read for that name,
275 - node is the node id from the last line read for that name,
276 - hist is the list of node ids previously associated with it (in file
276 - hist is the list of node ids previously associated with it (in file
277 order). All node ids are binary, not hex.
277 order). All node ids are binary, not hex.
278
278
279 - the second dict, hextaglines, is a mapping from tag name to a list of
279 - the second dict, hextaglines, is a mapping from tag name to a list of
280 [hexnode, line number] pairs, ordered from the oldest to the newest node.
280 [hexnode, line number] pairs, ordered from the oldest to the newest node.
281
281
282 When calcnodelines is False the hextaglines dict is not calculated (an
282 When calcnodelines is False the hextaglines dict is not calculated (an
283 empty dict is returned). This is done to improve this function's
283 empty dict is returned). This is done to improve this function's
284 performance in cases where the line numbers are not needed.
284 performance in cases where the line numbers are not needed.
285 """
285 """
286
286
287 bintaghist = util.sortdict()
287 bintaghist = util.sortdict()
288 hextaglines = util.sortdict()
288 hextaglines = util.sortdict()
289 count = 0
289 count = 0
290
290
291 def dbg(msg):
291 def dbg(msg):
292 ui.debug(b"%s, line %d: %s\n" % (fn, count, msg))
292 ui.debug(b"%s, line %d: %s\n" % (fn, count, msg))
293
293
294 for nline, line in enumerate(lines):
294 for nline, line in enumerate(lines):
295 count += 1
295 count += 1
296 if not line:
296 if not line:
297 continue
297 continue
298 try:
298 try:
299 (nodehex, name) = line.split(b" ", 1)
299 (nodehex, name) = line.split(b" ", 1)
300 except ValueError:
300 except ValueError:
301 dbg(b"cannot parse entry")
301 dbg(b"cannot parse entry")
302 continue
302 continue
303 name = name.strip()
303 name = name.strip()
304 if recode:
304 if recode:
305 name = recode(name)
305 name = recode(name)
306 try:
306 try:
307 nodebin = bin(nodehex)
307 nodebin = bin(nodehex)
308 except TypeError:
308 except TypeError:
309 dbg(b"node '%s' is not well formed" % nodehex)
309 dbg(b"node '%s' is not well formed" % nodehex)
310 continue
310 continue
311
311
312 # update filetags
312 # update filetags
313 if calcnodelines:
313 if calcnodelines:
314 # map tag name to a list of line numbers
314 # map tag name to a list of line numbers
315 if name not in hextaglines:
315 if name not in hextaglines:
316 hextaglines[name] = []
316 hextaglines[name] = []
317 hextaglines[name].append([nodehex, nline])
317 hextaglines[name].append([nodehex, nline])
318 continue
318 continue
319 # map tag name to (node, hist)
319 # map tag name to (node, hist)
320 if name not in bintaghist:
320 if name not in bintaghist:
321 bintaghist[name] = []
321 bintaghist[name] = []
322 bintaghist[name].append(nodebin)
322 bintaghist[name].append(nodebin)
323 return bintaghist, hextaglines
323 return bintaghist, hextaglines
324
324
325
325
326 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
326 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
327 """Read tag definitions from a file (or any source of lines).
327 """Read tag definitions from a file (or any source of lines).
328
328
329 Returns a mapping from tag name to (node, hist).
329 Returns a mapping from tag name to (node, hist).
330
330
331 "node" is the node id from the last line read for that name. "hist"
331 "node" is the node id from the last line read for that name. "hist"
332 is the list of node ids previously associated with it (in file order).
332 is the list of node ids previously associated with it (in file order).
333 All node ids are binary, not hex.
333 All node ids are binary, not hex.
334 """
334 """
335 filetags, nodelines = _readtaghist(
335 filetags, nodelines = _readtaghist(
336 ui, repo, lines, fn, recode=recode, calcnodelines=calcnodelines
336 ui, repo, lines, fn, recode=recode, calcnodelines=calcnodelines
337 )
337 )
338 # util.sortdict().__setitem__ is much slower at replacing then inserting
338 # util.sortdict().__setitem__ is much slower at replacing then inserting
339 # new entries. The difference can matter if there are thousands of tags.
339 # new entries. The difference can matter if there are thousands of tags.
340 # Create a new sortdict to avoid the performance penalty.
340 # Create a new sortdict to avoid the performance penalty.
341 newtags = util.sortdict()
341 newtags = util.sortdict()
342 for tag, taghist in filetags.items():
342 for tag, taghist in filetags.items():
343 newtags[tag] = (taghist[-1], taghist[:-1])
343 newtags[tag] = (taghist[-1], taghist[:-1])
344 return newtags
344 return newtags
345
345
346
346
347 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
347 def _updatetags(filetags, alltags, tagtype=None, tagtypes=None):
348 """Incorporate the tag info read from one file into dictionnaries
348 """Incorporate the tag info read from one file into dictionnaries
349
349
350 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
350 The first one, 'alltags', is a "tagmaps" (see 'findglobaltags' for details).
351
351
352 The second one, 'tagtypes', is optional and will be updated to track the
352 The second one, 'tagtypes', is optional and will be updated to track the
353 "tagtype" of entries in the tagmaps. When set, the 'tagtype' argument also
353 "tagtype" of entries in the tagmaps. When set, the 'tagtype' argument also
354 needs to be set."""
354 needs to be set."""
355 if tagtype is None:
355 if tagtype is None:
356 assert tagtypes is None
356 assert tagtypes is None
357
357
358 for name, nodehist in pycompat.iteritems(filetags):
358 for name, nodehist in pycompat.iteritems(filetags):
359 if name not in alltags:
359 if name not in alltags:
360 alltags[name] = nodehist
360 alltags[name] = nodehist
361 if tagtype is not None:
361 if tagtype is not None:
362 tagtypes[name] = tagtype
362 tagtypes[name] = tagtype
363 continue
363 continue
364
364
365 # we prefer alltags[name] if:
365 # we prefer alltags[name] if:
366 # it supersedes us OR
366 # it supersedes us OR
367 # mutual supersedes and it has a higher rank
367 # mutual supersedes and it has a higher rank
368 # otherwise we win because we're tip-most
368 # otherwise we win because we're tip-most
369 anode, ahist = nodehist
369 anode, ahist = nodehist
370 bnode, bhist = alltags[name]
370 bnode, bhist = alltags[name]
371 if (
371 if (
372 bnode != anode
372 bnode != anode
373 and anode in bhist
373 and anode in bhist
374 and (bnode not in ahist or len(bhist) > len(ahist))
374 and (bnode not in ahist or len(bhist) > len(ahist))
375 ):
375 ):
376 anode = bnode
376 anode = bnode
377 elif tagtype is not None:
377 elif tagtype is not None:
378 tagtypes[name] = tagtype
378 tagtypes[name] = tagtype
379 ahist.extend([n for n in bhist if n not in ahist])
379 ahist.extend([n for n in bhist if n not in ahist])
380 alltags[name] = anode, ahist
380 alltags[name] = anode, ahist
381
381
382
382
383 def _filename(repo):
383 def _filename(repo):
384 """name of a tagcache file for a given repo or repoview"""
384 """name of a tagcache file for a given repo or repoview"""
385 filename = b'tags2'
385 filename = b'tags2'
386 if repo.filtername:
386 if repo.filtername:
387 filename = b'%s-%s' % (filename, repo.filtername)
387 filename = b'%s-%s' % (filename, repo.filtername)
388 return filename
388 return filename
389
389
390
390
391 def _readtagcache(ui, repo):
391 def _readtagcache(ui, repo):
392 """Read the tag cache.
392 """Read the tag cache.
393
393
394 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
394 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
395
395
396 If the cache is completely up-to-date, "cachetags" is a dict of the
396 If the cache is completely up-to-date, "cachetags" is a dict of the
397 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
397 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
398 None and "shouldwrite" is False.
398 None and "shouldwrite" is False.
399
399
400 If the cache is not up to date, "cachetags" is None. "heads" is a list
400 If the cache is not up to date, "cachetags" is None. "heads" is a list
401 of all heads currently in the repository, ordered from tip to oldest.
401 of all heads currently in the repository, ordered from tip to oldest.
402 "validinfo" is a tuple describing cache validation info. This is used
402 "validinfo" is a tuple describing cache validation info. This is used
403 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
403 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
404 filenode. "shouldwrite" is True.
404 filenode. "shouldwrite" is True.
405
405
406 If the cache is not up to date, the caller is responsible for reading tag
406 If the cache is not up to date, the caller is responsible for reading tag
407 info from each returned head. (See findglobaltags().)
407 info from each returned head. (See findglobaltags().)
408 """
408 """
409 try:
409 try:
410 cachefile = repo.cachevfs(_filename(repo), b'r')
410 cachefile = repo.cachevfs(_filename(repo), b'r')
411 # force reading the file for static-http
411 # force reading the file for static-http
412 cachelines = iter(cachefile)
412 cachelines = iter(cachefile)
413 except IOError:
413 except IOError:
414 cachefile = None
414 cachefile = None
415
415
416 cacherev = None
416 cacherev = None
417 cachenode = None
417 cachenode = None
418 cachehash = None
418 cachehash = None
419 if cachefile:
419 if cachefile:
420 try:
420 try:
421 validline = next(cachelines)
421 validline = next(cachelines)
422 validline = validline.split()
422 validline = validline.split()
423 cacherev = int(validline[0])
423 cacherev = int(validline[0])
424 cachenode = bin(validline[1])
424 cachenode = bin(validline[1])
425 if len(validline) > 2:
425 if len(validline) > 2:
426 cachehash = bin(validline[2])
426 cachehash = bin(validline[2])
427 except Exception:
427 except Exception:
428 # corruption of the cache, just recompute it.
428 # corruption of the cache, just recompute it.
429 pass
429 pass
430
430
431 tipnode = repo.changelog.tip()
431 tipnode = repo.changelog.tip()
432 tiprev = len(repo.changelog) - 1
432 tiprev = len(repo.changelog) - 1
433
433
434 # Case 1 (common): tip is the same, so nothing has changed.
434 # Case 1 (common): tip is the same, so nothing has changed.
435 # (Unchanged tip trivially means no changesets have been added.
435 # (Unchanged tip trivially means no changesets have been added.
436 # But, thanks to localrepository.destroyed(), it also means none
436 # But, thanks to localrepository.destroyed(), it also means none
437 # have been destroyed by strip or rollback.)
437 # have been destroyed by strip or rollback.)
438 if (
438 if (
439 cacherev == tiprev
439 cacherev == tiprev
440 and cachenode == tipnode
440 and cachenode == tipnode
441 and cachehash == scmutil.filteredhash(repo, tiprev)
441 and cachehash == scmutil.filteredhash(repo, tiprev)
442 ):
442 ):
443 tags = _readtags(ui, repo, cachelines, cachefile.name)
443 tags = _readtags(ui, repo, cachelines, cachefile.name)
444 cachefile.close()
444 cachefile.close()
445 return (None, None, None, tags, False)
445 return (None, None, None, tags, False)
446 if cachefile:
446 if cachefile:
447 cachefile.close() # ignore rest of file
447 cachefile.close() # ignore rest of file
448
448
449 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
449 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
450
450
451 repoheads = repo.heads()
451 repoheads = repo.heads()
452 # Case 2 (uncommon): empty repo; get out quickly and don't bother
452 # Case 2 (uncommon): empty repo; get out quickly and don't bother
453 # writing an empty cache.
453 # writing an empty cache.
454 if repoheads == [repo.nullid]:
454 if repoheads == [repo.nullid]:
455 return ([], {}, valid, {}, False)
455 return ([], {}, valid, {}, False)
456
456
457 # Case 3 (uncommon): cache file missing or empty.
457 # Case 3 (uncommon): cache file missing or empty.
458
458
459 # Case 4 (uncommon): tip rev decreased. This should only happen
459 # Case 4 (uncommon): tip rev decreased. This should only happen
460 # when we're called from localrepository.destroyed(). Refresh the
460 # when we're called from localrepository.destroyed(). Refresh the
461 # cache so future invocations will not see disappeared heads in the
461 # cache so future invocations will not see disappeared heads in the
462 # cache.
462 # cache.
463
463
464 # Case 5 (common): tip has changed, so we've added/replaced heads.
464 # Case 5 (common): tip has changed, so we've added/replaced heads.
465
465
466 # As it happens, the code to handle cases 3, 4, 5 is the same.
466 # As it happens, the code to handle cases 3, 4, 5 is the same.
467
467
468 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
468 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
469 # exposed".
469 # exposed".
470 if not len(repo.file(b'.hgtags')):
470 if not len(repo.file(b'.hgtags')):
471 # No tags have ever been committed, so we can avoid a
471 # No tags have ever been committed, so we can avoid a
472 # potentially expensive search.
472 # potentially expensive search.
473 return ([], {}, valid, None, True)
473 return ([], {}, valid, None, True)
474
474
475 # Now we have to lookup the .hgtags filenode for every new head.
475 # Now we have to lookup the .hgtags filenode for every new head.
476 # This is the most expensive part of finding tags, so performance
476 # This is the most expensive part of finding tags, so performance
477 # depends primarily on the size of newheads. Worst case: no cache
477 # depends primarily on the size of newheads. Worst case: no cache
478 # file, so newheads == repoheads.
478 # file, so newheads == repoheads.
479 # Reversed order helps the cache ('repoheads' is in descending order)
479 # Reversed order helps the cache ('repoheads' is in descending order)
480 cachefnode = _getfnodes(ui, repo, reversed(repoheads))
480 cachefnode = _getfnodes(ui, repo, reversed(repoheads))
481
481
482 # Caller has to iterate over all heads, but can use the filenodes in
482 # Caller has to iterate over all heads, but can use the filenodes in
483 # cachefnode to get to each .hgtags revision quickly.
483 # cachefnode to get to each .hgtags revision quickly.
484 return (repoheads, cachefnode, valid, None, True)
484 return (repoheads, cachefnode, valid, None, True)
485
485
486
486
487 def _getfnodes(ui, repo, nodes):
487 def _getfnodes(ui, repo, nodes):
488 """return .hgtags fnodes for a list of changeset nodes
488 """return .hgtags fnodes for a list of changeset nodes
489
489
490 Return value is a {node: fnode} mapping. There will be no entry for nodes
490 Return value is a {node: fnode} mapping. There will be no entry for nodes
491 without a '.hgtags' file.
491 without a '.hgtags' file.
492 """
492 """
493 starttime = util.timer()
493 starttime = util.timer()
494 fnodescache = hgtagsfnodescache(repo.unfiltered())
494 fnodescache = hgtagsfnodescache(repo.unfiltered())
495 cachefnode = {}
495 cachefnode = {}
496 validated_fnodes = set()
496 validated_fnodes = set()
497 unknown_entries = set()
497 unknown_entries = set()
498 for node in nodes:
498 for node in nodes:
499 fnode = fnodescache.getfnode(node)
499 fnode = fnodescache.getfnode(node)
500 flog = repo.file(b'.hgtags')
500 flog = repo.file(b'.hgtags')
501 if fnode != repo.nullid:
501 if fnode != repo.nullid:
502 if fnode not in validated_fnodes:
502 if fnode not in validated_fnodes:
503 if flog.hasnode(fnode):
503 if flog.hasnode(fnode):
504 validated_fnodes.add(fnode)
504 validated_fnodes.add(fnode)
505 else:
505 else:
506 unknown_entries.add(node)
506 unknown_entries.add(node)
507 cachefnode[node] = fnode
507 cachefnode[node] = fnode
508
508
509 if unknown_entries:
509 if unknown_entries:
510 fixed_nodemap = fnodescache.refresh_invalid_nodes(unknown_entries)
510 fixed_nodemap = fnodescache.refresh_invalid_nodes(unknown_entries)
511 for node, fnode in pycompat.iteritems(fixed_nodemap):
511 for node, fnode in pycompat.iteritems(fixed_nodemap):
512 if fnode != repo.nullid:
512 if fnode != repo.nullid:
513 cachefnode[node] = fnode
513 cachefnode[node] = fnode
514
514
515 fnodescache.write()
515 fnodescache.write()
516
516
517 duration = util.timer() - starttime
517 duration = util.timer() - starttime
518 ui.log(
518 ui.log(
519 b'tagscache',
519 b'tagscache',
520 b'%d/%d cache hits/lookups in %0.4f seconds\n',
520 b'%d/%d cache hits/lookups in %0.4f seconds\n',
521 fnodescache.hitcount,
521 fnodescache.hitcount,
522 fnodescache.lookupcount,
522 fnodescache.lookupcount,
523 duration,
523 duration,
524 )
524 )
525 return cachefnode
525 return cachefnode
526
526
527
527
528 def _writetagcache(ui, repo, valid, cachetags):
528 def _writetagcache(ui, repo, valid, cachetags):
529 filename = _filename(repo)
529 filename = _filename(repo)
530 try:
530 try:
531 cachefile = repo.cachevfs(filename, b'w', atomictemp=True)
531 cachefile = repo.cachevfs(filename, b'w', atomictemp=True)
532 except (OSError, IOError):
532 except (OSError, IOError):
533 return
533 return
534
534
535 ui.log(
535 ui.log(
536 b'tagscache',
536 b'tagscache',
537 b'writing .hg/cache/%s with %d tags\n',
537 b'writing .hg/cache/%s with %d tags\n',
538 filename,
538 filename,
539 len(cachetags),
539 len(cachetags),
540 )
540 )
541
541
542 if valid[2]:
542 if valid[2]:
543 cachefile.write(
543 cachefile.write(
544 b'%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2]))
544 b'%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2]))
545 )
545 )
546 else:
546 else:
547 cachefile.write(b'%d %s\n' % (valid[0], hex(valid[1])))
547 cachefile.write(b'%d %s\n' % (valid[0], hex(valid[1])))
548
548
549 # Tag names in the cache are in UTF-8 -- which is the whole reason
549 # Tag names in the cache are in UTF-8 -- which is the whole reason
550 # we keep them in UTF-8 throughout this module. If we converted
550 # we keep them in UTF-8 throughout this module. If we converted
551 # them local encoding on input, we would lose info writing them to
551 # them local encoding on input, we would lose info writing them to
552 # the cache.
552 # the cache.
553 for (name, (node, hist)) in sorted(pycompat.iteritems(cachetags)):
553 for (name, (node, hist)) in sorted(pycompat.iteritems(cachetags)):
554 for n in hist:
554 for n in hist:
555 cachefile.write(b"%s %s\n" % (hex(n), name))
555 cachefile.write(b"%s %s\n" % (hex(n), name))
556 cachefile.write(b"%s %s\n" % (hex(node), name))
556 cachefile.write(b"%s %s\n" % (hex(node), name))
557
557
558 try:
558 try:
559 cachefile.close()
559 cachefile.close()
560 except (OSError, IOError):
560 except (OSError, IOError):
561 pass
561 pass
562
562
563
563
564 def tag(repo, names, node, message, local, user, date, editor=False):
564 def tag(repo, names, node, message, local, user, date, editor=False):
565 """tag a revision with one or more symbolic names.
565 """tag a revision with one or more symbolic names.
566
566
567 names is a list of strings or, when adding a single tag, names may be a
567 names is a list of strings or, when adding a single tag, names may be a
568 string.
568 string.
569
569
570 if local is True, the tags are stored in a per-repository file.
570 if local is True, the tags are stored in a per-repository file.
571 otherwise, they are stored in the .hgtags file, and a new
571 otherwise, they are stored in the .hgtags file, and a new
572 changeset is committed with the change.
572 changeset is committed with the change.
573
573
574 keyword arguments:
574 keyword arguments:
575
575
576 local: whether to store tags in non-version-controlled file
576 local: whether to store tags in non-version-controlled file
577 (default False)
577 (default False)
578
578
579 message: commit message to use if committing
579 message: commit message to use if committing
580
580
581 user: name of user to use if committing
581 user: name of user to use if committing
582
582
583 date: date tuple to use if committing"""
583 date: date tuple to use if committing"""
584
584
585 if not local:
585 if not local:
586 m = matchmod.exact([b'.hgtags'])
586 m = matchmod.exact([b'.hgtags'])
587 st = repo.status(match=m, unknown=True, ignored=True)
587 st = repo.status(match=m, unknown=True, ignored=True)
588 if any(
588 if any(
589 (
589 (
590 st.modified,
590 st.modified,
591 st.added,
591 st.added,
592 st.removed,
592 st.removed,
593 st.deleted,
593 st.deleted,
594 st.unknown,
594 st.unknown,
595 st.ignored,
595 st.ignored,
596 )
596 )
597 ):
597 ):
598 raise error.Abort(
598 raise error.Abort(
599 _(b'working copy of .hgtags is changed'),
599 _(b'working copy of .hgtags is changed'),
600 hint=_(b'please commit .hgtags manually'),
600 hint=_(b'please commit .hgtags manually'),
601 )
601 )
602
602
603 with repo.wlock():
603 with repo.wlock():
604 repo.tags() # instantiate the cache
604 repo.tags() # instantiate the cache
605 _tag(repo, names, node, message, local, user, date, editor=editor)
605 _tag(repo, names, node, message, local, user, date, editor=editor)
606
606
607
607
608 def _tag(
608 def _tag(
609 repo, names, node, message, local, user, date, extra=None, editor=False
609 repo, names, node, message, local, user, date, extra=None, editor=False
610 ):
610 ):
611 if isinstance(names, bytes):
611 if isinstance(names, bytes):
612 names = (names,)
612 names = (names,)
613
613
614 branches = repo.branchmap()
614 branches = repo.branchmap()
615 for name in names:
615 for name in names:
616 repo.hook(b'pretag', throw=True, node=hex(node), tag=name, local=local)
616 repo.hook(b'pretag', throw=True, node=hex(node), tag=name, local=local)
617 if name in branches:
617 if name in branches:
618 repo.ui.warn(
618 repo.ui.warn(
619 _(b"warning: tag %s conflicts with existing branch name\n")
619 _(b"warning: tag %s conflicts with existing branch name\n")
620 % name
620 % name
621 )
621 )
622
622
623 def writetags(fp, names, munge, prevtags):
623 def writetags(fp, names, munge, prevtags):
624 fp.seek(0, io.SEEK_END)
624 fp.seek(0, io.SEEK_END)
625 if prevtags and not prevtags.endswith(b'\n'):
625 if prevtags and not prevtags.endswith(b'\n'):
626 fp.write(b'\n')
626 fp.write(b'\n')
627 for name in names:
627 for name in names:
628 if munge:
628 if munge:
629 m = munge(name)
629 m = munge(name)
630 else:
630 else:
631 m = name
631 m = name
632
632
633 if repo._tagscache.tagtypes and name in repo._tagscache.tagtypes:
633 if repo._tagscache.tagtypes and name in repo._tagscache.tagtypes:
634 old = repo.tags().get(name, repo.nullid)
634 old = repo.tags().get(name, repo.nullid)
635 fp.write(b'%s %s\n' % (hex(old), m))
635 fp.write(b'%s %s\n' % (hex(old), m))
636 fp.write(b'%s %s\n' % (hex(node), m))
636 fp.write(b'%s %s\n' % (hex(node), m))
637 fp.close()
637 fp.close()
638
638
639 prevtags = b''
639 prevtags = b''
640 if local:
640 if local:
641 try:
641 try:
642 fp = repo.vfs(b'localtags', b'r+')
642 fp = repo.vfs(b'localtags', b'r+')
643 except IOError:
643 except IOError:
644 fp = repo.vfs(b'localtags', b'a')
644 fp = repo.vfs(b'localtags', b'a')
645 else:
645 else:
646 prevtags = fp.read()
646 prevtags = fp.read()
647
647
648 # local tags are stored in the current charset
648 # local tags are stored in the current charset
649 writetags(fp, names, None, prevtags)
649 writetags(fp, names, None, prevtags)
650 for name in names:
650 for name in names:
651 repo.hook(b'tag', node=hex(node), tag=name, local=local)
651 repo.hook(b'tag', node=hex(node), tag=name, local=local)
652 return
652 return
653
653
654 try:
654 try:
655 fp = repo.wvfs(b'.hgtags', b'rb+')
655 fp = repo.wvfs(b'.hgtags', b'rb+')
656 except IOError as e:
656 except IOError as e:
657 if e.errno != errno.ENOENT:
657 if e.errno != errno.ENOENT:
658 raise
658 raise
659 fp = repo.wvfs(b'.hgtags', b'ab')
659 fp = repo.wvfs(b'.hgtags', b'ab')
660 else:
660 else:
661 prevtags = fp.read()
661 prevtags = fp.read()
662
662
663 # committed tags are stored in UTF-8
663 # committed tags are stored in UTF-8
664 writetags(fp, names, encoding.fromlocal, prevtags)
664 writetags(fp, names, encoding.fromlocal, prevtags)
665
665
666 fp.close()
666 fp.close()
667
667
668 repo.invalidatecaches()
668 repo.invalidatecaches()
669
669
670 if b'.hgtags' not in repo.dirstate:
670 if b'.hgtags' not in repo.dirstate:
671 repo[None].add([b'.hgtags'])
671 repo[None].add([b'.hgtags'])
672
672
673 m = matchmod.exact([b'.hgtags'])
673 m = matchmod.exact([b'.hgtags'])
674 tagnode = repo.commit(
674 tagnode = repo.commit(
675 message, user, date, extra=extra, match=m, editor=editor
675 message, user, date, extra=extra, match=m, editor=editor
676 )
676 )
677
677
678 for name in names:
678 for name in names:
679 repo.hook(b'tag', node=hex(node), tag=name, local=local)
679 repo.hook(b'tag', node=hex(node), tag=name, local=local)
680
680
681 return tagnode
681 return tagnode
682
682
683
683
684 _fnodescachefile = b'hgtagsfnodes1'
684 _fnodescachefile = b'hgtagsfnodes1'
685 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
685 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
686 _fnodesmissingrec = b'\xff' * 24
686 _fnodesmissingrec = b'\xff' * 24
687
687
688
688
689 class hgtagsfnodescache(object):
689 class hgtagsfnodescache(object):
690 """Persistent cache mapping revisions to .hgtags filenodes.
690 """Persistent cache mapping revisions to .hgtags filenodes.
691
691
692 The cache is an array of records. Each item in the array corresponds to
692 The cache is an array of records. Each item in the array corresponds to
693 a changelog revision. Values in the array contain the first 4 bytes of
693 a changelog revision. Values in the array contain the first 4 bytes of
694 the node hash and the 20 bytes .hgtags filenode for that revision.
694 the node hash and the 20 bytes .hgtags filenode for that revision.
695
695
696 The first 4 bytes are present as a form of verification. Repository
696 The first 4 bytes are present as a form of verification. Repository
697 stripping and rewriting may change the node at a numeric revision in the
697 stripping and rewriting may change the node at a numeric revision in the
698 changelog. The changeset fragment serves as a verifier to detect
698 changelog. The changeset fragment serves as a verifier to detect
699 rewriting. This logic is shared with the rev branch cache (see
699 rewriting. This logic is shared with the rev branch cache (see
700 branchmap.py).
700 branchmap.py).
701
701
702 The instance holds in memory the full cache content but entries are
702 The instance holds in memory the full cache content but entries are
703 only parsed on read.
703 only parsed on read.
704
704
705 Instances behave like lists. ``c[i]`` works where i is a rev or
705 Instances behave like lists. ``c[i]`` works where i is a rev or
706 changeset node. Missing indexes are populated automatically on access.
706 changeset node. Missing indexes are populated automatically on access.
707 """
707 """
708
708
709 def __init__(self, repo):
709 def __init__(self, repo):
710 assert repo.filtername is None
710 assert repo.filtername is None
711
711
712 self._repo = repo
712 self._repo = repo
713
713
714 # Only for reporting purposes.
714 # Only for reporting purposes.
715 self.lookupcount = 0
715 self.lookupcount = 0
716 self.hitcount = 0
716 self.hitcount = 0
717
717
718 try:
718 try:
719 data = repo.cachevfs.read(_fnodescachefile)
719 data = repo.cachevfs.read(_fnodescachefile)
720 except (OSError, IOError):
720 except (OSError, IOError):
721 data = b""
721 data = b""
722 self._raw = bytearray(data)
722 self._raw = bytearray(data)
723
723
724 # The end state of self._raw is an array that is of the exact length
724 # The end state of self._raw is an array that is of the exact length
725 # required to hold a record for every revision in the repository.
725 # required to hold a record for every revision in the repository.
726 # We truncate or extend the array as necessary. self._dirtyoffset is
726 # We truncate or extend the array as necessary. self._dirtyoffset is
727 # defined to be the start offset at which we need to write the output
727 # defined to be the start offset at which we need to write the output
728 # file. This offset is also adjusted when new entries are calculated
728 # file. This offset is also adjusted when new entries are calculated
729 # for array members.
729 # for array members.
730 cllen = len(repo.changelog)
730 cllen = len(repo.changelog)
731 wantedlen = cllen * _fnodesrecsize
731 wantedlen = cllen * _fnodesrecsize
732 rawlen = len(self._raw)
732 rawlen = len(self._raw)
733
733
734 self._dirtyoffset = None
734 self._dirtyoffset = None
735
735
736 rawlentokeep = min(
736 rawlentokeep = min(
737 wantedlen, (rawlen // _fnodesrecsize) * _fnodesrecsize
737 wantedlen, (rawlen // _fnodesrecsize) * _fnodesrecsize
738 )
738 )
739 if rawlen > rawlentokeep:
739 if rawlen > rawlentokeep:
740 # There's no easy way to truncate array instances. This seems
740 # There's no easy way to truncate array instances. This seems
741 # slightly less evil than copying a potentially large array slice.
741 # slightly less evil than copying a potentially large array slice.
742 for i in range(rawlen - rawlentokeep):
742 for i in range(rawlen - rawlentokeep):
743 self._raw.pop()
743 self._raw.pop()
744 rawlen = len(self._raw)
744 rawlen = len(self._raw)
745 self._dirtyoffset = rawlen
745 self._dirtyoffset = rawlen
746 if rawlen < wantedlen:
746 if rawlen < wantedlen:
747 if self._dirtyoffset is None:
747 if self._dirtyoffset is None:
748 self._dirtyoffset = rawlen
748 self._dirtyoffset = rawlen
749 # TODO: zero fill entire record, because it's invalid not missing?
749 # TODO: zero fill entire record, because it's invalid not missing?
750 self._raw.extend(b'\xff' * (wantedlen - rawlen))
750 self._raw.extend(b'\xff' * (wantedlen - rawlen))
751
751
752 def getfnode(self, node, computemissing=True):
752 def getfnode(self, node, computemissing=True):
753 """Obtain the filenode of the .hgtags file at a specified revision.
753 """Obtain the filenode of the .hgtags file at a specified revision.
754
754
755 If the value is in the cache, the entry will be validated and returned.
755 If the value is in the cache, the entry will be validated and returned.
756 Otherwise, the filenode will be computed and returned unless
756 Otherwise, the filenode will be computed and returned unless
757 "computemissing" is False. In that case, None will be returned if
757 "computemissing" is False. In that case, None will be returned if
758 the entry is missing or False if the entry is invalid without
758 the entry is missing or False if the entry is invalid without
759 any potentially expensive computation being performed.
759 any potentially expensive computation being performed.
760
760
761 If an .hgtags does not exist at the specified revision, nullid is
761 If an .hgtags does not exist at the specified revision, nullid is
762 returned.
762 returned.
763 """
763 """
764 if node == self._repo.nullid:
764 if node == self._repo.nullid:
765 return node
765 return node
766
766
767 ctx = self._repo[node]
767 ctx = self._repo[node]
768 rev = ctx.rev()
768 rev = ctx.rev()
769
769
770 self.lookupcount += 1
770 self.lookupcount += 1
771
771
772 offset = rev * _fnodesrecsize
772 offset = rev * _fnodesrecsize
773 record = b'%s' % self._raw[offset : offset + _fnodesrecsize]
773 record = b'%s' % self._raw[offset : offset + _fnodesrecsize]
774 properprefix = node[0:4]
774 properprefix = node[0:4]
775
775
776 # Validate and return existing entry.
776 # Validate and return existing entry.
777 if record != _fnodesmissingrec and len(record) == _fnodesrecsize:
777 if record != _fnodesmissingrec and len(record) == _fnodesrecsize:
778 fileprefix = record[0:4]
778 fileprefix = record[0:4]
779
779
780 if fileprefix == properprefix:
780 if fileprefix == properprefix:
781 self.hitcount += 1
781 self.hitcount += 1
782 return record[4:]
782 return record[4:]
783
783
784 # Fall through.
784 # Fall through.
785
785
786 # If we get here, the entry is either missing or invalid.
786 # If we get here, the entry is either missing or invalid.
787
787
788 if not computemissing:
788 if not computemissing:
789 if record != _fnodesmissingrec:
789 if record != _fnodesmissingrec:
790 return False
790 return False
791 return None
791 return None
792
792
793 fnode = self._computefnode(node)
793 fnode = self._computefnode(node)
794 self._writeentry(offset, properprefix, fnode)
794 self._writeentry(offset, properprefix, fnode)
795 return fnode
795 return fnode
796
796
797 def _computefnode(self, node):
797 def _computefnode(self, node):
798 """Finds the tag filenode for a node which is missing or invalid
798 """Finds the tag filenode for a node which is missing or invalid
799 in cache"""
799 in cache"""
800 ctx = self._repo[node]
800 ctx = self._repo[node]
801 rev = ctx.rev()
801 rev = ctx.rev()
802 fnode = None
802 fnode = None
803 cl = self._repo.changelog
803 cl = self._repo.changelog
804 p1rev, p2rev = cl._uncheckedparentrevs(rev)
804 p1rev, p2rev = cl._uncheckedparentrevs(rev)
805 p1node = cl.node(p1rev)
805 p1node = cl.node(p1rev)
806 p1fnode = self.getfnode(p1node, computemissing=False)
806 p1fnode = self.getfnode(p1node, computemissing=False)
807 if p2rev != nullrev:
807 if p2rev != nullrev:
808 # There is some no-merge changeset where p1 is null and p2 is set
808 # There is some no-merge changeset where p1 is null and p2 is set
809 # Processing them as merge is just slower, but still gives a good
809 # Processing them as merge is just slower, but still gives a good
810 # result.
810 # result.
811 p2node = cl.node(p1rev)
811 p2node = cl.node(p2rev)
812 p2fnode = self.getfnode(p2node, computemissing=False)
812 p2fnode = self.getfnode(p2node, computemissing=False)
813 if p1fnode != p2fnode:
813 if p1fnode != p2fnode:
814 # we cannot rely on readfast because we don't know against what
814 # we cannot rely on readfast because we don't know against what
815 # parent the readfast delta is computed
815 # parent the readfast delta is computed
816 p1fnode = None
816 p1fnode = None
817 if p1fnode:
817 if p1fnode:
818 mctx = ctx.manifestctx()
818 mctx = ctx.manifestctx()
819 fnode = mctx.readfast().get(b'.hgtags')
819 fnode = mctx.readfast().get(b'.hgtags')
820 if fnode is None:
820 if fnode is None:
821 fnode = p1fnode
821 fnode = p1fnode
822 if fnode is None:
822 if fnode is None:
823 # Populate missing entry.
823 # Populate missing entry.
824 try:
824 try:
825 fnode = ctx.filenode(b'.hgtags')
825 fnode = ctx.filenode(b'.hgtags')
826 except error.LookupError:
826 except error.LookupError:
827 # No .hgtags file on this revision.
827 # No .hgtags file on this revision.
828 fnode = self._repo.nullid
828 fnode = self._repo.nullid
829 return fnode
829 return fnode
830
830
831 def setfnode(self, node, fnode):
831 def setfnode(self, node, fnode):
832 """Set the .hgtags filenode for a given changeset."""
832 """Set the .hgtags filenode for a given changeset."""
833 assert len(fnode) == 20
833 assert len(fnode) == 20
834 ctx = self._repo[node]
834 ctx = self._repo[node]
835
835
836 # Do a lookup first to avoid writing if nothing has changed.
836 # Do a lookup first to avoid writing if nothing has changed.
837 if self.getfnode(ctx.node(), computemissing=False) == fnode:
837 if self.getfnode(ctx.node(), computemissing=False) == fnode:
838 return
838 return
839
839
840 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
840 self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
841
841
842 def refresh_invalid_nodes(self, nodes):
842 def refresh_invalid_nodes(self, nodes):
843 """recomputes file nodes for a given set of nodes which has unknown
843 """recomputes file nodes for a given set of nodes which has unknown
844 filenodes for them in the cache
844 filenodes for them in the cache
845 Also updates the in-memory cache with the correct filenode.
845 Also updates the in-memory cache with the correct filenode.
846 Caller needs to take care about calling `.write()` so that updates are
846 Caller needs to take care about calling `.write()` so that updates are
847 persisted.
847 persisted.
848 Returns a map {node: recomputed fnode}
848 Returns a map {node: recomputed fnode}
849 """
849 """
850 fixed_nodemap = {}
850 fixed_nodemap = {}
851 for node in nodes:
851 for node in nodes:
852 fnode = self._computefnode(node)
852 fnode = self._computefnode(node)
853 fixed_nodemap[node] = fnode
853 fixed_nodemap[node] = fnode
854 self.setfnode(node, fnode)
854 self.setfnode(node, fnode)
855 return fixed_nodemap
855 return fixed_nodemap
856
856
857 def _writeentry(self, offset, prefix, fnode):
857 def _writeentry(self, offset, prefix, fnode):
858 # Slices on array instances only accept other array.
858 # Slices on array instances only accept other array.
859 entry = bytearray(prefix + fnode)
859 entry = bytearray(prefix + fnode)
860 self._raw[offset : offset + _fnodesrecsize] = entry
860 self._raw[offset : offset + _fnodesrecsize] = entry
861 # self._dirtyoffset could be None.
861 # self._dirtyoffset could be None.
862 self._dirtyoffset = min(self._dirtyoffset or 0, offset or 0)
862 self._dirtyoffset = min(self._dirtyoffset or 0, offset or 0)
863
863
864 def write(self):
864 def write(self):
865 """Perform all necessary writes to cache file.
865 """Perform all necessary writes to cache file.
866
866
867 This may no-op if no writes are needed or if a write lock could
867 This may no-op if no writes are needed or if a write lock could
868 not be obtained.
868 not be obtained.
869 """
869 """
870 if self._dirtyoffset is None:
870 if self._dirtyoffset is None:
871 return
871 return
872
872
873 data = self._raw[self._dirtyoffset :]
873 data = self._raw[self._dirtyoffset :]
874 if not data:
874 if not data:
875 return
875 return
876
876
877 repo = self._repo
877 repo = self._repo
878
878
879 try:
879 try:
880 lock = repo.lock(wait=False)
880 lock = repo.lock(wait=False)
881 except error.LockError:
881 except error.LockError:
882 repo.ui.log(
882 repo.ui.log(
883 b'tagscache',
883 b'tagscache',
884 b'not writing .hg/cache/%s because '
884 b'not writing .hg/cache/%s because '
885 b'lock cannot be acquired\n' % _fnodescachefile,
885 b'lock cannot be acquired\n' % _fnodescachefile,
886 )
886 )
887 return
887 return
888
888
889 try:
889 try:
890 f = repo.cachevfs.open(_fnodescachefile, b'ab')
890 f = repo.cachevfs.open(_fnodescachefile, b'ab')
891 try:
891 try:
892 # if the file has been truncated
892 # if the file has been truncated
893 actualoffset = f.tell()
893 actualoffset = f.tell()
894 if actualoffset < self._dirtyoffset:
894 if actualoffset < self._dirtyoffset:
895 self._dirtyoffset = actualoffset
895 self._dirtyoffset = actualoffset
896 data = self._raw[self._dirtyoffset :]
896 data = self._raw[self._dirtyoffset :]
897 f.seek(self._dirtyoffset)
897 f.seek(self._dirtyoffset)
898 f.truncate()
898 f.truncate()
899 repo.ui.log(
899 repo.ui.log(
900 b'tagscache',
900 b'tagscache',
901 b'writing %d bytes to cache/%s\n'
901 b'writing %d bytes to cache/%s\n'
902 % (len(data), _fnodescachefile),
902 % (len(data), _fnodescachefile),
903 )
903 )
904 f.write(data)
904 f.write(data)
905 self._dirtyoffset = None
905 self._dirtyoffset = None
906 finally:
906 finally:
907 f.close()
907 f.close()
908 except (IOError, OSError) as inst:
908 except (IOError, OSError) as inst:
909 repo.ui.log(
909 repo.ui.log(
910 b'tagscache',
910 b'tagscache',
911 b"couldn't write cache/%s: %s\n"
911 b"couldn't write cache/%s: %s\n"
912 % (_fnodescachefile, stringutil.forcebytestr(inst)),
912 % (_fnodescachefile, stringutil.forcebytestr(inst)),
913 )
913 )
914 finally:
914 finally:
915 lock.release()
915 lock.release()
@@ -1,935 +1,990 b''
1 setup
1 setup
2
2
3 $ cat >> $HGRCPATH << EOF
3 $ cat >> $HGRCPATH << EOF
4 > [extensions]
4 > [extensions]
5 > blackbox=
5 > blackbox=
6 > mock=$TESTDIR/mockblackbox.py
6 > mock=$TESTDIR/mockblackbox.py
7 > [blackbox]
7 > [blackbox]
8 > track = command, commandfinish, tagscache
8 > track = command, commandfinish, tagscache
9 > EOF
9 > EOF
10
10
11 Helper functions:
11 Helper functions:
12
12
13 $ cacheexists() {
13 $ cacheexists() {
14 > [ -f .hg/cache/tags2-visible ] && echo "tag cache exists" || echo "no tag cache"
14 > [ -f .hg/cache/tags2-visible ] && echo "tag cache exists" || echo "no tag cache"
15 > }
15 > }
16
16
17 $ fnodescacheexists() {
17 $ fnodescacheexists() {
18 > [ -f .hg/cache/hgtagsfnodes1 ] && echo "fnodes cache exists" || echo "no fnodes cache"
18 > [ -f .hg/cache/hgtagsfnodes1 ] && echo "fnodes cache exists" || echo "no fnodes cache"
19 > }
19 > }
20
20
21 $ dumptags() {
21 $ dumptags() {
22 > rev=$1
22 > rev=$1
23 > echo "rev $rev: .hgtags:"
23 > echo "rev $rev: .hgtags:"
24 > hg cat -r$rev .hgtags
24 > hg cat -r$rev .hgtags
25 > }
25 > }
26
26
27 # XXX need to test that the tag cache works when we strip an old head
27 # XXX need to test that the tag cache works when we strip an old head
28 # and add a new one rooted off non-tip: i.e. node and rev of tip are the
28 # and add a new one rooted off non-tip: i.e. node and rev of tip are the
29 # same, but stuff has changed behind tip.
29 # same, but stuff has changed behind tip.
30
30
31 Setup:
31 Setup:
32
32
33 $ hg init t
33 $ hg init t
34 $ cd t
34 $ cd t
35 $ cacheexists
35 $ cacheexists
36 no tag cache
36 no tag cache
37 $ fnodescacheexists
37 $ fnodescacheexists
38 no fnodes cache
38 no fnodes cache
39 $ hg id
39 $ hg id
40 000000000000 tip
40 000000000000 tip
41 $ cacheexists
41 $ cacheexists
42 no tag cache
42 no tag cache
43 $ fnodescacheexists
43 $ fnodescacheexists
44 no fnodes cache
44 no fnodes cache
45 $ echo a > a
45 $ echo a > a
46 $ hg add a
46 $ hg add a
47 $ hg commit -m "test"
47 $ hg commit -m "test"
48 $ hg co
48 $ hg co
49 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 $ hg identify
50 $ hg identify
51 acb14030fe0a tip
51 acb14030fe0a tip
52 $ hg identify -r 'wdir()'
52 $ hg identify -r 'wdir()'
53 acb14030fe0a tip
53 acb14030fe0a tip
54 $ cacheexists
54 $ cacheexists
55 tag cache exists
55 tag cache exists
56 No fnodes cache because .hgtags file doesn't exist
56 No fnodes cache because .hgtags file doesn't exist
57 (this is an implementation detail)
57 (this is an implementation detail)
58 $ fnodescacheexists
58 $ fnodescacheexists
59 no fnodes cache
59 no fnodes cache
60
60
61 Try corrupting the cache
61 Try corrupting the cache
62
62
63 $ printf 'a b' > .hg/cache/tags2-visible
63 $ printf 'a b' > .hg/cache/tags2-visible
64 $ hg identify
64 $ hg identify
65 acb14030fe0a tip
65 acb14030fe0a tip
66 $ cacheexists
66 $ cacheexists
67 tag cache exists
67 tag cache exists
68 $ fnodescacheexists
68 $ fnodescacheexists
69 no fnodes cache
69 no fnodes cache
70 $ hg identify
70 $ hg identify
71 acb14030fe0a tip
71 acb14030fe0a tip
72
72
73 Create local tag with long name:
73 Create local tag with long name:
74
74
75 $ T=`hg identify --debug --id`
75 $ T=`hg identify --debug --id`
76 $ hg tag -l "This is a local tag with a really long name!"
76 $ hg tag -l "This is a local tag with a really long name!"
77 $ hg tags
77 $ hg tags
78 tip 0:acb14030fe0a
78 tip 0:acb14030fe0a
79 This is a local tag with a really long name! 0:acb14030fe0a
79 This is a local tag with a really long name! 0:acb14030fe0a
80 $ rm .hg/localtags
80 $ rm .hg/localtags
81
81
82 Create a tag behind hg's back:
82 Create a tag behind hg's back:
83
83
84 $ echo "$T first" > .hgtags
84 $ echo "$T first" > .hgtags
85 $ cat .hgtags
85 $ cat .hgtags
86 acb14030fe0a21b60322c440ad2d20cf7685a376 first
86 acb14030fe0a21b60322c440ad2d20cf7685a376 first
87 $ hg add .hgtags
87 $ hg add .hgtags
88 $ hg commit -m "add tags"
88 $ hg commit -m "add tags"
89 $ hg tags
89 $ hg tags
90 tip 1:b9154636be93
90 tip 1:b9154636be93
91 first 0:acb14030fe0a
91 first 0:acb14030fe0a
92 $ hg identify
92 $ hg identify
93 b9154636be93 tip
93 b9154636be93 tip
94
94
95 We should have a fnodes cache now that we have a real tag
95 We should have a fnodes cache now that we have a real tag
96 The cache should have an empty entry for rev 0 and a valid entry for rev 1.
96 The cache should have an empty entry for rev 0 and a valid entry for rev 1.
97
97
98
98
99 $ fnodescacheexists
99 $ fnodescacheexists
100 fnodes cache exists
100 fnodes cache exists
101 $ f --size --hexdump .hg/cache/hgtagsfnodes1
101 $ f --size --hexdump .hg/cache/hgtagsfnodes1
102 .hg/cache/hgtagsfnodes1: size=48
102 .hg/cache/hgtagsfnodes1: size=48
103 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
103 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
104 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
104 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
105 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
105 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
106 $ hg debugtagscache
106 $ hg debugtagscache
107 0 acb14030fe0a21b60322c440ad2d20cf7685a376 missing
107 0 acb14030fe0a21b60322c440ad2d20cf7685a376 missing
108 1 b9154636be938d3d431e75a7c906504a079bfe07 26b7b4a773e09ee3c52f510e19e05e1ff966d859
108 1 b9154636be938d3d431e75a7c906504a079bfe07 26b7b4a773e09ee3c52f510e19e05e1ff966d859
109
109
110 Repeat with cold tag cache:
110 Repeat with cold tag cache:
111
111
112 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
112 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
113 $ hg identify
113 $ hg identify
114 b9154636be93 tip
114 b9154636be93 tip
115
115
116 $ fnodescacheexists
116 $ fnodescacheexists
117 fnodes cache exists
117 fnodes cache exists
118 $ f --size --hexdump .hg/cache/hgtagsfnodes1
118 $ f --size --hexdump .hg/cache/hgtagsfnodes1
119 .hg/cache/hgtagsfnodes1: size=48
119 .hg/cache/hgtagsfnodes1: size=48
120 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
120 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
121 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
121 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
122 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
122 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
123
123
124 And again, but now unable to write tag cache or lock file:
124 And again, but now unable to write tag cache or lock file:
125
125
126 #if unix-permissions no-fsmonitor
126 #if unix-permissions no-fsmonitor
127
127
128 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
128 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
129 $ chmod 555 .hg/cache
129 $ chmod 555 .hg/cache
130 $ hg identify
130 $ hg identify
131 b9154636be93 tip
131 b9154636be93 tip
132 $ chmod 755 .hg/cache
132 $ chmod 755 .hg/cache
133
133
134 (this block should be protected by no-fsmonitor, because "chmod 555 .hg"
134 (this block should be protected by no-fsmonitor, because "chmod 555 .hg"
135 makes watchman fail at accessing to files under .hg)
135 makes watchman fail at accessing to files under .hg)
136
136
137 $ chmod 555 .hg
137 $ chmod 555 .hg
138 $ hg identify
138 $ hg identify
139 b9154636be93 tip
139 b9154636be93 tip
140 $ chmod 755 .hg
140 $ chmod 755 .hg
141 #endif
141 #endif
142
142
143 Tag cache debug info written to blackbox log
143 Tag cache debug info written to blackbox log
144
144
145 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
145 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
146 $ hg identify
146 $ hg identify
147 b9154636be93 tip
147 b9154636be93 tip
148 $ hg blackbox -l 6
148 $ hg blackbox -l 6
149 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify
149 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify
150 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing 48 bytes to cache/hgtagsfnodes1
150 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing 48 bytes to cache/hgtagsfnodes1
151 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob)
151 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob)
152 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags
152 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags
153 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob)
153 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob)
154 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6
154 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6
155
155
156 Failure to acquire lock results in no write
156 Failure to acquire lock results in no write
157
157
158 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
158 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
159 $ echo 'foo:1' > .hg/store/lock
159 $ echo 'foo:1' > .hg/store/lock
160 $ hg identify
160 $ hg identify
161 b9154636be93 tip
161 b9154636be93 tip
162 $ hg blackbox -l 6
162 $ hg blackbox -l 6
163 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify
163 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify
164 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> not writing .hg/cache/hgtagsfnodes1 because lock cannot be acquired
164 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> not writing .hg/cache/hgtagsfnodes1 because lock cannot be acquired
165 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob)
165 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> 0/2 cache hits/lookups in * seconds (glob)
166 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags
166 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> writing .hg/cache/tags2-visible with 1 tags
167 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob)
167 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> identify exited 0 after * seconds (glob)
168 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6
168 1970-01-01 00:00:00.000 bob @b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l 6
169
169
170 $ fnodescacheexists
170 $ fnodescacheexists
171 no fnodes cache
171 no fnodes cache
172
172
173 $ rm .hg/store/lock
173 $ rm .hg/store/lock
174
174
175 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
175 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
176 $ hg identify
176 $ hg identify
177 b9154636be93 tip
177 b9154636be93 tip
178
178
179 Create a branch:
179 Create a branch:
180
180
181 $ echo bb > a
181 $ echo bb > a
182 $ hg status
182 $ hg status
183 M a
183 M a
184 $ hg identify
184 $ hg identify
185 b9154636be93+ tip
185 b9154636be93+ tip
186 $ hg co first
186 $ hg co first
187 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
187 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
188 $ hg id
188 $ hg id
189 acb14030fe0a+ first
189 acb14030fe0a+ first
190 $ hg id -r 'wdir()'
190 $ hg id -r 'wdir()'
191 acb14030fe0a+ first
191 acb14030fe0a+ first
192 $ hg -v id
192 $ hg -v id
193 acb14030fe0a+ first
193 acb14030fe0a+ first
194 $ hg status
194 $ hg status
195 M a
195 M a
196 $ echo 1 > b
196 $ echo 1 > b
197 $ hg add b
197 $ hg add b
198 $ hg commit -m "branch"
198 $ hg commit -m "branch"
199 created new head
199 created new head
200
200
201 Creating a new commit shouldn't append the .hgtags fnodes cache until
201 Creating a new commit shouldn't append the .hgtags fnodes cache until
202 tags info is accessed
202 tags info is accessed
203
203
204 $ f --size --hexdump .hg/cache/hgtagsfnodes1
204 $ f --size --hexdump .hg/cache/hgtagsfnodes1
205 .hg/cache/hgtagsfnodes1: size=48
205 .hg/cache/hgtagsfnodes1: size=48
206 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
206 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
207 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
207 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
208 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
208 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
209
209
210 $ hg id
210 $ hg id
211 c8edf04160c7 tip
211 c8edf04160c7 tip
212
212
213 First 4 bytes of record 3 are changeset fragment
213 First 4 bytes of record 3 are changeset fragment
214
214
215 $ f --size --hexdump .hg/cache/hgtagsfnodes1
215 $ f --size --hexdump .hg/cache/hgtagsfnodes1
216 .hg/cache/hgtagsfnodes1: size=72
216 .hg/cache/hgtagsfnodes1: size=72
217 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
217 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
218 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
218 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
219 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
219 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
220 0030: c8 ed f0 41 00 00 00 00 00 00 00 00 00 00 00 00 |...A............|
220 0030: c8 ed f0 41 00 00 00 00 00 00 00 00 00 00 00 00 |...A............|
221 0040: 00 00 00 00 00 00 00 00 |........|
221 0040: 00 00 00 00 00 00 00 00 |........|
222
222
223 Merge the two heads:
223 Merge the two heads:
224
224
225 $ hg merge 1
225 $ hg merge 1
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 (branch merge, don't forget to commit)
227 (branch merge, don't forget to commit)
228 $ hg blackbox -l3
228 $ hg blackbox -l3
229 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28 (5000)> merge 1
229 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28 (5000)> merge 1
230 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> merge 1 exited 0 after * seconds (glob)
230 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> merge 1 exited 0 after * seconds (glob)
231 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l3
231 1970-01-01 00:00:00.000 bob @c8edf04160c7f731e4589d66ab3ab3486a64ac28+b9154636be938d3d431e75a7c906504a079bfe07 (5000)> blackbox -l3
232 $ hg id
232 $ hg id
233 c8edf04160c7+b9154636be93+ tip
233 c8edf04160c7+b9154636be93+ tip
234 $ hg status
234 $ hg status
235 M .hgtags
235 M .hgtags
236 $ hg commit -m "merge"
236 $ hg commit -m "merge"
237
237
238 Create a fake head, make sure tag not visible afterwards:
238 Create a fake head, make sure tag not visible afterwards:
239
239
240 $ cp .hgtags tags
240 $ cp .hgtags tags
241 $ hg tag last
241 $ hg tag last
242 $ hg rm .hgtags
242 $ hg rm .hgtags
243 $ hg commit -m "remove"
243 $ hg commit -m "remove"
244
244
245 $ mv tags .hgtags
245 $ mv tags .hgtags
246 $ hg add .hgtags
246 $ hg add .hgtags
247 $ hg commit -m "readd"
247 $ hg commit -m "readd"
248 $
248 $
249 $ hg tags
249 $ hg tags
250 tip 6:35ff301afafe
250 tip 6:35ff301afafe
251 first 0:acb14030fe0a
251 first 0:acb14030fe0a
252
252
253 Add invalid tags:
253 Add invalid tags:
254
254
255 $ echo "spam" >> .hgtags
255 $ echo "spam" >> .hgtags
256 $ echo >> .hgtags
256 $ echo >> .hgtags
257 $ echo "foo bar" >> .hgtags
257 $ echo "foo bar" >> .hgtags
258 $ echo "a5a5 invalid" >> .hg/localtags
258 $ echo "a5a5 invalid" >> .hg/localtags
259 $ cat .hgtags
259 $ cat .hgtags
260 acb14030fe0a21b60322c440ad2d20cf7685a376 first
260 acb14030fe0a21b60322c440ad2d20cf7685a376 first
261 spam
261 spam
262
262
263 foo bar
263 foo bar
264 $ hg commit -m "tags"
264 $ hg commit -m "tags"
265
265
266 Report tag parse error on other head:
266 Report tag parse error on other head:
267
267
268 $ hg up 3
268 $ hg up 3
269 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
269 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 $ echo 'x y' >> .hgtags
270 $ echo 'x y' >> .hgtags
271 $ hg commit -m "head"
271 $ hg commit -m "head"
272 created new head
272 created new head
273
273
274 $ hg tags --debug
274 $ hg tags --debug
275 .hgtags@75d9f02dfe28, line 2: cannot parse entry
275 .hgtags@75d9f02dfe28, line 2: cannot parse entry
276 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
276 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
277 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
277 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
278 tip 8:c4be69a18c11e8bc3a5fdbb576017c25f7d84663
278 tip 8:c4be69a18c11e8bc3a5fdbb576017c25f7d84663
279 first 0:acb14030fe0a21b60322c440ad2d20cf7685a376
279 first 0:acb14030fe0a21b60322c440ad2d20cf7685a376
280 $ hg tip
280 $ hg tip
281 changeset: 8:c4be69a18c11
281 changeset: 8:c4be69a18c11
282 tag: tip
282 tag: tip
283 parent: 3:ac5e980c4dc0
283 parent: 3:ac5e980c4dc0
284 user: test
284 user: test
285 date: Thu Jan 01 00:00:00 1970 +0000
285 date: Thu Jan 01 00:00:00 1970 +0000
286 summary: head
286 summary: head
287
287
288
288
289 Test tag precedence rules:
289 Test tag precedence rules:
290
290
291 $ cd ..
291 $ cd ..
292 $ hg init t2
292 $ hg init t2
293 $ cd t2
293 $ cd t2
294 $ echo foo > foo
294 $ echo foo > foo
295 $ hg add foo
295 $ hg add foo
296 $ hg ci -m 'add foo' # rev 0
296 $ hg ci -m 'add foo' # rev 0
297 $ hg tag bar # rev 1
297 $ hg tag bar # rev 1
298 $ echo >> foo
298 $ echo >> foo
299 $ hg ci -m 'change foo 1' # rev 2
299 $ hg ci -m 'change foo 1' # rev 2
300 $ hg up -C 1
300 $ hg up -C 1
301 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
301 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 $ hg tag -r 1 -f bar # rev 3
302 $ hg tag -r 1 -f bar # rev 3
303 $ hg up -C 1
303 $ hg up -C 1
304 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
304 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
305 $ echo >> foo
305 $ echo >> foo
306 $ hg ci -m 'change foo 2' # rev 4
306 $ hg ci -m 'change foo 2' # rev 4
307 created new head
307 created new head
308 $ hg tags
308 $ hg tags
309 tip 4:0c192d7d5e6b
309 tip 4:0c192d7d5e6b
310 bar 1:78391a272241
310 bar 1:78391a272241
311
311
312 Repeat in case of cache effects:
312 Repeat in case of cache effects:
313
313
314 $ hg tags
314 $ hg tags
315 tip 4:0c192d7d5e6b
315 tip 4:0c192d7d5e6b
316 bar 1:78391a272241
316 bar 1:78391a272241
317
317
318 Detailed dump of tag info:
318 Detailed dump of tag info:
319
319
320 $ hg heads -q # expect 4, 3, 2
320 $ hg heads -q # expect 4, 3, 2
321 4:0c192d7d5e6b
321 4:0c192d7d5e6b
322 3:6fa450212aeb
322 3:6fa450212aeb
323 2:7a94127795a3
323 2:7a94127795a3
324 $ dumptags 2
324 $ dumptags 2
325 rev 2: .hgtags:
325 rev 2: .hgtags:
326 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
326 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
327 $ dumptags 3
327 $ dumptags 3
328 rev 3: .hgtags:
328 rev 3: .hgtags:
329 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
329 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
330 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
330 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
331 78391a272241d70354aa14c874552cad6b51bb42 bar
331 78391a272241d70354aa14c874552cad6b51bb42 bar
332 $ dumptags 4
332 $ dumptags 4
333 rev 4: .hgtags:
333 rev 4: .hgtags:
334 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
334 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
335
335
336 Dump cache:
336 Dump cache:
337
337
338 $ cat .hg/cache/tags2-visible
338 $ cat .hg/cache/tags2-visible
339 4 0c192d7d5e6b78a714de54a2e9627952a877e25a
339 4 0c192d7d5e6b78a714de54a2e9627952a877e25a
340 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
340 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
341 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
341 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
342 78391a272241d70354aa14c874552cad6b51bb42 bar
342 78391a272241d70354aa14c874552cad6b51bb42 bar
343
343
344 $ f --size --hexdump .hg/cache/hgtagsfnodes1
344 $ f --size --hexdump .hg/cache/hgtagsfnodes1
345 .hg/cache/hgtagsfnodes1: size=120
345 .hg/cache/hgtagsfnodes1: size=120
346 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
346 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
347 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
347 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
348 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
348 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
349 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
349 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
350 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
350 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
351 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
351 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
352 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
352 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
353 0070: 78 ee 5a 2d ad bc 94 3d |x.Z-...=|
353 0070: 78 ee 5a 2d ad bc 94 3d |x.Z-...=|
354
354
355 Corrupt the .hgtags fnodes cache
355 Corrupt the .hgtags fnodes cache
356 Extra junk data at the end should get overwritten on next cache update
356 Extra junk data at the end should get overwritten on next cache update
357
357
358 $ echo extra >> .hg/cache/hgtagsfnodes1
358 $ echo extra >> .hg/cache/hgtagsfnodes1
359 $ echo dummy1 > foo
359 $ echo dummy1 > foo
360 $ hg commit -m throwaway1
360 $ hg commit -m throwaway1
361
361
362 $ hg tags
362 $ hg tags
363 tip 5:8dbfe60eff30
363 tip 5:8dbfe60eff30
364 bar 1:78391a272241
364 bar 1:78391a272241
365
365
366 $ hg blackbox -l 6
366 $ hg blackbox -l 6
367 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags
367 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags
368 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing 24 bytes to cache/hgtagsfnodes1
368 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing 24 bytes to cache/hgtagsfnodes1
369 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> 3/4 cache hits/lookups in * seconds (glob)
369 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> 3/4 cache hits/lookups in * seconds (glob)
370 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing .hg/cache/tags2-visible with 1 tags
370 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> writing .hg/cache/tags2-visible with 1 tags
371 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags exited 0 after * seconds (glob)
371 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> tags exited 0 after * seconds (glob)
372 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> blackbox -l 6
372 1970-01-01 00:00:00.000 bob @8dbfe60eff306a54259cfe007db9e330e7ecf866 (5000)> blackbox -l 6
373
373
374 On junk data + missing cache entries, hg also overwrites the junk.
374 On junk data + missing cache entries, hg also overwrites the junk.
375
375
376 $ rm -f .hg/cache/tags2-visible
376 $ rm -f .hg/cache/tags2-visible
377 >>> import os
377 >>> import os
378 >>> with open(".hg/cache/hgtagsfnodes1", "ab+") as fp:
378 >>> with open(".hg/cache/hgtagsfnodes1", "ab+") as fp:
379 ... fp.seek(-10, os.SEEK_END) and None
379 ... fp.seek(-10, os.SEEK_END) and None
380 ... fp.truncate() and None
380 ... fp.truncate() and None
381
381
382 $ hg debugtagscache | tail -2
382 $ hg debugtagscache | tail -2
383 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
383 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
384 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 missing
384 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 missing
385 $ hg tags
385 $ hg tags
386 tip 5:8dbfe60eff30
386 tip 5:8dbfe60eff30
387 bar 1:78391a272241
387 bar 1:78391a272241
388 $ hg debugtagscache | tail -2
388 $ hg debugtagscache | tail -2
389 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
389 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
390 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8af31de17fab7422878ee5a2dadbc943d
390 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8af31de17fab7422878ee5a2dadbc943d
391
391
392 If the 4 bytes of node hash for a record don't match an existing node, the entry
392 If the 4 bytes of node hash for a record don't match an existing node, the entry
393 is flagged as invalid.
393 is flagged as invalid.
394
394
395 >>> import os
395 >>> import os
396 >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp:
396 >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp:
397 ... fp.seek(-24, os.SEEK_END) and None
397 ... fp.seek(-24, os.SEEK_END) and None
398 ... fp.write(b'\xde\xad') and None
398 ... fp.write(b'\xde\xad') and None
399
399
400 $ f --size --hexdump .hg/cache/hgtagsfnodes1
400 $ f --size --hexdump .hg/cache/hgtagsfnodes1
401 .hg/cache/hgtagsfnodes1: size=144
401 .hg/cache/hgtagsfnodes1: size=144
402 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
402 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
403 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
403 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
404 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
404 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
405 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
405 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
406 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
406 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
407 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
407 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
408 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
408 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
409 0070: 78 ee 5a 2d ad bc 94 3d de ad e6 0e 0c 04 f2 a8 |x.Z-...=........|
409 0070: 78 ee 5a 2d ad bc 94 3d de ad e6 0e 0c 04 f2 a8 |x.Z-...=........|
410 0080: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=|
410 0080: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=|
411
411
412 $ hg debugtagscache | tail -2
412 $ hg debugtagscache | tail -2
413 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
413 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
414 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 invalid
414 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 invalid
415
415
416 $ hg tags
416 $ hg tags
417 tip 5:8dbfe60eff30
417 tip 5:8dbfe60eff30
418 bar 1:78391a272241
418 bar 1:78391a272241
419
419
420 BUG: If the filenode part of an entry in hgtagsfnodes is corrupt and
420 BUG: If the filenode part of an entry in hgtagsfnodes is corrupt and
421 tags2-visible is missing, `hg tags` aborts. Corrupting the leading 4 bytes of
421 tags2-visible is missing, `hg tags` aborts. Corrupting the leading 4 bytes of
422 node hash (as above) doesn't seem to trigger the issue. Also note that the
422 node hash (as above) doesn't seem to trigger the issue. Also note that the
423 debug command hides the corruption, both with and without tags2-visible.
423 debug command hides the corruption, both with and without tags2-visible.
424
424
425 $ mv .hg/cache/hgtagsfnodes1 .hg/cache/hgtagsfnodes1.bak
425 $ mv .hg/cache/hgtagsfnodes1 .hg/cache/hgtagsfnodes1.bak
426 $ hg debugupdatecaches
426 $ hg debugupdatecaches
427
427
428 >>> import os
428 >>> import os
429 >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp:
429 >>> with open(".hg/cache/hgtagsfnodes1", "rb+") as fp:
430 ... fp.seek(-16, os.SEEK_END) and None
430 ... fp.seek(-16, os.SEEK_END) and None
431 ... fp.write(b'\xde\xad') and None
431 ... fp.write(b'\xde\xad') and None
432
432
433 $ f --size --hexdump .hg/cache/hgtagsfnodes1
433 $ f --size --hexdump .hg/cache/hgtagsfnodes1
434 .hg/cache/hgtagsfnodes1: size=144
434 .hg/cache/hgtagsfnodes1: size=144
435 0000: bb d1 79 df 00 00 00 00 00 00 00 00 00 00 00 00 |..y.............|
435 0000: bb d1 79 df 00 00 00 00 00 00 00 00 00 00 00 00 |..y.............|
436 0010: 00 00 00 00 00 00 00 00 78 39 1a 27 0c 04 f2 a8 |........x9.'....|
436 0010: 00 00 00 00 00 00 00 00 78 39 1a 27 0c 04 f2 a8 |........x9.'....|
437 0020: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=|
437 0020: af 31 de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |.1....B(x.Z-...=|
438 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
438 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
439 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
439 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
440 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
440 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
441 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
441 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
442 0070: 78 ee 5a 2d ad bc 94 3d 8d bf e6 0e 0c 04 f2 a8 |x.Z-...=........|
442 0070: 78 ee 5a 2d ad bc 94 3d 8d bf e6 0e 0c 04 f2 a8 |x.Z-...=........|
443 0080: de ad de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |......B(x.Z-...=|
443 0080: de ad de 17 fa b7 42 28 78 ee 5a 2d ad bc 94 3d |......B(x.Z-...=|
444
444
445 $ hg debugtagscache | tail -2
445 $ hg debugtagscache | tail -2
446 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
446 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
447 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node)
447 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node)
448
448
449 $ rm -f .hg/cache/tags2-visible
449 $ rm -f .hg/cache/tags2-visible
450 $ hg debugtagscache | tail -2
450 $ hg debugtagscache | tail -2
451 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
451 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
452 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node)
452 5 8dbfe60eff306a54259cfe007db9e330e7ecf866 0c04f2a8deadde17fab7422878ee5a2dadbc943d (unknown node)
453
453
454 $ hg tags
454 $ hg tags
455 tip 5:8dbfe60eff30
455 tip 5:8dbfe60eff30
456 bar 1:78391a272241
456 bar 1:78391a272241
457
457
458 BUG: Unless this file is restored, the `hg tags` in the next unix-permissions
458 BUG: Unless this file is restored, the `hg tags` in the next unix-permissions
459 conditional will fail: "abort: data/.hgtags.i@0c04f2a8dead: no match found"
459 conditional will fail: "abort: data/.hgtags.i@0c04f2a8dead: no match found"
460
460
461 $ mv .hg/cache/hgtagsfnodes1.bak .hg/cache/hgtagsfnodes1
461 $ mv .hg/cache/hgtagsfnodes1.bak .hg/cache/hgtagsfnodes1
462
462
463 #if unix-permissions no-root
463 #if unix-permissions no-root
464 Errors writing to .hgtags fnodes cache are silently ignored
464 Errors writing to .hgtags fnodes cache are silently ignored
465
465
466 $ echo dummy2 > foo
466 $ echo dummy2 > foo
467 $ hg commit -m throwaway2
467 $ hg commit -m throwaway2
468
468
469 $ chmod a-w .hg/cache/hgtagsfnodes1
469 $ chmod a-w .hg/cache/hgtagsfnodes1
470 $ rm -f .hg/cache/tags2-visible
470 $ rm -f .hg/cache/tags2-visible
471
471
472 $ hg tags
472 $ hg tags
473 tip 6:b968051b5cf3
473 tip 6:b968051b5cf3
474 bar 1:78391a272241
474 bar 1:78391a272241
475
475
476 $ hg blackbox -l 6
476 $ hg blackbox -l 6
477 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags
477 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags
478 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> couldn't write cache/hgtagsfnodes1: [Errno *] * (glob)
478 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> couldn't write cache/hgtagsfnodes1: [Errno *] * (glob)
479 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob)
479 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob)
480 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags
480 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags
481 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob)
481 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob)
482 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6
482 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6
483
483
484 $ chmod a+w .hg/cache/hgtagsfnodes1
484 $ chmod a+w .hg/cache/hgtagsfnodes1
485
485
486 $ rm -f .hg/cache/tags2-visible
486 $ rm -f .hg/cache/tags2-visible
487 $ hg tags
487 $ hg tags
488 tip 6:b968051b5cf3
488 tip 6:b968051b5cf3
489 bar 1:78391a272241
489 bar 1:78391a272241
490
490
491 $ hg blackbox -l 6
491 $ hg blackbox -l 6
492 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags
492 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags
493 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing 24 bytes to cache/hgtagsfnodes1
493 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing 24 bytes to cache/hgtagsfnodes1
494 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob)
494 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/4 cache hits/lookups in * seconds (glob)
495 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags
495 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags
496 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob)
496 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob)
497 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6
497 1970-01-01 00:00:00.000 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> blackbox -l 6
498
498
499 $ f --size .hg/cache/hgtagsfnodes1
499 $ f --size .hg/cache/hgtagsfnodes1
500 .hg/cache/hgtagsfnodes1: size=168
500 .hg/cache/hgtagsfnodes1: size=168
501
501
502 $ hg -q --config extensions.strip= strip -r 6 --no-backup
502 $ hg -q --config extensions.strip= strip -r 6 --no-backup
503 #endif
503 #endif
504
504
505 Stripping doesn't truncate the tags cache until new data is available
505 Stripping doesn't truncate the tags cache until new data is available
506
506
507 $ rm -f .hg/cache/hgtagsfnodes1 .hg/cache/tags2-visible
507 $ rm -f .hg/cache/hgtagsfnodes1 .hg/cache/tags2-visible
508 $ hg tags
508 $ hg tags
509 tip 5:8dbfe60eff30
509 tip 5:8dbfe60eff30
510 bar 1:78391a272241
510 bar 1:78391a272241
511
511
512 $ f --size .hg/cache/hgtagsfnodes1
512 $ f --size .hg/cache/hgtagsfnodes1
513 .hg/cache/hgtagsfnodes1: size=144
513 .hg/cache/hgtagsfnodes1: size=144
514
514
515 $ hg -q --config extensions.strip= strip -r 5 --no-backup
515 $ hg -q --config extensions.strip= strip -r 5 --no-backup
516 $ hg tags
516 $ hg tags
517 tip 4:0c192d7d5e6b
517 tip 4:0c192d7d5e6b
518 bar 1:78391a272241
518 bar 1:78391a272241
519
519
520 $ hg blackbox -l 5
520 $ hg blackbox -l 5
521 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing 24 bytes to cache/hgtagsfnodes1
521 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing 24 bytes to cache/hgtagsfnodes1
522 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> 2/4 cache hits/lookups in * seconds (glob)
522 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> 2/4 cache hits/lookups in * seconds (glob)
523 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing .hg/cache/tags2-visible with 1 tags
523 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> writing .hg/cache/tags2-visible with 1 tags
524 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> tags exited 0 after * seconds (glob)
524 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> tags exited 0 after * seconds (glob)
525 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> blackbox -l 5
525 1970-01-01 00:00:00.000 bob @0c192d7d5e6b78a714de54a2e9627952a877e25a (5000)> blackbox -l 5
526
526
527 $ f --size .hg/cache/hgtagsfnodes1
527 $ f --size .hg/cache/hgtagsfnodes1
528 .hg/cache/hgtagsfnodes1: size=120
528 .hg/cache/hgtagsfnodes1: size=120
529
529
530 $ echo dummy > foo
530 $ echo dummy > foo
531 $ hg commit -m throwaway3
531 $ hg commit -m throwaway3
532
532
533 $ hg tags
533 $ hg tags
534 tip 5:035f65efb448
534 tip 5:035f65efb448
535 bar 1:78391a272241
535 bar 1:78391a272241
536
536
537 $ hg blackbox -l 6
537 $ hg blackbox -l 6
538 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags
538 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags
539 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing 24 bytes to cache/hgtagsfnodes1
539 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing 24 bytes to cache/hgtagsfnodes1
540 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> 3/4 cache hits/lookups in * seconds (glob)
540 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> 3/4 cache hits/lookups in * seconds (glob)
541 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing .hg/cache/tags2-visible with 1 tags
541 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> writing .hg/cache/tags2-visible with 1 tags
542 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags exited 0 after * seconds (glob)
542 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> tags exited 0 after * seconds (glob)
543 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> blackbox -l 6
543 1970-01-01 00:00:00.000 bob @035f65efb448350f4772141702a81ab1df48c465 (5000)> blackbox -l 6
544 $ f --size .hg/cache/hgtagsfnodes1
544 $ f --size .hg/cache/hgtagsfnodes1
545 .hg/cache/hgtagsfnodes1: size=144
545 .hg/cache/hgtagsfnodes1: size=144
546
546
547 $ hg -q --config extensions.strip= strip -r 5 --no-backup
547 $ hg -q --config extensions.strip= strip -r 5 --no-backup
548
548
549 Test tag removal:
549 Test tag removal:
550
550
551 $ hg tag --remove bar # rev 5
551 $ hg tag --remove bar # rev 5
552 $ hg tip -vp
552 $ hg tip -vp
553 changeset: 5:5f6e8655b1c7
553 changeset: 5:5f6e8655b1c7
554 tag: tip
554 tag: tip
555 user: test
555 user: test
556 date: Thu Jan 01 00:00:00 1970 +0000
556 date: Thu Jan 01 00:00:00 1970 +0000
557 files: .hgtags
557 files: .hgtags
558 description:
558 description:
559 Removed tag bar
559 Removed tag bar
560
560
561
561
562 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
562 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
563 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
563 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
564 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
564 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
565 @@ -1,1 +1,3 @@
565 @@ -1,1 +1,3 @@
566 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
566 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
567 +78391a272241d70354aa14c874552cad6b51bb42 bar
567 +78391a272241d70354aa14c874552cad6b51bb42 bar
568 +0000000000000000000000000000000000000000 bar
568 +0000000000000000000000000000000000000000 bar
569
569
570 $ hg tags
570 $ hg tags
571 tip 5:5f6e8655b1c7
571 tip 5:5f6e8655b1c7
572 $ hg tags # again, try to expose cache bugs
572 $ hg tags # again, try to expose cache bugs
573 tip 5:5f6e8655b1c7
573 tip 5:5f6e8655b1c7
574
574
575 Remove nonexistent tag:
575 Remove nonexistent tag:
576
576
577 $ hg tag --remove foobar
577 $ hg tag --remove foobar
578 abort: tag 'foobar' does not exist
578 abort: tag 'foobar' does not exist
579 [10]
579 [10]
580 $ hg tip
580 $ hg tip
581 changeset: 5:5f6e8655b1c7
581 changeset: 5:5f6e8655b1c7
582 tag: tip
582 tag: tip
583 user: test
583 user: test
584 date: Thu Jan 01 00:00:00 1970 +0000
584 date: Thu Jan 01 00:00:00 1970 +0000
585 summary: Removed tag bar
585 summary: Removed tag bar
586
586
587
587
588 Undo a tag with rollback:
588 Undo a tag with rollback:
589
589
590 $ hg rollback # destroy rev 5 (restore bar)
590 $ hg rollback # destroy rev 5 (restore bar)
591 repository tip rolled back to revision 4 (undo commit)
591 repository tip rolled back to revision 4 (undo commit)
592 working directory now based on revision 4
592 working directory now based on revision 4
593 $ hg tags
593 $ hg tags
594 tip 4:0c192d7d5e6b
594 tip 4:0c192d7d5e6b
595 bar 1:78391a272241
595 bar 1:78391a272241
596 $ hg tags
596 $ hg tags
597 tip 4:0c192d7d5e6b
597 tip 4:0c192d7d5e6b
598 bar 1:78391a272241
598 bar 1:78391a272241
599
599
600 Test tag rank:
600 Test tag rank:
601
601
602 $ cd ..
602 $ cd ..
603 $ hg init t3
603 $ hg init t3
604 $ cd t3
604 $ cd t3
605 $ echo foo > foo
605 $ echo foo > foo
606 $ hg add foo
606 $ hg add foo
607 $ hg ci -m 'add foo' # rev 0
607 $ hg ci -m 'add foo' # rev 0
608 $ hg tag -f bar # rev 1 bar -> 0
608 $ hg tag -f bar # rev 1 bar -> 0
609 $ hg tag -f bar # rev 2 bar -> 1
609 $ hg tag -f bar # rev 2 bar -> 1
610 $ hg tag -fr 0 bar # rev 3 bar -> 0
610 $ hg tag -fr 0 bar # rev 3 bar -> 0
611 $ hg tag -fr 1 bar # rev 4 bar -> 1
611 $ hg tag -fr 1 bar # rev 4 bar -> 1
612 $ hg tag -fr 0 bar # rev 5 bar -> 0
612 $ hg tag -fr 0 bar # rev 5 bar -> 0
613 $ hg tags
613 $ hg tags
614 tip 5:85f05169d91d
614 tip 5:85f05169d91d
615 bar 0:bbd179dfa0a7
615 bar 0:bbd179dfa0a7
616 $ hg co 3
616 $ hg co 3
617 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
617 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
618 $ echo barbar > foo
618 $ echo barbar > foo
619 $ hg ci -m 'change foo' # rev 6
619 $ hg ci -m 'change foo' # rev 6
620 created new head
620 created new head
621 $ hg tags
621 $ hg tags
622 tip 6:735c3ca72986
622 tip 6:735c3ca72986
623 bar 0:bbd179dfa0a7
623 bar 0:bbd179dfa0a7
624
624
625 Don't allow moving tag without -f:
625 Don't allow moving tag without -f:
626
626
627 $ hg tag -r 3 bar
627 $ hg tag -r 3 bar
628 abort: tag 'bar' already exists (use -f to force)
628 abort: tag 'bar' already exists (use -f to force)
629 [10]
629 [10]
630 $ hg tags
630 $ hg tags
631 tip 6:735c3ca72986
631 tip 6:735c3ca72986
632 bar 0:bbd179dfa0a7
632 bar 0:bbd179dfa0a7
633
633
634 Strip 1: expose an old head:
634 Strip 1: expose an old head:
635
635
636 $ hg --config extensions.mq= strip 5
636 $ hg --config extensions.mq= strip 5
637 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
637 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
638 $ hg tags # partly stale cache
638 $ hg tags # partly stale cache
639 tip 5:735c3ca72986
639 tip 5:735c3ca72986
640 bar 1:78391a272241
640 bar 1:78391a272241
641 $ hg tags # up-to-date cache
641 $ hg tags # up-to-date cache
642 tip 5:735c3ca72986
642 tip 5:735c3ca72986
643 bar 1:78391a272241
643 bar 1:78391a272241
644
644
645 Strip 2: destroy whole branch, no old head exposed
645 Strip 2: destroy whole branch, no old head exposed
646
646
647 $ hg --config extensions.mq= strip 4
647 $ hg --config extensions.mq= strip 4
648 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
648 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
649 $ hg tags # partly stale
649 $ hg tags # partly stale
650 tip 4:735c3ca72986
650 tip 4:735c3ca72986
651 bar 0:bbd179dfa0a7
651 bar 0:bbd179dfa0a7
652 $ rm -f .hg/cache/tags2-visible
652 $ rm -f .hg/cache/tags2-visible
653 $ hg tags # cold cache
653 $ hg tags # cold cache
654 tip 4:735c3ca72986
654 tip 4:735c3ca72986
655 bar 0:bbd179dfa0a7
655 bar 0:bbd179dfa0a7
656
656
657 Test tag rank with 3 heads:
657 Test tag rank with 3 heads:
658
658
659 $ cd ..
659 $ cd ..
660 $ hg init t4
660 $ hg init t4
661 $ cd t4
661 $ cd t4
662 $ echo foo > foo
662 $ echo foo > foo
663 $ hg add
663 $ hg add
664 adding foo
664 adding foo
665 $ hg ci -m 'add foo' # rev 0
665 $ hg ci -m 'add foo' # rev 0
666 $ hg tag bar # rev 1 bar -> 0
666 $ hg tag bar # rev 1 bar -> 0
667 $ hg tag -f bar # rev 2 bar -> 1
667 $ hg tag -f bar # rev 2 bar -> 1
668 $ hg up -qC 0
668 $ hg up -qC 0
669 $ hg tag -fr 2 bar # rev 3 bar -> 2
669 $ hg tag -fr 2 bar # rev 3 bar -> 2
670 $ hg tags
670 $ hg tags
671 tip 3:197c21bbbf2c
671 tip 3:197c21bbbf2c
672 bar 2:6fa450212aeb
672 bar 2:6fa450212aeb
673 $ hg up -qC 0
673 $ hg up -qC 0
674 $ hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
674 $ hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
675
675
676 Bar should still point to rev 2:
676 Bar should still point to rev 2:
677
677
678 $ hg tags
678 $ hg tags
679 tip 4:3b4b14ed0202
679 tip 4:3b4b14ed0202
680 bar 2:6fa450212aeb
680 bar 2:6fa450212aeb
681
681
682 Test that removing global/local tags does not get confused when trying
682 Test that removing global/local tags does not get confused when trying
683 to remove a tag of type X which actually only exists as a type Y:
683 to remove a tag of type X which actually only exists as a type Y:
684
684
685 $ cd ..
685 $ cd ..
686 $ hg init t5
686 $ hg init t5
687 $ cd t5
687 $ cd t5
688 $ echo foo > foo
688 $ echo foo > foo
689 $ hg add
689 $ hg add
690 adding foo
690 adding foo
691 $ hg ci -m 'add foo' # rev 0
691 $ hg ci -m 'add foo' # rev 0
692
692
693 $ hg tag -r 0 -l localtag
693 $ hg tag -r 0 -l localtag
694 $ hg tag --remove localtag
694 $ hg tag --remove localtag
695 abort: tag 'localtag' is not a global tag
695 abort: tag 'localtag' is not a global tag
696 [10]
696 [10]
697 $
697 $
698 $ hg tag -r 0 globaltag
698 $ hg tag -r 0 globaltag
699 $ hg tag --remove -l globaltag
699 $ hg tag --remove -l globaltag
700 abort: tag 'globaltag' is not a local tag
700 abort: tag 'globaltag' is not a local tag
701 [10]
701 [10]
702 $ hg tags -v
702 $ hg tags -v
703 tip 1:a0b6fe111088
703 tip 1:a0b6fe111088
704 localtag 0:bbd179dfa0a7 local
704 localtag 0:bbd179dfa0a7 local
705 globaltag 0:bbd179dfa0a7
705 globaltag 0:bbd179dfa0a7
706
706
707 Templated output:
707 Templated output:
708
708
709 (immediate values)
709 (immediate values)
710
710
711 $ hg tags -T '{pad(tag, 9)} {rev}:{node} ({type})\n'
711 $ hg tags -T '{pad(tag, 9)} {rev}:{node} ({type})\n'
712 tip 1:a0b6fe111088c8c29567d3876cc466aa02927cae ()
712 tip 1:a0b6fe111088c8c29567d3876cc466aa02927cae ()
713 localtag 0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa (local)
713 localtag 0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa (local)
714 globaltag 0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa ()
714 globaltag 0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa ()
715
715
716 (ctx/revcache dependent)
716 (ctx/revcache dependent)
717
717
718 $ hg tags -T '{pad(tag, 9)} {rev} {file_adds}\n'
718 $ hg tags -T '{pad(tag, 9)} {rev} {file_adds}\n'
719 tip 1 .hgtags
719 tip 1 .hgtags
720 localtag 0 foo
720 localtag 0 foo
721 globaltag 0 foo
721 globaltag 0 foo
722
722
723 $ hg tags -T '{pad(tag, 9)} {rev}:{node|shortest}\n'
723 $ hg tags -T '{pad(tag, 9)} {rev}:{node|shortest}\n'
724 tip 1:a0b6
724 tip 1:a0b6
725 localtag 0:bbd1
725 localtag 0:bbd1
726 globaltag 0:bbd1
726 globaltag 0:bbd1
727
727
728 Test for issue3911
728 Test for issue3911
729
729
730 $ hg tag -r 0 -l localtag2
730 $ hg tag -r 0 -l localtag2
731 $ hg tag -l --remove localtag2
731 $ hg tag -l --remove localtag2
732 $ hg tags -v
732 $ hg tags -v
733 tip 1:a0b6fe111088
733 tip 1:a0b6fe111088
734 localtag 0:bbd179dfa0a7 local
734 localtag 0:bbd179dfa0a7 local
735 globaltag 0:bbd179dfa0a7
735 globaltag 0:bbd179dfa0a7
736
736
737 $ hg tag -r 1 -f localtag
737 $ hg tag -r 1 -f localtag
738 $ hg tags -v
738 $ hg tags -v
739 tip 2:5c70a037bb37
739 tip 2:5c70a037bb37
740 localtag 1:a0b6fe111088
740 localtag 1:a0b6fe111088
741 globaltag 0:bbd179dfa0a7
741 globaltag 0:bbd179dfa0a7
742
742
743 $ hg tags -v
743 $ hg tags -v
744 tip 2:5c70a037bb37
744 tip 2:5c70a037bb37
745 localtag 1:a0b6fe111088
745 localtag 1:a0b6fe111088
746 globaltag 0:bbd179dfa0a7
746 globaltag 0:bbd179dfa0a7
747
747
748 $ hg tag -r 1 localtag2
748 $ hg tag -r 1 localtag2
749 $ hg tags -v
749 $ hg tags -v
750 tip 3:bbfb8cd42be2
750 tip 3:bbfb8cd42be2
751 localtag2 1:a0b6fe111088
751 localtag2 1:a0b6fe111088
752 localtag 1:a0b6fe111088
752 localtag 1:a0b6fe111088
753 globaltag 0:bbd179dfa0a7
753 globaltag 0:bbd179dfa0a7
754
754
755 $ hg tags -v
755 $ hg tags -v
756 tip 3:bbfb8cd42be2
756 tip 3:bbfb8cd42be2
757 localtag2 1:a0b6fe111088
757 localtag2 1:a0b6fe111088
758 localtag 1:a0b6fe111088
758 localtag 1:a0b6fe111088
759 globaltag 0:bbd179dfa0a7
759 globaltag 0:bbd179dfa0a7
760
760
761 $ cd ..
761 $ cd ..
762
762
763 Create a repository with tags data to test .hgtags fnodes transfer
763 Create a repository with tags data to test .hgtags fnodes transfer
764
764
765 $ hg init tagsserver
765 $ hg init tagsserver
766 $ cd tagsserver
766 $ cd tagsserver
767 $ touch foo
767 $ touch foo
768 $ hg -q commit -A -m initial
768 $ hg -q commit -A -m initial
769 $ hg tag -m 'tag 0.1' 0.1
769 $ hg tag -m 'tag 0.1' 0.1
770 $ echo second > foo
770 $ echo second > foo
771 $ hg commit -m second
771 $ hg commit -m second
772 $ hg tag -m 'tag 0.2' 0.2
772 $ hg tag -m 'tag 0.2' 0.2
773 $ hg tags
773 $ hg tags
774 tip 3:40f0358cb314
774 tip 3:40f0358cb314
775 0.2 2:f63cc8fe54e4
775 0.2 2:f63cc8fe54e4
776 0.1 0:96ee1d7354c4
776 0.1 0:96ee1d7354c4
777 $ cd ..
777 $ cd ..
778
778
779 Cloning should pull down hgtags fnodes mappings and write the cache file
779 Cloning should pull down hgtags fnodes mappings and write the cache file
780
780
781 $ hg clone --pull tagsserver tagsclient
781 $ hg clone --pull tagsserver tagsclient
782 requesting all changes
782 requesting all changes
783 adding changesets
783 adding changesets
784 adding manifests
784 adding manifests
785 adding file changes
785 adding file changes
786 added 4 changesets with 4 changes to 2 files
786 added 4 changesets with 4 changes to 2 files
787 new changesets 96ee1d7354c4:40f0358cb314
787 new changesets 96ee1d7354c4:40f0358cb314
788 updating to branch default
788 updating to branch default
789 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
789 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
790
790
791 Missing tags2* files means the cache wasn't written through the normal mechanism.
791 Missing tags2* files means the cache wasn't written through the normal mechanism.
792
792
793 $ ls tagsclient/.hg/cache
793 $ ls tagsclient/.hg/cache
794 branch2-base
794 branch2-base
795 branch2-immutable
795 branch2-immutable
796 branch2-served
796 branch2-served
797 branch2-served.hidden
797 branch2-served.hidden
798 branch2-visible
798 branch2-visible
799 branch2-visible-hidden
799 branch2-visible-hidden
800 hgtagsfnodes1
800 hgtagsfnodes1
801 rbc-names-v1
801 rbc-names-v1
802 rbc-revs-v1
802 rbc-revs-v1
803 tags2
803 tags2
804 tags2-served
804 tags2-served
805
805
806 Cache should contain the head only, even though other nodes have tags data
806 Cache should contain the head only, even though other nodes have tags data
807
807
808 $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
808 $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
809 tagsclient/.hg/cache/hgtagsfnodes1: size=96
809 tagsclient/.hg/cache/hgtagsfnodes1: size=96
810 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
810 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
811 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
811 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
812 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
812 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
813 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
813 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
814 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
814 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
815 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
815 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
816
816
817 Running hg tags should produce tags2* file and not change cache
817 Running hg tags should produce tags2* file and not change cache
818
818
819 $ hg -R tagsclient tags
819 $ hg -R tagsclient tags
820 tip 3:40f0358cb314
820 tip 3:40f0358cb314
821 0.2 2:f63cc8fe54e4
821 0.2 2:f63cc8fe54e4
822 0.1 0:96ee1d7354c4
822 0.1 0:96ee1d7354c4
823
823
824 $ ls tagsclient/.hg/cache
824 $ ls tagsclient/.hg/cache
825 branch2-base
825 branch2-base
826 branch2-immutable
826 branch2-immutable
827 branch2-served
827 branch2-served
828 branch2-served.hidden
828 branch2-served.hidden
829 branch2-visible
829 branch2-visible
830 branch2-visible-hidden
830 branch2-visible-hidden
831 hgtagsfnodes1
831 hgtagsfnodes1
832 rbc-names-v1
832 rbc-names-v1
833 rbc-revs-v1
833 rbc-revs-v1
834 tags2
834 tags2
835 tags2-served
835 tags2-served
836 tags2-visible
836 tags2-visible
837
837
838 $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
838 $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
839 tagsclient/.hg/cache/hgtagsfnodes1: size=96
839 tagsclient/.hg/cache/hgtagsfnodes1: size=96
840 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
840 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
841 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
841 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
842 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
842 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
843 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
843 0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
844 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
844 0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
845 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
845 0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
846
846
847 Check that the bundle includes cache data
847 Check that the bundle includes cache data
848
848
849 $ hg -R tagsclient bundle --all ./test-cache-in-bundle-all-rev.hg
849 $ hg -R tagsclient bundle --all ./test-cache-in-bundle-all-rev.hg
850 4 changesets found
850 4 changesets found
851 $ hg debugbundle ./test-cache-in-bundle-all-rev.hg
851 $ hg debugbundle ./test-cache-in-bundle-all-rev.hg
852 Stream params: {Compression: BZ}
852 Stream params: {Compression: BZ}
853 changegroup -- {nbchanges: 4, version: 02} (mandatory: True)
853 changegroup -- {nbchanges: 4, version: 02} (mandatory: True)
854 96ee1d7354c4ad7372047672c36a1f561e3a6a4c
854 96ee1d7354c4ad7372047672c36a1f561e3a6a4c
855 c4dab0c2fd337eb9191f80c3024830a4889a8f34
855 c4dab0c2fd337eb9191f80c3024830a4889a8f34
856 f63cc8fe54e4d326f8d692805d70e092f851ddb1
856 f63cc8fe54e4d326f8d692805d70e092f851ddb1
857 40f0358cb314c824a5929ee527308d90e023bc10
857 40f0358cb314c824a5929ee527308d90e023bc10
858 hgtagsfnodes -- {} (mandatory: True)
858 hgtagsfnodes -- {} (mandatory: True)
859 cache:rev-branch-cache -- {} (mandatory: False)
859 cache:rev-branch-cache -- {} (mandatory: False)
860
860
861 Check that local clone includes cache data
861 Check that local clone includes cache data
862
862
863 $ hg clone tagsclient tags-local-clone
863 $ hg clone tagsclient tags-local-clone
864 updating to branch default
864 updating to branch default
865 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
865 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
866 $ (cd tags-local-clone/.hg/cache/; ls -1 tag*)
866 $ (cd tags-local-clone/.hg/cache/; ls -1 tag*)
867 tags2
867 tags2
868 tags2-served
868 tags2-served
869 tags2-visible
869 tags2-visible
870
870
871 Avoid writing logs on trying to delete an already deleted tag
871 Avoid writing logs on trying to delete an already deleted tag
872 $ hg init issue5752
872 $ hg init issue5752
873 $ cd issue5752
873 $ cd issue5752
874 $ echo > a
874 $ echo > a
875 $ hg commit -Am 'add a'
875 $ hg commit -Am 'add a'
876 adding a
876 adding a
877 $ hg tag a
877 $ hg tag a
878 $ hg tags
878 $ hg tags
879 tip 1:bd7ee4f3939b
879 tip 1:bd7ee4f3939b
880 a 0:a8a82d372bb3
880 a 0:a8a82d372bb3
881 $ hg log
881 $ hg log
882 changeset: 1:bd7ee4f3939b
882 changeset: 1:bd7ee4f3939b
883 tag: tip
883 tag: tip
884 user: test
884 user: test
885 date: Thu Jan 01 00:00:00 1970 +0000
885 date: Thu Jan 01 00:00:00 1970 +0000
886 summary: Added tag a for changeset a8a82d372bb3
886 summary: Added tag a for changeset a8a82d372bb3
887
887
888 changeset: 0:a8a82d372bb3
888 changeset: 0:a8a82d372bb3
889 tag: a
889 tag: a
890 user: test
890 user: test
891 date: Thu Jan 01 00:00:00 1970 +0000
891 date: Thu Jan 01 00:00:00 1970 +0000
892 summary: add a
892 summary: add a
893
893
894 $ hg tag --remove a
894 $ hg tag --remove a
895 $ hg log
895 $ hg log
896 changeset: 2:e7feacc7ec9e
896 changeset: 2:e7feacc7ec9e
897 tag: tip
897 tag: tip
898 user: test
898 user: test
899 date: Thu Jan 01 00:00:00 1970 +0000
899 date: Thu Jan 01 00:00:00 1970 +0000
900 summary: Removed tag a
900 summary: Removed tag a
901
901
902 changeset: 1:bd7ee4f3939b
902 changeset: 1:bd7ee4f3939b
903 user: test
903 user: test
904 date: Thu Jan 01 00:00:00 1970 +0000
904 date: Thu Jan 01 00:00:00 1970 +0000
905 summary: Added tag a for changeset a8a82d372bb3
905 summary: Added tag a for changeset a8a82d372bb3
906
906
907 changeset: 0:a8a82d372bb3
907 changeset: 0:a8a82d372bb3
908 user: test
908 user: test
909 date: Thu Jan 01 00:00:00 1970 +0000
909 date: Thu Jan 01 00:00:00 1970 +0000
910 summary: add a
910 summary: add a
911
911
912 $ hg tag --remove a
912 $ hg tag --remove a
913 abort: tag 'a' is already removed
913 abort: tag 'a' is already removed
914 [10]
914 [10]
915 $ hg log
915 $ hg log
916 changeset: 2:e7feacc7ec9e
916 changeset: 2:e7feacc7ec9e
917 tag: tip
917 tag: tip
918 user: test
918 user: test
919 date: Thu Jan 01 00:00:00 1970 +0000
919 date: Thu Jan 01 00:00:00 1970 +0000
920 summary: Removed tag a
920 summary: Removed tag a
921
921
922 changeset: 1:bd7ee4f3939b
922 changeset: 1:bd7ee4f3939b
923 user: test
923 user: test
924 date: Thu Jan 01 00:00:00 1970 +0000
924 date: Thu Jan 01 00:00:00 1970 +0000
925 summary: Added tag a for changeset a8a82d372bb3
925 summary: Added tag a for changeset a8a82d372bb3
926
926
927 changeset: 0:a8a82d372bb3
927 changeset: 0:a8a82d372bb3
928 user: test
928 user: test
929 date: Thu Jan 01 00:00:00 1970 +0000
929 date: Thu Jan 01 00:00:00 1970 +0000
930 summary: add a
930 summary: add a
931
931
932 $ cat .hgtags
932 $ cat .hgtags
933 a8a82d372bb35b42ff736e74f07c23bcd99c371f a
933 a8a82d372bb35b42ff736e74f07c23bcd99c371f a
934 a8a82d372bb35b42ff736e74f07c23bcd99c371f a
934 a8a82d372bb35b42ff736e74f07c23bcd99c371f a
935 0000000000000000000000000000000000000000 a
935 0000000000000000000000000000000000000000 a
936
937 $ cd ..
938
939 .hgtags fnode should be properly resolved at merge revision (issue6673)
940
941 $ hg init issue6673
942 $ cd issue6673
943
944 $ touch a
945 $ hg ci -qAm a
946 $ hg branch -q stable
947 $ hg ci -m branch
948
949 $ hg up -q default
950 $ hg merge -q stable
951 $ hg ci -m merge
952
953 add tag to stable branch:
954
955 $ hg up -q stable
956 $ echo a >> a
957 $ hg ci -m a
958 $ hg tag whatever
959 $ hg log -GT'{rev} {tags}\n'
960 @ 4 tip
961 |
962 o 3 whatever
963 |
964 | o 2
965 |/|
966 o | 1
967 |/
968 o 0
969
970
971 merge tagged stable into default:
972
973 $ hg up -q default
974 $ hg merge -q stable
975 $ hg ci -m merge
976 $ hg log -GT'{rev} {tags}\n'
977 @ 5 tip
978 |\
979 | o 4
980 | |
981 | o 3 whatever
982 | |
983 o | 2
984 |\|
985 | o 1
986 |/
987 o 0
988
989
990 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now