##// END OF EJS Templates
tags: do not abort if failed to write lock file to save cache...
Yuya Nishihara -
r24806:61aea11f stable
parent child Browse files
Show More
@@ -1,534 +1,534
1 # tags.py - read tag info from local repository
1 # tags.py - read tag info from local repository
2 #
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009 Matt Mackall <mpm@selenic.com>
4 # Copyright 2009 Greg Ward <greg@gerg.ca>
4 # Copyright 2009 Greg Ward <greg@gerg.ca>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 # Currently this module only deals with reading and caching tags.
9 # Currently this module only deals with reading and caching tags.
10 # Eventually, it could take care of updating (adding/removing/moving)
10 # Eventually, it could take care of updating (adding/removing/moving)
11 # tags too.
11 # tags too.
12
12
13 from node import nullid, bin, hex, short
13 from node import nullid, bin, hex, short
14 from i18n import _
14 from i18n import _
15 import util
15 import util
16 import encoding
16 import encoding
17 import error
17 import error
18 from array import array
18 from array import array
19 import errno
19 import errno
20 import time
20 import time
21
21
22 # Tags computation can be expensive and caches exist to make it fast in
22 # Tags computation can be expensive and caches exist to make it fast in
23 # the common case.
23 # the common case.
24 #
24 #
25 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
25 # The "hgtagsfnodes1" cache file caches the .hgtags filenode values for
26 # each revision in the repository. The file is effectively an array of
26 # each revision in the repository. The file is effectively an array of
27 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
27 # fixed length records. Read the docs for "hgtagsfnodescache" for technical
28 # details.
28 # details.
29 #
29 #
30 # The .hgtags filenode cache grows in proportion to the length of the
30 # The .hgtags filenode cache grows in proportion to the length of the
31 # changelog. The file is truncated when the # changelog is stripped.
31 # changelog. The file is truncated when the # changelog is stripped.
32 #
32 #
33 # The purpose of the filenode cache is to avoid the most expensive part
33 # The purpose of the filenode cache is to avoid the most expensive part
34 # of finding global tags, which is looking up the .hgtags filenode in the
34 # of finding global tags, which is looking up the .hgtags filenode in the
35 # manifest for each head. This can take dozens or over 100ms for
35 # manifest for each head. This can take dozens or over 100ms for
36 # repositories with very large manifests. Multiplied by dozens or even
36 # repositories with very large manifests. Multiplied by dozens or even
37 # hundreds of heads and there is a significant performance concern.
37 # hundreds of heads and there is a significant performance concern.
38 #
38 #
39 # There also exist a separate cache file for each repository filter.
39 # There also exist a separate cache file for each repository filter.
40 # These "tags-*" files store information about the history of tags.
40 # These "tags-*" files store information about the history of tags.
41 #
41 #
42 # The tags cache files consists of a cache validation line followed by
42 # The tags cache files consists of a cache validation line followed by
43 # a history of tags.
43 # a history of tags.
44 #
44 #
45 # The cache validation line has the format:
45 # The cache validation line has the format:
46 #
46 #
47 # <tiprev> <tipnode> [<filteredhash>]
47 # <tiprev> <tipnode> [<filteredhash>]
48 #
48 #
49 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
49 # <tiprev> is an integer revision and <tipnode> is a 40 character hex
50 # node for that changeset. These redundantly identify the repository
50 # node for that changeset. These redundantly identify the repository
51 # tip from the time the cache was written. In addition, <filteredhash>,
51 # tip from the time the cache was written. In addition, <filteredhash>,
52 # if present, is a 40 character hex hash of the contents of the filtered
52 # if present, is a 40 character hex hash of the contents of the filtered
53 # revisions for this filter. If the set of filtered revs changes, the
53 # revisions for this filter. If the set of filtered revs changes, the
54 # hash will change and invalidate the cache.
54 # hash will change and invalidate the cache.
55 #
55 #
56 # The history part of the tags cache consists of lines of the form:
56 # The history part of the tags cache consists of lines of the form:
57 #
57 #
58 # <node> <tag>
58 # <node> <tag>
59 #
59 #
60 # (This format is identical to that of .hgtags files.)
60 # (This format is identical to that of .hgtags files.)
61 #
61 #
62 # <tag> is the tag name and <node> is the 40 character hex changeset
62 # <tag> is the tag name and <node> is the 40 character hex changeset
63 # the tag is associated with.
63 # the tag is associated with.
64 #
64 #
65 # Tags are written sorted by tag name.
65 # Tags are written sorted by tag name.
66 #
66 #
67 # Tags associated with multiple changesets have an entry for each changeset.
67 # Tags associated with multiple changesets have an entry for each changeset.
68 # The most recent changeset (in terms of revlog ordering for the head
68 # The most recent changeset (in terms of revlog ordering for the head
69 # setting it) for each tag is last.
69 # setting it) for each tag is last.
70
70
71 def findglobaltags(ui, repo, alltags, tagtypes):
71 def findglobaltags(ui, repo, alltags, tagtypes):
72 '''Find global tags in a repo.
72 '''Find global tags in a repo.
73
73
74 "alltags" maps tag name to (node, hist) 2-tuples.
74 "alltags" maps tag name to (node, hist) 2-tuples.
75
75
76 "tagtypes" maps tag name to tag type. Global tags always have the
76 "tagtypes" maps tag name to tag type. Global tags always have the
77 "global" tag type.
77 "global" tag type.
78
78
79 The "alltags" and "tagtypes" dicts are updated in place. Empty dicts
79 The "alltags" and "tagtypes" dicts are updated in place. Empty dicts
80 should be passed in.
80 should be passed in.
81
81
82 The tags cache is read and updated as a side-effect of calling.
82 The tags cache is read and updated as a side-effect of calling.
83 '''
83 '''
84 # This is so we can be lazy and assume alltags contains only global
84 # This is so we can be lazy and assume alltags contains only global
85 # tags when we pass it to _writetagcache().
85 # tags when we pass it to _writetagcache().
86 assert len(alltags) == len(tagtypes) == 0, \
86 assert len(alltags) == len(tagtypes) == 0, \
87 "findglobaltags() should be called first"
87 "findglobaltags() should be called first"
88
88
89 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
89 (heads, tagfnode, valid, cachetags, shouldwrite) = _readtagcache(ui, repo)
90 if cachetags is not None:
90 if cachetags is not None:
91 assert not shouldwrite
91 assert not shouldwrite
92 # XXX is this really 100% correct? are there oddball special
92 # XXX is this really 100% correct? are there oddball special
93 # cases where a global tag should outrank a local tag but won't,
93 # cases where a global tag should outrank a local tag but won't,
94 # because cachetags does not contain rank info?
94 # because cachetags does not contain rank info?
95 _updatetags(cachetags, 'global', alltags, tagtypes)
95 _updatetags(cachetags, 'global', alltags, tagtypes)
96 return
96 return
97
97
98 seen = set() # set of fnode
98 seen = set() # set of fnode
99 fctx = None
99 fctx = None
100 for head in reversed(heads): # oldest to newest
100 for head in reversed(heads): # oldest to newest
101 assert head in repo.changelog.nodemap, \
101 assert head in repo.changelog.nodemap, \
102 "tag cache returned bogus head %s" % short(head)
102 "tag cache returned bogus head %s" % short(head)
103
103
104 fnode = tagfnode.get(head)
104 fnode = tagfnode.get(head)
105 if fnode and fnode not in seen:
105 if fnode and fnode not in seen:
106 seen.add(fnode)
106 seen.add(fnode)
107 if not fctx:
107 if not fctx:
108 fctx = repo.filectx('.hgtags', fileid=fnode)
108 fctx = repo.filectx('.hgtags', fileid=fnode)
109 else:
109 else:
110 fctx = fctx.filectx(fnode)
110 fctx = fctx.filectx(fnode)
111
111
112 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
112 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
113 _updatetags(filetags, 'global', alltags, tagtypes)
113 _updatetags(filetags, 'global', alltags, tagtypes)
114
114
115 # and update the cache (if necessary)
115 # and update the cache (if necessary)
116 if shouldwrite:
116 if shouldwrite:
117 _writetagcache(ui, repo, valid, alltags)
117 _writetagcache(ui, repo, valid, alltags)
118
118
119 def readlocaltags(ui, repo, alltags, tagtypes):
119 def readlocaltags(ui, repo, alltags, tagtypes):
120 '''Read local tags in repo. Update alltags and tagtypes.'''
120 '''Read local tags in repo. Update alltags and tagtypes.'''
121 try:
121 try:
122 data = repo.vfs.read("localtags")
122 data = repo.vfs.read("localtags")
123 except IOError, inst:
123 except IOError, inst:
124 if inst.errno != errno.ENOENT:
124 if inst.errno != errno.ENOENT:
125 raise
125 raise
126 return
126 return
127
127
128 # localtags is in the local encoding; re-encode to UTF-8 on
128 # localtags is in the local encoding; re-encode to UTF-8 on
129 # input for consistency with the rest of this module.
129 # input for consistency with the rest of this module.
130 filetags = _readtags(
130 filetags = _readtags(
131 ui, repo, data.splitlines(), "localtags",
131 ui, repo, data.splitlines(), "localtags",
132 recode=encoding.fromlocal)
132 recode=encoding.fromlocal)
133
133
134 # remove tags pointing to invalid nodes
134 # remove tags pointing to invalid nodes
135 cl = repo.changelog
135 cl = repo.changelog
136 for t in filetags.keys():
136 for t in filetags.keys():
137 try:
137 try:
138 cl.rev(filetags[t][0])
138 cl.rev(filetags[t][0])
139 except (LookupError, ValueError):
139 except (LookupError, ValueError):
140 del filetags[t]
140 del filetags[t]
141
141
142 _updatetags(filetags, "local", alltags, tagtypes)
142 _updatetags(filetags, "local", alltags, tagtypes)
143
143
144 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
144 def _readtaghist(ui, repo, lines, fn, recode=None, calcnodelines=False):
145 '''Read tag definitions from a file (or any source of lines).
145 '''Read tag definitions from a file (or any source of lines).
146
146
147 This function returns two sortdicts with similar information:
147 This function returns two sortdicts with similar information:
148
148
149 - the first dict, bintaghist, contains the tag information as expected by
149 - the first dict, bintaghist, contains the tag information as expected by
150 the _readtags function, i.e. a mapping from tag name to (node, hist):
150 the _readtags function, i.e. a mapping from tag name to (node, hist):
151 - node is the node id from the last line read for that name,
151 - node is the node id from the last line read for that name,
152 - hist is the list of node ids previously associated with it (in file
152 - hist is the list of node ids previously associated with it (in file
153 order). All node ids are binary, not hex.
153 order). All node ids are binary, not hex.
154
154
155 - the second dict, hextaglines, is a mapping from tag name to a list of
155 - the second dict, hextaglines, is a mapping from tag name to a list of
156 [hexnode, line number] pairs, ordered from the oldest to the newest node.
156 [hexnode, line number] pairs, ordered from the oldest to the newest node.
157
157
158 When calcnodelines is False the hextaglines dict is not calculated (an
158 When calcnodelines is False the hextaglines dict is not calculated (an
159 empty dict is returned). This is done to improve this function's
159 empty dict is returned). This is done to improve this function's
160 performance in cases where the line numbers are not needed.
160 performance in cases where the line numbers are not needed.
161 '''
161 '''
162
162
163 bintaghist = util.sortdict()
163 bintaghist = util.sortdict()
164 hextaglines = util.sortdict()
164 hextaglines = util.sortdict()
165 count = 0
165 count = 0
166
166
167 def warn(msg):
167 def warn(msg):
168 ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
168 ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
169
169
170 for nline, line in enumerate(lines):
170 for nline, line in enumerate(lines):
171 count += 1
171 count += 1
172 if not line:
172 if not line:
173 continue
173 continue
174 try:
174 try:
175 (nodehex, name) = line.split(" ", 1)
175 (nodehex, name) = line.split(" ", 1)
176 except ValueError:
176 except ValueError:
177 warn(_("cannot parse entry"))
177 warn(_("cannot parse entry"))
178 continue
178 continue
179 name = name.strip()
179 name = name.strip()
180 if recode:
180 if recode:
181 name = recode(name)
181 name = recode(name)
182 try:
182 try:
183 nodebin = bin(nodehex)
183 nodebin = bin(nodehex)
184 except TypeError:
184 except TypeError:
185 warn(_("node '%s' is not well formed") % nodehex)
185 warn(_("node '%s' is not well formed") % nodehex)
186 continue
186 continue
187
187
188 # update filetags
188 # update filetags
189 if calcnodelines:
189 if calcnodelines:
190 # map tag name to a list of line numbers
190 # map tag name to a list of line numbers
191 if name not in hextaglines:
191 if name not in hextaglines:
192 hextaglines[name] = []
192 hextaglines[name] = []
193 hextaglines[name].append([nodehex, nline])
193 hextaglines[name].append([nodehex, nline])
194 continue
194 continue
195 # map tag name to (node, hist)
195 # map tag name to (node, hist)
196 if name not in bintaghist:
196 if name not in bintaghist:
197 bintaghist[name] = []
197 bintaghist[name] = []
198 bintaghist[name].append(nodebin)
198 bintaghist[name].append(nodebin)
199 return bintaghist, hextaglines
199 return bintaghist, hextaglines
200
200
201 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
201 def _readtags(ui, repo, lines, fn, recode=None, calcnodelines=False):
202 '''Read tag definitions from a file (or any source of lines).
202 '''Read tag definitions from a file (or any source of lines).
203
203
204 Returns a mapping from tag name to (node, hist).
204 Returns a mapping from tag name to (node, hist).
205
205
206 "node" is the node id from the last line read for that name. "hist"
206 "node" is the node id from the last line read for that name. "hist"
207 is the list of node ids previously associated with it (in file order).
207 is the list of node ids previously associated with it (in file order).
208 All node ids are binary, not hex.
208 All node ids are binary, not hex.
209 '''
209 '''
210 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode,
210 filetags, nodelines = _readtaghist(ui, repo, lines, fn, recode=recode,
211 calcnodelines=calcnodelines)
211 calcnodelines=calcnodelines)
212 for tag, taghist in filetags.items():
212 for tag, taghist in filetags.items():
213 filetags[tag] = (taghist[-1], taghist[:-1])
213 filetags[tag] = (taghist[-1], taghist[:-1])
214 return filetags
214 return filetags
215
215
216 def _updatetags(filetags, tagtype, alltags, tagtypes):
216 def _updatetags(filetags, tagtype, alltags, tagtypes):
217 '''Incorporate the tag info read from one file into the two
217 '''Incorporate the tag info read from one file into the two
218 dictionaries, alltags and tagtypes, that contain all tag
218 dictionaries, alltags and tagtypes, that contain all tag
219 info (global across all heads plus local).'''
219 info (global across all heads plus local).'''
220
220
221 for name, nodehist in filetags.iteritems():
221 for name, nodehist in filetags.iteritems():
222 if name not in alltags:
222 if name not in alltags:
223 alltags[name] = nodehist
223 alltags[name] = nodehist
224 tagtypes[name] = tagtype
224 tagtypes[name] = tagtype
225 continue
225 continue
226
226
227 # we prefer alltags[name] if:
227 # we prefer alltags[name] if:
228 # it supersedes us OR
228 # it supersedes us OR
229 # mutual supersedes and it has a higher rank
229 # mutual supersedes and it has a higher rank
230 # otherwise we win because we're tip-most
230 # otherwise we win because we're tip-most
231 anode, ahist = nodehist
231 anode, ahist = nodehist
232 bnode, bhist = alltags[name]
232 bnode, bhist = alltags[name]
233 if (bnode != anode and anode in bhist and
233 if (bnode != anode and anode in bhist and
234 (bnode not in ahist or len(bhist) > len(ahist))):
234 (bnode not in ahist or len(bhist) > len(ahist))):
235 anode = bnode
235 anode = bnode
236 else:
236 else:
237 tagtypes[name] = tagtype
237 tagtypes[name] = tagtype
238 ahist.extend([n for n in bhist if n not in ahist])
238 ahist.extend([n for n in bhist if n not in ahist])
239 alltags[name] = anode, ahist
239 alltags[name] = anode, ahist
240
240
241 def _filename(repo):
241 def _filename(repo):
242 """name of a tagcache file for a given repo or repoview"""
242 """name of a tagcache file for a given repo or repoview"""
243 filename = 'cache/tags2'
243 filename = 'cache/tags2'
244 if repo.filtername:
244 if repo.filtername:
245 filename = '%s-%s' % (filename, repo.filtername)
245 filename = '%s-%s' % (filename, repo.filtername)
246 return filename
246 return filename
247
247
248 def _readtagcache(ui, repo):
248 def _readtagcache(ui, repo):
249 '''Read the tag cache.
249 '''Read the tag cache.
250
250
251 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
251 Returns a tuple (heads, fnodes, validinfo, cachetags, shouldwrite).
252
252
253 If the cache is completely up-to-date, "cachetags" is a dict of the
253 If the cache is completely up-to-date, "cachetags" is a dict of the
254 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
254 form returned by _readtags() and "heads", "fnodes", and "validinfo" are
255 None and "shouldwrite" is False.
255 None and "shouldwrite" is False.
256
256
257 If the cache is not up to date, "cachetags" is None. "heads" is a list
257 If the cache is not up to date, "cachetags" is None. "heads" is a list
258 of all heads currently in the repository, ordered from tip to oldest.
258 of all heads currently in the repository, ordered from tip to oldest.
259 "validinfo" is a tuple describing cache validation info. This is used
259 "validinfo" is a tuple describing cache validation info. This is used
260 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
260 when writing the tags cache. "fnodes" is a mapping from head to .hgtags
261 filenode. "shouldwrite" is True.
261 filenode. "shouldwrite" is True.
262
262
263 If the cache is not up to date, the caller is responsible for reading tag
263 If the cache is not up to date, the caller is responsible for reading tag
264 info from each returned head. (See findglobaltags().)
264 info from each returned head. (See findglobaltags().)
265 '''
265 '''
266 import scmutil # avoid cycle
266 import scmutil # avoid cycle
267
267
268 try:
268 try:
269 cachefile = repo.vfs(_filename(repo), 'r')
269 cachefile = repo.vfs(_filename(repo), 'r')
270 # force reading the file for static-http
270 # force reading the file for static-http
271 cachelines = iter(cachefile)
271 cachelines = iter(cachefile)
272 except IOError:
272 except IOError:
273 cachefile = None
273 cachefile = None
274
274
275 cacherev = None
275 cacherev = None
276 cachenode = None
276 cachenode = None
277 cachehash = None
277 cachehash = None
278 if cachefile:
278 if cachefile:
279 try:
279 try:
280 validline = cachelines.next()
280 validline = cachelines.next()
281 validline = validline.split()
281 validline = validline.split()
282 cacherev = int(validline[0])
282 cacherev = int(validline[0])
283 cachenode = bin(validline[1])
283 cachenode = bin(validline[1])
284 if len(validline) > 2:
284 if len(validline) > 2:
285 cachehash = bin(validline[2])
285 cachehash = bin(validline[2])
286 except Exception:
286 except Exception:
287 # corruption of the cache, just recompute it.
287 # corruption of the cache, just recompute it.
288 pass
288 pass
289
289
290 tipnode = repo.changelog.tip()
290 tipnode = repo.changelog.tip()
291 tiprev = len(repo.changelog) - 1
291 tiprev = len(repo.changelog) - 1
292
292
293 # Case 1 (common): tip is the same, so nothing has changed.
293 # Case 1 (common): tip is the same, so nothing has changed.
294 # (Unchanged tip trivially means no changesets have been added.
294 # (Unchanged tip trivially means no changesets have been added.
295 # But, thanks to localrepository.destroyed(), it also means none
295 # But, thanks to localrepository.destroyed(), it also means none
296 # have been destroyed by strip or rollback.)
296 # have been destroyed by strip or rollback.)
297 if (cacherev == tiprev
297 if (cacherev == tiprev
298 and cachenode == tipnode
298 and cachenode == tipnode
299 and cachehash == scmutil.filteredhash(repo, tiprev)):
299 and cachehash == scmutil.filteredhash(repo, tiprev)):
300 tags = _readtags(ui, repo, cachelines, cachefile.name)
300 tags = _readtags(ui, repo, cachelines, cachefile.name)
301 cachefile.close()
301 cachefile.close()
302 return (None, None, None, tags, False)
302 return (None, None, None, tags, False)
303 if cachefile:
303 if cachefile:
304 cachefile.close() # ignore rest of file
304 cachefile.close() # ignore rest of file
305
305
306 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
306 valid = (tiprev, tipnode, scmutil.filteredhash(repo, tiprev))
307
307
308 repoheads = repo.heads()
308 repoheads = repo.heads()
309 # Case 2 (uncommon): empty repo; get out quickly and don't bother
309 # Case 2 (uncommon): empty repo; get out quickly and don't bother
310 # writing an empty cache.
310 # writing an empty cache.
311 if repoheads == [nullid]:
311 if repoheads == [nullid]:
312 return ([], {}, valid, {}, False)
312 return ([], {}, valid, {}, False)
313
313
314 # Case 3 (uncommon): cache file missing or empty.
314 # Case 3 (uncommon): cache file missing or empty.
315
315
316 # Case 4 (uncommon): tip rev decreased. This should only happen
316 # Case 4 (uncommon): tip rev decreased. This should only happen
317 # when we're called from localrepository.destroyed(). Refresh the
317 # when we're called from localrepository.destroyed(). Refresh the
318 # cache so future invocations will not see disappeared heads in the
318 # cache so future invocations will not see disappeared heads in the
319 # cache.
319 # cache.
320
320
321 # Case 5 (common): tip has changed, so we've added/replaced heads.
321 # Case 5 (common): tip has changed, so we've added/replaced heads.
322
322
323 # As it happens, the code to handle cases 3, 4, 5 is the same.
323 # As it happens, the code to handle cases 3, 4, 5 is the same.
324
324
325 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
325 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
326 # exposed".
326 # exposed".
327 if not len(repo.file('.hgtags')):
327 if not len(repo.file('.hgtags')):
328 # No tags have ever been committed, so we can avoid a
328 # No tags have ever been committed, so we can avoid a
329 # potentially expensive search.
329 # potentially expensive search.
330 return ([], {}, valid, None, True)
330 return ([], {}, valid, None, True)
331
331
332 starttime = time.time()
332 starttime = time.time()
333
333
334 # Now we have to lookup the .hgtags filenode for every new head.
334 # Now we have to lookup the .hgtags filenode for every new head.
335 # This is the most expensive part of finding tags, so performance
335 # This is the most expensive part of finding tags, so performance
336 # depends primarily on the size of newheads. Worst case: no cache
336 # depends primarily on the size of newheads. Worst case: no cache
337 # file, so newheads == repoheads.
337 # file, so newheads == repoheads.
338 fnodescache = hgtagsfnodescache(repo.unfiltered())
338 fnodescache = hgtagsfnodescache(repo.unfiltered())
339 cachefnode = {}
339 cachefnode = {}
340 for head in reversed(repoheads):
340 for head in reversed(repoheads):
341 fnode = fnodescache.getfnode(head)
341 fnode = fnodescache.getfnode(head)
342 if fnode != nullid:
342 if fnode != nullid:
343 cachefnode[head] = fnode
343 cachefnode[head] = fnode
344
344
345 fnodescache.write()
345 fnodescache.write()
346
346
347 duration = time.time() - starttime
347 duration = time.time() - starttime
348 ui.log('tagscache',
348 ui.log('tagscache',
349 '%d/%d cache hits/lookups in %0.4f '
349 '%d/%d cache hits/lookups in %0.4f '
350 'seconds\n',
350 'seconds\n',
351 fnodescache.hitcount, fnodescache.lookupcount, duration)
351 fnodescache.hitcount, fnodescache.lookupcount, duration)
352
352
353 # Caller has to iterate over all heads, but can use the filenodes in
353 # Caller has to iterate over all heads, but can use the filenodes in
354 # cachefnode to get to each .hgtags revision quickly.
354 # cachefnode to get to each .hgtags revision quickly.
355 return (repoheads, cachefnode, valid, None, True)
355 return (repoheads, cachefnode, valid, None, True)
356
356
357 def _writetagcache(ui, repo, valid, cachetags):
357 def _writetagcache(ui, repo, valid, cachetags):
358 filename = _filename(repo)
358 filename = _filename(repo)
359 try:
359 try:
360 cachefile = repo.vfs(filename, 'w', atomictemp=True)
360 cachefile = repo.vfs(filename, 'w', atomictemp=True)
361 except (OSError, IOError):
361 except (OSError, IOError):
362 return
362 return
363
363
364 ui.log('tagscache', 'writing .hg/%s with %d tags\n',
364 ui.log('tagscache', 'writing .hg/%s with %d tags\n',
365 filename, len(cachetags))
365 filename, len(cachetags))
366
366
367 if valid[2]:
367 if valid[2]:
368 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
368 cachefile.write('%d %s %s\n' % (valid[0], hex(valid[1]), hex(valid[2])))
369 else:
369 else:
370 cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
370 cachefile.write('%d %s\n' % (valid[0], hex(valid[1])))
371
371
372 # Tag names in the cache are in UTF-8 -- which is the whole reason
372 # Tag names in the cache are in UTF-8 -- which is the whole reason
373 # we keep them in UTF-8 throughout this module. If we converted
373 # we keep them in UTF-8 throughout this module. If we converted
374 # them local encoding on input, we would lose info writing them to
374 # them local encoding on input, we would lose info writing them to
375 # the cache.
375 # the cache.
376 for (name, (node, hist)) in sorted(cachetags.iteritems()):
376 for (name, (node, hist)) in sorted(cachetags.iteritems()):
377 for n in hist:
377 for n in hist:
378 cachefile.write("%s %s\n" % (hex(n), name))
378 cachefile.write("%s %s\n" % (hex(n), name))
379 cachefile.write("%s %s\n" % (hex(node), name))
379 cachefile.write("%s %s\n" % (hex(node), name))
380
380
381 try:
381 try:
382 cachefile.close()
382 cachefile.close()
383 except (OSError, IOError):
383 except (OSError, IOError):
384 pass
384 pass
385
385
386 _fnodescachefile = 'cache/hgtagsfnodes1'
386 _fnodescachefile = 'cache/hgtagsfnodes1'
387 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
387 _fnodesrecsize = 4 + 20 # changeset fragment + filenode
388 _fnodesmissingrec = '\xff' * 24
388 _fnodesmissingrec = '\xff' * 24
389
389
390 class hgtagsfnodescache(object):
390 class hgtagsfnodescache(object):
391 """Persistent cache mapping revisions to .hgtags filenodes.
391 """Persistent cache mapping revisions to .hgtags filenodes.
392
392
393 The cache is an array of records. Each item in the array corresponds to
393 The cache is an array of records. Each item in the array corresponds to
394 a changelog revision. Values in the array contain the first 4 bytes of
394 a changelog revision. Values in the array contain the first 4 bytes of
395 the node hash and the 20 bytes .hgtags filenode for that revision.
395 the node hash and the 20 bytes .hgtags filenode for that revision.
396
396
397 The first 4 bytes are present as a form of verification. Repository
397 The first 4 bytes are present as a form of verification. Repository
398 stripping and rewriting may change the node at a numeric revision in the
398 stripping and rewriting may change the node at a numeric revision in the
399 changelog. The changeset fragment serves as a verifier to detect
399 changelog. The changeset fragment serves as a verifier to detect
400 rewriting. This logic is shared with the rev branch cache (see
400 rewriting. This logic is shared with the rev branch cache (see
401 branchmap.py).
401 branchmap.py).
402
402
403 The instance holds in memory the full cache content but entries are
403 The instance holds in memory the full cache content but entries are
404 only parsed on read.
404 only parsed on read.
405
405
406 Instances behave like lists. ``c[i]`` works where i is a rev or
406 Instances behave like lists. ``c[i]`` works where i is a rev or
407 changeset node. Missing indexes are populated automatically on access.
407 changeset node. Missing indexes are populated automatically on access.
408 """
408 """
409 def __init__(self, repo):
409 def __init__(self, repo):
410 assert repo.filtername is None
410 assert repo.filtername is None
411
411
412 self._repo = repo
412 self._repo = repo
413
413
414 # Only for reporting purposes.
414 # Only for reporting purposes.
415 self.lookupcount = 0
415 self.lookupcount = 0
416 self.hitcount = 0
416 self.hitcount = 0
417
417
418 self._raw = array('c')
418 self._raw = array('c')
419
419
420 data = repo.vfs.tryread(_fnodescachefile)
420 data = repo.vfs.tryread(_fnodescachefile)
421 self._raw.fromstring(data)
421 self._raw.fromstring(data)
422
422
423 # The end state of self._raw is an array that is of the exact length
423 # The end state of self._raw is an array that is of the exact length
424 # required to hold a record for every revision in the repository.
424 # required to hold a record for every revision in the repository.
425 # We truncate or extend the array as necessary. self._dirtyoffset is
425 # We truncate or extend the array as necessary. self._dirtyoffset is
426 # defined to be the start offset at which we need to write the output
426 # defined to be the start offset at which we need to write the output
427 # file. This offset is also adjusted when new entries are calculated
427 # file. This offset is also adjusted when new entries are calculated
428 # for array members.
428 # for array members.
429 cllen = len(repo.changelog)
429 cllen = len(repo.changelog)
430 wantedlen = cllen * _fnodesrecsize
430 wantedlen = cllen * _fnodesrecsize
431 rawlen = len(self._raw)
431 rawlen = len(self._raw)
432
432
433 self._dirtyoffset = None
433 self._dirtyoffset = None
434
434
435 if rawlen < wantedlen:
435 if rawlen < wantedlen:
436 self._dirtyoffset = rawlen
436 self._dirtyoffset = rawlen
437 self._raw.extend('\xff' * (wantedlen - rawlen))
437 self._raw.extend('\xff' * (wantedlen - rawlen))
438 elif rawlen > wantedlen:
438 elif rawlen > wantedlen:
439 # There's no easy way to truncate array instances. This seems
439 # There's no easy way to truncate array instances. This seems
440 # slightly less evil than copying a potentially large array slice.
440 # slightly less evil than copying a potentially large array slice.
441 for i in range(rawlen - wantedlen):
441 for i in range(rawlen - wantedlen):
442 self._raw.pop()
442 self._raw.pop()
443 self._dirtyoffset = len(self._raw)
443 self._dirtyoffset = len(self._raw)
444
444
445 def getfnode(self, node):
445 def getfnode(self, node):
446 """Obtain the filenode of the .hgtags file at a specified revision.
446 """Obtain the filenode of the .hgtags file at a specified revision.
447
447
448 If the value is in the cache, the entry will be validated and returned.
448 If the value is in the cache, the entry will be validated and returned.
449 Otherwise, the filenode will be computed and returned.
449 Otherwise, the filenode will be computed and returned.
450
450
451 If an .hgtags does not exist at the specified revision, nullid is
451 If an .hgtags does not exist at the specified revision, nullid is
452 returned.
452 returned.
453 """
453 """
454 ctx = self._repo[node]
454 ctx = self._repo[node]
455 rev = ctx.rev()
455 rev = ctx.rev()
456
456
457 self.lookupcount += 1
457 self.lookupcount += 1
458
458
459 offset = rev * _fnodesrecsize
459 offset = rev * _fnodesrecsize
460 record = self._raw[offset:offset + _fnodesrecsize].tostring()
460 record = self._raw[offset:offset + _fnodesrecsize].tostring()
461 properprefix = node[0:4]
461 properprefix = node[0:4]
462
462
463 # Validate and return existing entry.
463 # Validate and return existing entry.
464 if record != _fnodesmissingrec:
464 if record != _fnodesmissingrec:
465 fileprefix = record[0:4]
465 fileprefix = record[0:4]
466
466
467 if fileprefix == properprefix:
467 if fileprefix == properprefix:
468 self.hitcount += 1
468 self.hitcount += 1
469 return record[4:]
469 return record[4:]
470
470
471 # Fall through.
471 # Fall through.
472
472
473 # If we get here, the entry is either missing or invalid. Populate it.
473 # If we get here, the entry is either missing or invalid. Populate it.
474 try:
474 try:
475 fnode = ctx.filenode('.hgtags')
475 fnode = ctx.filenode('.hgtags')
476 except error.LookupError:
476 except error.LookupError:
477 # No .hgtags file on this revision.
477 # No .hgtags file on this revision.
478 fnode = nullid
478 fnode = nullid
479
479
480 # Slices on array instances only accept other array.
480 # Slices on array instances only accept other array.
481 entry = array('c', properprefix + fnode)
481 entry = array('c', properprefix + fnode)
482 self._raw[offset:offset + _fnodesrecsize] = entry
482 self._raw[offset:offset + _fnodesrecsize] = entry
483 # self._dirtyoffset could be None.
483 # self._dirtyoffset could be None.
484 self._dirtyoffset = min(self._dirtyoffset, offset) or 0
484 self._dirtyoffset = min(self._dirtyoffset, offset) or 0
485
485
486 return fnode
486 return fnode
487
487
488 def write(self):
488 def write(self):
489 """Perform all necessary writes to cache file.
489 """Perform all necessary writes to cache file.
490
490
491 This may no-op if no writes are needed or if a write lock could
491 This may no-op if no writes are needed or if a write lock could
492 not be obtained.
492 not be obtained.
493 """
493 """
494 if self._dirtyoffset is None:
494 if self._dirtyoffset is None:
495 return
495 return
496
496
497 data = self._raw[self._dirtyoffset:]
497 data = self._raw[self._dirtyoffset:]
498 if not data:
498 if not data:
499 return
499 return
500
500
501 repo = self._repo
501 repo = self._repo
502
502
503 try:
503 try:
504 lock = repo.wlock(wait=False)
504 lock = repo.wlock(wait=False)
505 except error.LockHeld:
505 except error.LockError:
506 repo.ui.log('tagscache',
506 repo.ui.log('tagscache',
507 'not writing .hg/%s because lock held\n' %
507 'not writing .hg/%s because lock cannot be acquired\n' %
508 (_fnodescachefile))
508 (_fnodescachefile))
509 return
509 return
510
510
511 try:
511 try:
512 try:
512 try:
513 f = repo.vfs.open(_fnodescachefile, 'ab')
513 f = repo.vfs.open(_fnodescachefile, 'ab')
514 try:
514 try:
515 # if the file has been truncated
515 # if the file has been truncated
516 actualoffset = f.tell()
516 actualoffset = f.tell()
517 if actualoffset < self._dirtyoffset:
517 if actualoffset < self._dirtyoffset:
518 self._dirtyoffset = actualoffset
518 self._dirtyoffset = actualoffset
519 data = self._raw[self._dirtyoffset:]
519 data = self._raw[self._dirtyoffset:]
520 f.seek(self._dirtyoffset)
520 f.seek(self._dirtyoffset)
521 f.truncate()
521 f.truncate()
522 repo.ui.log('tagscache',
522 repo.ui.log('tagscache',
523 'writing %d bytes to %s\n' % (
523 'writing %d bytes to %s\n' % (
524 len(data), _fnodescachefile))
524 len(data), _fnodescachefile))
525 f.write(data)
525 f.write(data)
526 self._dirtyoffset = None
526 self._dirtyoffset = None
527 finally:
527 finally:
528 f.close()
528 f.close()
529 except (IOError, OSError), inst:
529 except (IOError, OSError), inst:
530 repo.ui.log('tagscache',
530 repo.ui.log('tagscache',
531 "couldn't write %s: %s\n" % (
531 "couldn't write %s: %s\n" % (
532 _fnodescachefile, inst))
532 _fnodescachefile, inst))
533 finally:
533 finally:
534 lock.release()
534 lock.release()
@@ -1,622 +1,627
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 > EOF
7 > EOF
8
8
9 Helper functions:
9 Helper functions:
10
10
11 $ cacheexists() {
11 $ cacheexists() {
12 > [ -f .hg/cache/tags2-visible ] && echo "tag cache exists" || echo "no tag cache"
12 > [ -f .hg/cache/tags2-visible ] && echo "tag cache exists" || echo "no tag cache"
13 > }
13 > }
14
14
15 $ fnodescacheexists() {
15 $ fnodescacheexists() {
16 > [ -f .hg/cache/hgtagsfnodes1 ] && echo "fnodes cache exists" || echo "no fnodes cache"
16 > [ -f .hg/cache/hgtagsfnodes1 ] && echo "fnodes cache exists" || echo "no fnodes cache"
17 > }
17 > }
18
18
19 $ dumptags() {
19 $ dumptags() {
20 > rev=$1
20 > rev=$1
21 > echo "rev $rev: .hgtags:"
21 > echo "rev $rev: .hgtags:"
22 > hg cat -r$rev .hgtags
22 > hg cat -r$rev .hgtags
23 > }
23 > }
24
24
25 # XXX need to test that the tag cache works when we strip an old head
25 # XXX need to test that the tag cache works when we strip an old head
26 # and add a new one rooted off non-tip: i.e. node and rev of tip are the
26 # and add a new one rooted off non-tip: i.e. node and rev of tip are the
27 # same, but stuff has changed behind tip.
27 # same, but stuff has changed behind tip.
28
28
29 Setup:
29 Setup:
30
30
31 $ hg init t
31 $ hg init t
32 $ cd t
32 $ cd t
33 $ cacheexists
33 $ cacheexists
34 no tag cache
34 no tag cache
35 $ fnodescacheexists
35 $ fnodescacheexists
36 no fnodes cache
36 no fnodes cache
37 $ hg id
37 $ hg id
38 000000000000 tip
38 000000000000 tip
39 $ cacheexists
39 $ cacheexists
40 no tag cache
40 no tag cache
41 $ fnodescacheexists
41 $ fnodescacheexists
42 no fnodes cache
42 no fnodes cache
43 $ echo a > a
43 $ echo a > a
44 $ hg add a
44 $ hg add a
45 $ hg commit -m "test"
45 $ hg commit -m "test"
46 $ hg co
46 $ hg co
47 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 $ hg identify
48 $ hg identify
49 acb14030fe0a tip
49 acb14030fe0a tip
50 $ cacheexists
50 $ cacheexists
51 tag cache exists
51 tag cache exists
52 No fnodes cache because .hgtags file doesn't exist
52 No fnodes cache because .hgtags file doesn't exist
53 (this is an implementation detail)
53 (this is an implementation detail)
54 $ fnodescacheexists
54 $ fnodescacheexists
55 no fnodes cache
55 no fnodes cache
56
56
57 Try corrupting the cache
57 Try corrupting the cache
58
58
59 $ printf 'a b' > .hg/cache/tags2-visible
59 $ printf 'a b' > .hg/cache/tags2-visible
60 $ hg identify
60 $ hg identify
61 acb14030fe0a tip
61 acb14030fe0a tip
62 $ cacheexists
62 $ cacheexists
63 tag cache exists
63 tag cache exists
64 $ fnodescacheexists
64 $ fnodescacheexists
65 no fnodes cache
65 no fnodes cache
66 $ hg identify
66 $ hg identify
67 acb14030fe0a tip
67 acb14030fe0a tip
68
68
69 Create local tag with long name:
69 Create local tag with long name:
70
70
71 $ T=`hg identify --debug --id`
71 $ T=`hg identify --debug --id`
72 $ hg tag -l "This is a local tag with a really long name!"
72 $ hg tag -l "This is a local tag with a really long name!"
73 $ hg tags
73 $ hg tags
74 tip 0:acb14030fe0a
74 tip 0:acb14030fe0a
75 This is a local tag with a really long name! 0:acb14030fe0a
75 This is a local tag with a really long name! 0:acb14030fe0a
76 $ rm .hg/localtags
76 $ rm .hg/localtags
77
77
78 Create a tag behind hg's back:
78 Create a tag behind hg's back:
79
79
80 $ echo "$T first" > .hgtags
80 $ echo "$T first" > .hgtags
81 $ cat .hgtags
81 $ cat .hgtags
82 acb14030fe0a21b60322c440ad2d20cf7685a376 first
82 acb14030fe0a21b60322c440ad2d20cf7685a376 first
83 $ hg add .hgtags
83 $ hg add .hgtags
84 $ hg commit -m "add tags"
84 $ hg commit -m "add tags"
85 $ hg tags
85 $ hg tags
86 tip 1:b9154636be93
86 tip 1:b9154636be93
87 first 0:acb14030fe0a
87 first 0:acb14030fe0a
88 $ hg identify
88 $ hg identify
89 b9154636be93 tip
89 b9154636be93 tip
90
90
91 We should have a fnodes cache now that we have a real tag
91 We should have a fnodes cache now that we have a real tag
92 The cache should have an empty entry for rev 0 and a valid entry for rev 1.
92 The cache should have an empty entry for rev 0 and a valid entry for rev 1.
93
93
94
94
95 $ fnodescacheexists
95 $ fnodescacheexists
96 fnodes cache exists
96 fnodes cache exists
97 $ f --size --hexdump .hg/cache/hgtagsfnodes1
97 $ f --size --hexdump .hg/cache/hgtagsfnodes1
98 .hg/cache/hgtagsfnodes1: size=48
98 .hg/cache/hgtagsfnodes1: size=48
99 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
99 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
100 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
100 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
101 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
101 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
102
102
103 Repeat with cold tag cache:
103 Repeat with cold tag cache:
104
104
105 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
105 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
106 $ hg identify
106 $ hg identify
107 b9154636be93 tip
107 b9154636be93 tip
108
108
109 $ fnodescacheexists
109 $ fnodescacheexists
110 fnodes cache exists
110 fnodes cache exists
111 $ f --size --hexdump .hg/cache/hgtagsfnodes1
111 $ f --size --hexdump .hg/cache/hgtagsfnodes1
112 .hg/cache/hgtagsfnodes1: size=48
112 .hg/cache/hgtagsfnodes1: size=48
113 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
113 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
114 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
114 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
115 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
115 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
116
116
117 And again, but now unable to write tag cache:
117 And again, but now unable to write tag cache or lock file:
118
118
119 #if unix-permissions
119 #if unix-permissions
120 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
120 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
121 $ chmod 555 .hg/cache
121 $ chmod 555 .hg/cache
122 $ hg identify
122 $ hg identify
123 b9154636be93 tip
123 b9154636be93 tip
124 $ chmod 755 .hg/cache
124 $ chmod 755 .hg/cache
125
126 $ chmod 555 .hg
127 $ hg identify
128 b9154636be93 tip
129 $ chmod 755 .hg
125 #endif
130 #endif
126
131
127 Tag cache debug info written to blackbox log
132 Tag cache debug info written to blackbox log
128
133
129 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
134 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
130 $ hg identify
135 $ hg identify
131 b9154636be93 tip
136 b9154636be93 tip
132 $ hg blackbox -l 5
137 $ hg blackbox -l 5
133 1970/01/01 00:00:00 bob> identify
138 1970/01/01 00:00:00 bob> identify
134 1970/01/01 00:00:00 bob> writing 48 bytes to cache/hgtagsfnodes1
139 1970/01/01 00:00:00 bob> writing 48 bytes to cache/hgtagsfnodes1
135 1970/01/01 00:00:00 bob> 0/1 cache hits/lookups in * seconds (glob)
140 1970/01/01 00:00:00 bob> 0/1 cache hits/lookups in * seconds (glob)
136 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
141 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
137 1970/01/01 00:00:00 bob> identify exited 0 after ?.?? seconds (glob)
142 1970/01/01 00:00:00 bob> identify exited 0 after ?.?? seconds (glob)
138
143
139 Failure to acquire lock results in no write
144 Failure to acquire lock results in no write
140
145
141 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
146 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
142 $ echo 'foo:1' > .hg/wlock
147 $ echo 'foo:1' > .hg/wlock
143 $ hg identify
148 $ hg identify
144 b9154636be93 tip
149 b9154636be93 tip
145 $ hg blackbox -l 5
150 $ hg blackbox -l 5
146 1970/01/01 00:00:00 bob> identify
151 1970/01/01 00:00:00 bob> identify
147 1970/01/01 00:00:00 bob> not writing .hg/cache/hgtagsfnodes1 because lock held
152 1970/01/01 00:00:00 bob> not writing .hg/cache/hgtagsfnodes1 because lock cannot be acquired
148 1970/01/01 00:00:00 bob> 0/1 cache hits/lookups in * seconds (glob)
153 1970/01/01 00:00:00 bob> 0/1 cache hits/lookups in * seconds (glob)
149 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
154 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
150 1970/01/01 00:00:00 bob> identify exited 0 after * seconds (glob)
155 1970/01/01 00:00:00 bob> identify exited 0 after * seconds (glob)
151
156
152 $ fnodescacheexists
157 $ fnodescacheexists
153 no fnodes cache
158 no fnodes cache
154
159
155 $ rm .hg/wlock
160 $ rm .hg/wlock
156
161
157 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
162 $ rm -f .hg/cache/tags2-visible .hg/cache/hgtagsfnodes1
158 $ hg identify
163 $ hg identify
159 b9154636be93 tip
164 b9154636be93 tip
160
165
161 Create a branch:
166 Create a branch:
162
167
163 $ echo bb > a
168 $ echo bb > a
164 $ hg status
169 $ hg status
165 M a
170 M a
166 $ hg identify
171 $ hg identify
167 b9154636be93+ tip
172 b9154636be93+ tip
168 $ hg co first
173 $ hg co first
169 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
174 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
170 $ hg id
175 $ hg id
171 acb14030fe0a+ first
176 acb14030fe0a+ first
172 $ hg -v id
177 $ hg -v id
173 acb14030fe0a+ first
178 acb14030fe0a+ first
174 $ hg status
179 $ hg status
175 M a
180 M a
176 $ echo 1 > b
181 $ echo 1 > b
177 $ hg add b
182 $ hg add b
178 $ hg commit -m "branch"
183 $ hg commit -m "branch"
179 created new head
184 created new head
180
185
181 Creating a new commit shouldn't append the .hgtags fnodes cache until
186 Creating a new commit shouldn't append the .hgtags fnodes cache until
182 tags info is accessed
187 tags info is accessed
183
188
184 $ f --size --hexdump .hg/cache/hgtagsfnodes1
189 $ f --size --hexdump .hg/cache/hgtagsfnodes1
185 .hg/cache/hgtagsfnodes1: size=48
190 .hg/cache/hgtagsfnodes1: size=48
186 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
191 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
187 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
192 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
188 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
193 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
189
194
190 $ hg id
195 $ hg id
191 c8edf04160c7 tip
196 c8edf04160c7 tip
192
197
193 First 4 bytes of record 3 are changeset fragment
198 First 4 bytes of record 3 are changeset fragment
194
199
195 $ f --size --hexdump .hg/cache/hgtagsfnodes1
200 $ f --size --hexdump .hg/cache/hgtagsfnodes1
196 .hg/cache/hgtagsfnodes1: size=72
201 .hg/cache/hgtagsfnodes1: size=72
197 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
202 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
198 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
203 0010: ff ff ff ff ff ff ff ff b9 15 46 36 26 b7 b4 a7 |..........F6&...|
199 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
204 0020: 73 e0 9e e3 c5 2f 51 0e 19 e0 5e 1f f9 66 d8 59 |s..../Q...^..f.Y|
200 0030: c8 ed f0 41 00 00 00 00 00 00 00 00 00 00 00 00 |...A............|
205 0030: c8 ed f0 41 00 00 00 00 00 00 00 00 00 00 00 00 |...A............|
201 0040: 00 00 00 00 00 00 00 00 |........|
206 0040: 00 00 00 00 00 00 00 00 |........|
202
207
203 Merge the two heads:
208 Merge the two heads:
204
209
205 $ hg merge 1
210 $ hg merge 1
206 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
211 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 (branch merge, don't forget to commit)
212 (branch merge, don't forget to commit)
208 $ hg id
213 $ hg id
209 c8edf04160c7+b9154636be93+ tip
214 c8edf04160c7+b9154636be93+ tip
210 $ hg status
215 $ hg status
211 M .hgtags
216 M .hgtags
212 $ hg commit -m "merge"
217 $ hg commit -m "merge"
213
218
214 Create a fake head, make sure tag not visible afterwards:
219 Create a fake head, make sure tag not visible afterwards:
215
220
216 $ cp .hgtags tags
221 $ cp .hgtags tags
217 $ hg tag last
222 $ hg tag last
218 $ hg rm .hgtags
223 $ hg rm .hgtags
219 $ hg commit -m "remove"
224 $ hg commit -m "remove"
220
225
221 $ mv tags .hgtags
226 $ mv tags .hgtags
222 $ hg add .hgtags
227 $ hg add .hgtags
223 $ hg commit -m "readd"
228 $ hg commit -m "readd"
224 $
229 $
225 $ hg tags
230 $ hg tags
226 tip 6:35ff301afafe
231 tip 6:35ff301afafe
227 first 0:acb14030fe0a
232 first 0:acb14030fe0a
228
233
229 Add invalid tags:
234 Add invalid tags:
230
235
231 $ echo "spam" >> .hgtags
236 $ echo "spam" >> .hgtags
232 $ echo >> .hgtags
237 $ echo >> .hgtags
233 $ echo "foo bar" >> .hgtags
238 $ echo "foo bar" >> .hgtags
234 $ echo "a5a5 invalid" >> .hg/localtags
239 $ echo "a5a5 invalid" >> .hg/localtags
235 $ cat .hgtags
240 $ cat .hgtags
236 acb14030fe0a21b60322c440ad2d20cf7685a376 first
241 acb14030fe0a21b60322c440ad2d20cf7685a376 first
237 spam
242 spam
238
243
239 foo bar
244 foo bar
240 $ hg commit -m "tags"
245 $ hg commit -m "tags"
241
246
242 Report tag parse error on other head:
247 Report tag parse error on other head:
243
248
244 $ hg up 3
249 $ hg up 3
245 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 $ echo 'x y' >> .hgtags
251 $ echo 'x y' >> .hgtags
247 $ hg commit -m "head"
252 $ hg commit -m "head"
248 created new head
253 created new head
249
254
250 $ hg tags
255 $ hg tags
251 .hgtags@75d9f02dfe28, line 2: cannot parse entry
256 .hgtags@75d9f02dfe28, line 2: cannot parse entry
252 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
257 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
253 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
258 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
254 tip 8:c4be69a18c11
259 tip 8:c4be69a18c11
255 first 0:acb14030fe0a
260 first 0:acb14030fe0a
256 $ hg tip
261 $ hg tip
257 changeset: 8:c4be69a18c11
262 changeset: 8:c4be69a18c11
258 tag: tip
263 tag: tip
259 parent: 3:ac5e980c4dc0
264 parent: 3:ac5e980c4dc0
260 user: test
265 user: test
261 date: Thu Jan 01 00:00:00 1970 +0000
266 date: Thu Jan 01 00:00:00 1970 +0000
262 summary: head
267 summary: head
263
268
264
269
265 Test tag precedence rules:
270 Test tag precedence rules:
266
271
267 $ cd ..
272 $ cd ..
268 $ hg init t2
273 $ hg init t2
269 $ cd t2
274 $ cd t2
270 $ echo foo > foo
275 $ echo foo > foo
271 $ hg add foo
276 $ hg add foo
272 $ hg ci -m 'add foo' # rev 0
277 $ hg ci -m 'add foo' # rev 0
273 $ hg tag bar # rev 1
278 $ hg tag bar # rev 1
274 $ echo >> foo
279 $ echo >> foo
275 $ hg ci -m 'change foo 1' # rev 2
280 $ hg ci -m 'change foo 1' # rev 2
276 $ hg up -C 1
281 $ hg up -C 1
277 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
282 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
278 $ hg tag -r 1 -f bar # rev 3
283 $ hg tag -r 1 -f bar # rev 3
279 $ hg up -C 1
284 $ hg up -C 1
280 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 $ echo >> foo
286 $ echo >> foo
282 $ hg ci -m 'change foo 2' # rev 4
287 $ hg ci -m 'change foo 2' # rev 4
283 created new head
288 created new head
284 $ hg tags
289 $ hg tags
285 tip 4:0c192d7d5e6b
290 tip 4:0c192d7d5e6b
286 bar 1:78391a272241
291 bar 1:78391a272241
287
292
288 Repeat in case of cache effects:
293 Repeat in case of cache effects:
289
294
290 $ hg tags
295 $ hg tags
291 tip 4:0c192d7d5e6b
296 tip 4:0c192d7d5e6b
292 bar 1:78391a272241
297 bar 1:78391a272241
293
298
294 Detailed dump of tag info:
299 Detailed dump of tag info:
295
300
296 $ hg heads -q # expect 4, 3, 2
301 $ hg heads -q # expect 4, 3, 2
297 4:0c192d7d5e6b
302 4:0c192d7d5e6b
298 3:6fa450212aeb
303 3:6fa450212aeb
299 2:7a94127795a3
304 2:7a94127795a3
300 $ dumptags 2
305 $ dumptags 2
301 rev 2: .hgtags:
306 rev 2: .hgtags:
302 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
307 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
303 $ dumptags 3
308 $ dumptags 3
304 rev 3: .hgtags:
309 rev 3: .hgtags:
305 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
310 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
306 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
311 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
307 78391a272241d70354aa14c874552cad6b51bb42 bar
312 78391a272241d70354aa14c874552cad6b51bb42 bar
308 $ dumptags 4
313 $ dumptags 4
309 rev 4: .hgtags:
314 rev 4: .hgtags:
310 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
315 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
311
316
312 Dump cache:
317 Dump cache:
313
318
314 $ cat .hg/cache/tags2-visible
319 $ cat .hg/cache/tags2-visible
315 4 0c192d7d5e6b78a714de54a2e9627952a877e25a
320 4 0c192d7d5e6b78a714de54a2e9627952a877e25a
316 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
321 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
317 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
322 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
318 78391a272241d70354aa14c874552cad6b51bb42 bar
323 78391a272241d70354aa14c874552cad6b51bb42 bar
319
324
320 $ f --size --hexdump .hg/cache/hgtagsfnodes1
325 $ f --size --hexdump .hg/cache/hgtagsfnodes1
321 .hg/cache/hgtagsfnodes1: size=120
326 .hg/cache/hgtagsfnodes1: size=120
322 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
327 0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
323 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
328 0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
324 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
329 0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
325 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
330 0030: 7a 94 12 77 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |z..w.....1....B(|
326 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
331 0040: 78 ee 5a 2d ad bc 94 3d 6f a4 50 21 7d 3b 71 8c |x.Z-...=o.P!};q.|
327 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
332 0050: 96 4e f3 7b 89 e5 50 eb da fd 57 89 e7 6c e1 b0 |.N.{..P...W..l..|
328 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
333 0060: 0c 19 2d 7d 0c 04 f2 a8 af 31 de 17 fa b7 42 28 |..-}.....1....B(|
329 0070: 78 ee 5a 2d ad bc 94 3d |x.Z-...=|
334 0070: 78 ee 5a 2d ad bc 94 3d |x.Z-...=|
330
335
331 Corrupt the .hgtags fnodes cache
336 Corrupt the .hgtags fnodes cache
332 Extra junk data at the end should get overwritten on next cache update
337 Extra junk data at the end should get overwritten on next cache update
333
338
334 $ echo extra >> .hg/cache/hgtagsfnodes1
339 $ echo extra >> .hg/cache/hgtagsfnodes1
335 $ echo dummy1 > foo
340 $ echo dummy1 > foo
336 $ hg commit -m throwaway1
341 $ hg commit -m throwaway1
337
342
338 $ hg tags
343 $ hg tags
339 tip 5:8dbfe60eff30
344 tip 5:8dbfe60eff30
340 bar 1:78391a272241
345 bar 1:78391a272241
341
346
342 $ hg blackbox -l 5
347 $ hg blackbox -l 5
343 1970/01/01 00:00:00 bob> tags
348 1970/01/01 00:00:00 bob> tags
344 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
349 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
345 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
350 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
346 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
351 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
347 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
352 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
348
353
349 #if unix-permissions no-root
354 #if unix-permissions no-root
350 Errors writing to .hgtags fnodes cache are silently ignored
355 Errors writing to .hgtags fnodes cache are silently ignored
351
356
352 $ echo dummy2 > foo
357 $ echo dummy2 > foo
353 $ hg commit -m throwaway2
358 $ hg commit -m throwaway2
354
359
355 $ chmod a-w .hg/cache/hgtagsfnodes1
360 $ chmod a-w .hg/cache/hgtagsfnodes1
356 $ rm -f .hg/cache/tags2-visible
361 $ rm -f .hg/cache/tags2-visible
357
362
358 $ hg tags
363 $ hg tags
359 tip 6:b968051b5cf3
364 tip 6:b968051b5cf3
360 bar 1:78391a272241
365 bar 1:78391a272241
361
366
362 $ hg blackbox -l 5
367 $ hg blackbox -l 5
363 1970/01/01 00:00:00 bob> tags
368 1970/01/01 00:00:00 bob> tags
364 1970/01/01 00:00:00 bob> couldn't write cache/hgtagsfnodes1: [Errno 13] Permission denied: '$TESTTMP/t2/.hg/cache/hgtagsfnodes1'
369 1970/01/01 00:00:00 bob> couldn't write cache/hgtagsfnodes1: [Errno 13] Permission denied: '$TESTTMP/t2/.hg/cache/hgtagsfnodes1'
365 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
370 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
366 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
371 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
367 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
372 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
368
373
369 $ chmod a+w .hg/cache/hgtagsfnodes1
374 $ chmod a+w .hg/cache/hgtagsfnodes1
370
375
371 $ rm -f .hg/cache/tags2-visible
376 $ rm -f .hg/cache/tags2-visible
372 $ hg tags
377 $ hg tags
373 tip 6:b968051b5cf3
378 tip 6:b968051b5cf3
374 bar 1:78391a272241
379 bar 1:78391a272241
375
380
376 $ hg blackbox -l 5
381 $ hg blackbox -l 5
377 1970/01/01 00:00:00 bob> tags
382 1970/01/01 00:00:00 bob> tags
378 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
383 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
379 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
384 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
380 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
385 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
381 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
386 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
382
387
383 $ f --size .hg/cache/hgtagsfnodes1
388 $ f --size .hg/cache/hgtagsfnodes1
384 .hg/cache/hgtagsfnodes1: size=168
389 .hg/cache/hgtagsfnodes1: size=168
385
390
386 $ hg -q --config extensions.strip= strip -r 6 --no-backup
391 $ hg -q --config extensions.strip= strip -r 6 --no-backup
387 #endif
392 #endif
388
393
389 Stripping doesn't truncate the tags cache until new data is available
394 Stripping doesn't truncate the tags cache until new data is available
390
395
391 $ rm -f .hg/cache/hgtagsfnodes1 .hg/cache/tags2-visible
396 $ rm -f .hg/cache/hgtagsfnodes1 .hg/cache/tags2-visible
392 $ hg tags
397 $ hg tags
393 tip 5:8dbfe60eff30
398 tip 5:8dbfe60eff30
394 bar 1:78391a272241
399 bar 1:78391a272241
395
400
396 $ f --size .hg/cache/hgtagsfnodes1
401 $ f --size .hg/cache/hgtagsfnodes1
397 .hg/cache/hgtagsfnodes1: size=144
402 .hg/cache/hgtagsfnodes1: size=144
398
403
399 $ hg -q --config extensions.strip= strip -r 5 --no-backup
404 $ hg -q --config extensions.strip= strip -r 5 --no-backup
400 $ hg tags
405 $ hg tags
401 tip 4:0c192d7d5e6b
406 tip 4:0c192d7d5e6b
402 bar 1:78391a272241
407 bar 1:78391a272241
403
408
404 $ hg blackbox -l 4
409 $ hg blackbox -l 4
405 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
410 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
406 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
411 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
407 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
412 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
408 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
413 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
409
414
410 $ f --size .hg/cache/hgtagsfnodes1
415 $ f --size .hg/cache/hgtagsfnodes1
411 .hg/cache/hgtagsfnodes1: size=120
416 .hg/cache/hgtagsfnodes1: size=120
412
417
413 $ echo dummy > foo
418 $ echo dummy > foo
414 $ hg commit -m throwaway3
419 $ hg commit -m throwaway3
415
420
416 $ hg tags
421 $ hg tags
417 tip 5:035f65efb448
422 tip 5:035f65efb448
418 bar 1:78391a272241
423 bar 1:78391a272241
419
424
420 $ hg blackbox -l 5
425 $ hg blackbox -l 5
421 1970/01/01 00:00:00 bob> tags
426 1970/01/01 00:00:00 bob> tags
422 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
427 1970/01/01 00:00:00 bob> writing 24 bytes to cache/hgtagsfnodes1
423 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
428 1970/01/01 00:00:00 bob> 2/3 cache hits/lookups in * seconds (glob)
424 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
429 1970/01/01 00:00:00 bob> writing .hg/cache/tags2-visible with 1 tags
425 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
430 1970/01/01 00:00:00 bob> tags exited 0 after * seconds (glob)
426 $ f --size .hg/cache/hgtagsfnodes1
431 $ f --size .hg/cache/hgtagsfnodes1
427 .hg/cache/hgtagsfnodes1: size=144
432 .hg/cache/hgtagsfnodes1: size=144
428
433
429 $ hg -q --config extensions.strip= strip -r 5 --no-backup
434 $ hg -q --config extensions.strip= strip -r 5 --no-backup
430
435
431 Test tag removal:
436 Test tag removal:
432
437
433 $ hg tag --remove bar # rev 5
438 $ hg tag --remove bar # rev 5
434 $ hg tip -vp
439 $ hg tip -vp
435 changeset: 5:5f6e8655b1c7
440 changeset: 5:5f6e8655b1c7
436 tag: tip
441 tag: tip
437 user: test
442 user: test
438 date: Thu Jan 01 00:00:00 1970 +0000
443 date: Thu Jan 01 00:00:00 1970 +0000
439 files: .hgtags
444 files: .hgtags
440 description:
445 description:
441 Removed tag bar
446 Removed tag bar
442
447
443
448
444 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
449 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
445 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
450 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
446 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
451 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
447 @@ -1,1 +1,3 @@
452 @@ -1,1 +1,3 @@
448 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
453 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
449 +78391a272241d70354aa14c874552cad6b51bb42 bar
454 +78391a272241d70354aa14c874552cad6b51bb42 bar
450 +0000000000000000000000000000000000000000 bar
455 +0000000000000000000000000000000000000000 bar
451
456
452 $ hg tags
457 $ hg tags
453 tip 5:5f6e8655b1c7
458 tip 5:5f6e8655b1c7
454 $ hg tags # again, try to expose cache bugs
459 $ hg tags # again, try to expose cache bugs
455 tip 5:5f6e8655b1c7
460 tip 5:5f6e8655b1c7
456
461
457 Remove nonexistent tag:
462 Remove nonexistent tag:
458
463
459 $ hg tag --remove foobar
464 $ hg tag --remove foobar
460 abort: tag 'foobar' does not exist
465 abort: tag 'foobar' does not exist
461 [255]
466 [255]
462 $ hg tip
467 $ hg tip
463 changeset: 5:5f6e8655b1c7
468 changeset: 5:5f6e8655b1c7
464 tag: tip
469 tag: tip
465 user: test
470 user: test
466 date: Thu Jan 01 00:00:00 1970 +0000
471 date: Thu Jan 01 00:00:00 1970 +0000
467 summary: Removed tag bar
472 summary: Removed tag bar
468
473
469
474
470 Undo a tag with rollback:
475 Undo a tag with rollback:
471
476
472 $ hg rollback # destroy rev 5 (restore bar)
477 $ hg rollback # destroy rev 5 (restore bar)
473 repository tip rolled back to revision 4 (undo commit)
478 repository tip rolled back to revision 4 (undo commit)
474 working directory now based on revision 4
479 working directory now based on revision 4
475 $ hg tags
480 $ hg tags
476 tip 4:0c192d7d5e6b
481 tip 4:0c192d7d5e6b
477 bar 1:78391a272241
482 bar 1:78391a272241
478 $ hg tags
483 $ hg tags
479 tip 4:0c192d7d5e6b
484 tip 4:0c192d7d5e6b
480 bar 1:78391a272241
485 bar 1:78391a272241
481
486
482 Test tag rank:
487 Test tag rank:
483
488
484 $ cd ..
489 $ cd ..
485 $ hg init t3
490 $ hg init t3
486 $ cd t3
491 $ cd t3
487 $ echo foo > foo
492 $ echo foo > foo
488 $ hg add foo
493 $ hg add foo
489 $ hg ci -m 'add foo' # rev 0
494 $ hg ci -m 'add foo' # rev 0
490 $ hg tag -f bar # rev 1 bar -> 0
495 $ hg tag -f bar # rev 1 bar -> 0
491 $ hg tag -f bar # rev 2 bar -> 1
496 $ hg tag -f bar # rev 2 bar -> 1
492 $ hg tag -fr 0 bar # rev 3 bar -> 0
497 $ hg tag -fr 0 bar # rev 3 bar -> 0
493 $ hg tag -fr 1 bar # rev 4 bar -> 1
498 $ hg tag -fr 1 bar # rev 4 bar -> 1
494 $ hg tag -fr 0 bar # rev 5 bar -> 0
499 $ hg tag -fr 0 bar # rev 5 bar -> 0
495 $ hg tags
500 $ hg tags
496 tip 5:85f05169d91d
501 tip 5:85f05169d91d
497 bar 0:bbd179dfa0a7
502 bar 0:bbd179dfa0a7
498 $ hg co 3
503 $ hg co 3
499 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
504 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 $ echo barbar > foo
505 $ echo barbar > foo
501 $ hg ci -m 'change foo' # rev 6
506 $ hg ci -m 'change foo' # rev 6
502 created new head
507 created new head
503 $ hg tags
508 $ hg tags
504 tip 6:735c3ca72986
509 tip 6:735c3ca72986
505 bar 0:bbd179dfa0a7
510 bar 0:bbd179dfa0a7
506
511
507 Don't allow moving tag without -f:
512 Don't allow moving tag without -f:
508
513
509 $ hg tag -r 3 bar
514 $ hg tag -r 3 bar
510 abort: tag 'bar' already exists (use -f to force)
515 abort: tag 'bar' already exists (use -f to force)
511 [255]
516 [255]
512 $ hg tags
517 $ hg tags
513 tip 6:735c3ca72986
518 tip 6:735c3ca72986
514 bar 0:bbd179dfa0a7
519 bar 0:bbd179dfa0a7
515
520
516 Strip 1: expose an old head:
521 Strip 1: expose an old head:
517
522
518 $ hg --config extensions.mq= strip 5
523 $ hg --config extensions.mq= strip 5
519 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
524 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
520 $ hg tags # partly stale cache
525 $ hg tags # partly stale cache
521 tip 5:735c3ca72986
526 tip 5:735c3ca72986
522 bar 1:78391a272241
527 bar 1:78391a272241
523 $ hg tags # up-to-date cache
528 $ hg tags # up-to-date cache
524 tip 5:735c3ca72986
529 tip 5:735c3ca72986
525 bar 1:78391a272241
530 bar 1:78391a272241
526
531
527 Strip 2: destroy whole branch, no old head exposed
532 Strip 2: destroy whole branch, no old head exposed
528
533
529 $ hg --config extensions.mq= strip 4
534 $ hg --config extensions.mq= strip 4
530 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
535 saved backup bundle to $TESTTMP/t3/.hg/strip-backup/*-backup.hg (glob)
531 $ hg tags # partly stale
536 $ hg tags # partly stale
532 tip 4:735c3ca72986
537 tip 4:735c3ca72986
533 bar 0:bbd179dfa0a7
538 bar 0:bbd179dfa0a7
534 $ rm -f .hg/cache/tags2-visible
539 $ rm -f .hg/cache/tags2-visible
535 $ hg tags # cold cache
540 $ hg tags # cold cache
536 tip 4:735c3ca72986
541 tip 4:735c3ca72986
537 bar 0:bbd179dfa0a7
542 bar 0:bbd179dfa0a7
538
543
539 Test tag rank with 3 heads:
544 Test tag rank with 3 heads:
540
545
541 $ cd ..
546 $ cd ..
542 $ hg init t4
547 $ hg init t4
543 $ cd t4
548 $ cd t4
544 $ echo foo > foo
549 $ echo foo > foo
545 $ hg add
550 $ hg add
546 adding foo
551 adding foo
547 $ hg ci -m 'add foo' # rev 0
552 $ hg ci -m 'add foo' # rev 0
548 $ hg tag bar # rev 1 bar -> 0
553 $ hg tag bar # rev 1 bar -> 0
549 $ hg tag -f bar # rev 2 bar -> 1
554 $ hg tag -f bar # rev 2 bar -> 1
550 $ hg up -qC 0
555 $ hg up -qC 0
551 $ hg tag -fr 2 bar # rev 3 bar -> 2
556 $ hg tag -fr 2 bar # rev 3 bar -> 2
552 $ hg tags
557 $ hg tags
553 tip 3:197c21bbbf2c
558 tip 3:197c21bbbf2c
554 bar 2:6fa450212aeb
559 bar 2:6fa450212aeb
555 $ hg up -qC 0
560 $ hg up -qC 0
556 $ hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
561 $ hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
557
562
558 Bar should still point to rev 2:
563 Bar should still point to rev 2:
559
564
560 $ hg tags
565 $ hg tags
561 tip 4:3b4b14ed0202
566 tip 4:3b4b14ed0202
562 bar 2:6fa450212aeb
567 bar 2:6fa450212aeb
563
568
564 Test that removing global/local tags does not get confused when trying
569 Test that removing global/local tags does not get confused when trying
565 to remove a tag of type X which actually only exists as a type Y:
570 to remove a tag of type X which actually only exists as a type Y:
566
571
567 $ cd ..
572 $ cd ..
568 $ hg init t5
573 $ hg init t5
569 $ cd t5
574 $ cd t5
570 $ echo foo > foo
575 $ echo foo > foo
571 $ hg add
576 $ hg add
572 adding foo
577 adding foo
573 $ hg ci -m 'add foo' # rev 0
578 $ hg ci -m 'add foo' # rev 0
574
579
575 $ hg tag -r 0 -l localtag
580 $ hg tag -r 0 -l localtag
576 $ hg tag --remove localtag
581 $ hg tag --remove localtag
577 abort: tag 'localtag' is not a global tag
582 abort: tag 'localtag' is not a global tag
578 [255]
583 [255]
579 $
584 $
580 $ hg tag -r 0 globaltag
585 $ hg tag -r 0 globaltag
581 $ hg tag --remove -l globaltag
586 $ hg tag --remove -l globaltag
582 abort: tag 'globaltag' is not a local tag
587 abort: tag 'globaltag' is not a local tag
583 [255]
588 [255]
584 $ hg tags -v
589 $ hg tags -v
585 tip 1:a0b6fe111088
590 tip 1:a0b6fe111088
586 localtag 0:bbd179dfa0a7 local
591 localtag 0:bbd179dfa0a7 local
587 globaltag 0:bbd179dfa0a7
592 globaltag 0:bbd179dfa0a7
588
593
589 Test for issue3911
594 Test for issue3911
590
595
591 $ hg tag -r 0 -l localtag2
596 $ hg tag -r 0 -l localtag2
592 $ hg tag -l --remove localtag2
597 $ hg tag -l --remove localtag2
593 $ hg tags -v
598 $ hg tags -v
594 tip 1:a0b6fe111088
599 tip 1:a0b6fe111088
595 localtag 0:bbd179dfa0a7 local
600 localtag 0:bbd179dfa0a7 local
596 globaltag 0:bbd179dfa0a7
601 globaltag 0:bbd179dfa0a7
597
602
598 $ hg tag -r 1 -f localtag
603 $ hg tag -r 1 -f localtag
599 $ hg tags -v
604 $ hg tags -v
600 tip 2:5c70a037bb37
605 tip 2:5c70a037bb37
601 localtag 1:a0b6fe111088
606 localtag 1:a0b6fe111088
602 globaltag 0:bbd179dfa0a7
607 globaltag 0:bbd179dfa0a7
603
608
604 $ hg tags -v
609 $ hg tags -v
605 tip 2:5c70a037bb37
610 tip 2:5c70a037bb37
606 localtag 1:a0b6fe111088
611 localtag 1:a0b6fe111088
607 globaltag 0:bbd179dfa0a7
612 globaltag 0:bbd179dfa0a7
608
613
609 $ hg tag -r 1 localtag2
614 $ hg tag -r 1 localtag2
610 $ hg tags -v
615 $ hg tags -v
611 tip 3:bbfb8cd42be2
616 tip 3:bbfb8cd42be2
612 localtag2 1:a0b6fe111088
617 localtag2 1:a0b6fe111088
613 localtag 1:a0b6fe111088
618 localtag 1:a0b6fe111088
614 globaltag 0:bbd179dfa0a7
619 globaltag 0:bbd179dfa0a7
615
620
616 $ hg tags -v
621 $ hg tags -v
617 tip 3:bbfb8cd42be2
622 tip 3:bbfb8cd42be2
618 localtag2 1:a0b6fe111088
623 localtag2 1:a0b6fe111088
619 localtag 1:a0b6fe111088
624 localtag 1:a0b6fe111088
620 globaltag 0:bbd179dfa0a7
625 globaltag 0:bbd179dfa0a7
621
626
622 $ cd ..
627 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now