##// END OF EJS Templates
tags: don't crash if unable to write tag cache...
Greg Ward -
r9366:9ff178e7 default
parent child Browse files
Show More
@@ -1,338 +1,341 b''
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, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
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 import os
13 import os
14 from node import nullid, bin, hex, short
14 from node import nullid, bin, hex, short
15 from i18n import _
15 from i18n import _
16 import encoding
16 import encoding
17 import error
17 import error
18
18
19 def _debugalways(ui, *msg):
19 def _debugalways(ui, *msg):
20 ui.write(*msg)
20 ui.write(*msg)
21
21
22 def _debugconditional(ui, *msg):
22 def _debugconditional(ui, *msg):
23 ui.debug(*msg)
23 ui.debug(*msg)
24
24
25 def _debugnever(ui, *msg):
25 def _debugnever(ui, *msg):
26 pass
26 pass
27
27
28 _debug = _debugalways
28 _debug = _debugalways
29 _debug = _debugnever
29 _debug = _debugnever
30
30
31 def findglobaltags1(ui, repo, alltags, tagtypes):
31 def findglobaltags1(ui, repo, alltags, tagtypes):
32 '''Find global tags in repo by reading .hgtags from every head that
32 '''Find global tags in repo by reading .hgtags from every head that
33 has a distinct version of it. Updates the dicts alltags, tagtypes
33 has a distinct version of it. Updates the dicts alltags, tagtypes
34 in place: alltags maps tag name to (node, hist) pair (see _readtags()
34 in place: alltags maps tag name to (node, hist) pair (see _readtags()
35 below), and tagtypes maps tag name to tag type ('global' in this
35 below), and tagtypes maps tag name to tag type ('global' in this
36 case).'''
36 case).'''
37
37
38 seen = set()
38 seen = set()
39 fctx = None
39 fctx = None
40 ctxs = [] # list of filectx
40 ctxs = [] # list of filectx
41 for node in repo.heads():
41 for node in repo.heads():
42 try:
42 try:
43 fnode = repo[node].filenode('.hgtags')
43 fnode = repo[node].filenode('.hgtags')
44 except error.LookupError:
44 except error.LookupError:
45 continue
45 continue
46 if fnode not in seen:
46 if fnode not in seen:
47 seen.add(fnode)
47 seen.add(fnode)
48 if not fctx:
48 if not fctx:
49 fctx = repo.filectx('.hgtags', fileid=fnode)
49 fctx = repo.filectx('.hgtags', fileid=fnode)
50 else:
50 else:
51 fctx = fctx.filectx(fnode)
51 fctx = fctx.filectx(fnode)
52 ctxs.append(fctx)
52 ctxs.append(fctx)
53
53
54 # read the tags file from each head, ending with the tip
54 # read the tags file from each head, ending with the tip
55 for fctx in reversed(ctxs):
55 for fctx in reversed(ctxs):
56 filetags = _readtags(
56 filetags = _readtags(
57 ui, repo, fctx.data().splitlines(), fctx)
57 ui, repo, fctx.data().splitlines(), fctx)
58 _updatetags(filetags, "global", alltags, tagtypes)
58 _updatetags(filetags, "global", alltags, tagtypes)
59
59
60 def findglobaltags2(ui, repo, alltags, tagtypes):
60 def findglobaltags2(ui, repo, alltags, tagtypes):
61 '''Same as findglobaltags1(), but with caching.'''
61 '''Same as findglobaltags1(), but with caching.'''
62 # This is so we can be lazy and assume alltags contains only global
62 # This is so we can be lazy and assume alltags contains only global
63 # tags when we pass it to _writetagcache().
63 # tags when we pass it to _writetagcache().
64 assert len(alltags) == len(tagtypes) == 0, \
64 assert len(alltags) == len(tagtypes) == 0, \
65 "findglobaltags() should be called first"
65 "findglobaltags() should be called first"
66
66
67 (heads, tagfnode, cachetags, shouldwrite) = _readtagcache(ui, repo)
67 (heads, tagfnode, cachetags, shouldwrite) = _readtagcache(ui, repo)
68 if cachetags is not None:
68 if cachetags is not None:
69 assert not shouldwrite
69 assert not shouldwrite
70 # XXX is this really 100% correct? are there oddball special
70 # XXX is this really 100% correct? are there oddball special
71 # cases where a global tag should outrank a local tag but won't,
71 # cases where a global tag should outrank a local tag but won't,
72 # because cachetags does not contain rank info?
72 # because cachetags does not contain rank info?
73 _updatetags(cachetags, 'global', alltags, tagtypes)
73 _updatetags(cachetags, 'global', alltags, tagtypes)
74 return
74 return
75
75
76 _debug(ui, "reading tags from %d head(s): %s\n"
76 _debug(ui, "reading tags from %d head(s): %s\n"
77 % (len(heads), map(short, reversed(heads))))
77 % (len(heads), map(short, reversed(heads))))
78 seen = set() # set of fnode
78 seen = set() # set of fnode
79 fctx = None
79 fctx = None
80 for head in reversed(heads): # oldest to newest
80 for head in reversed(heads): # oldest to newest
81 assert head in repo.changelog.nodemap, \
81 assert head in repo.changelog.nodemap, \
82 "tag cache returned bogus head %s" % short(head)
82 "tag cache returned bogus head %s" % short(head)
83
83
84 fnode = tagfnode.get(head)
84 fnode = tagfnode.get(head)
85 if fnode and fnode not in seen:
85 if fnode and fnode not in seen:
86 seen.add(fnode)
86 seen.add(fnode)
87 if not fctx:
87 if not fctx:
88 fctx = repo.filectx('.hgtags', fileid=fnode)
88 fctx = repo.filectx('.hgtags', fileid=fnode)
89 else:
89 else:
90 fctx = fctx.filectx(fnode)
90 fctx = fctx.filectx(fnode)
91
91
92 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
92 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
93 _updatetags(filetags, 'global', alltags, tagtypes)
93 _updatetags(filetags, 'global', alltags, tagtypes)
94
94
95 # and update the cache (if necessary)
95 # and update the cache (if necessary)
96 if shouldwrite:
96 if shouldwrite:
97 _writetagcache(ui, repo, heads, tagfnode, alltags)
97 _writetagcache(ui, repo, heads, tagfnode, alltags)
98
98
99 # Set this to findglobaltags1 to disable tag caching.
99 # Set this to findglobaltags1 to disable tag caching.
100 findglobaltags = findglobaltags2
100 findglobaltags = findglobaltags2
101
101
102 def readlocaltags(ui, repo, alltags, tagtypes):
102 def readlocaltags(ui, repo, alltags, tagtypes):
103 '''Read local tags in repo. Update alltags and tagtypes.'''
103 '''Read local tags in repo. Update alltags and tagtypes.'''
104 try:
104 try:
105 # localtags is in the local encoding; re-encode to UTF-8 on
105 # localtags is in the local encoding; re-encode to UTF-8 on
106 # input for consistency with the rest of this module.
106 # input for consistency with the rest of this module.
107 data = repo.opener("localtags").read()
107 data = repo.opener("localtags").read()
108 filetags = _readtags(
108 filetags = _readtags(
109 ui, repo, data.splitlines(), "localtags",
109 ui, repo, data.splitlines(), "localtags",
110 recode=encoding.fromlocal)
110 recode=encoding.fromlocal)
111 _updatetags(filetags, "local", alltags, tagtypes)
111 _updatetags(filetags, "local", alltags, tagtypes)
112 except IOError:
112 except IOError:
113 pass
113 pass
114
114
115 def _readtags(ui, repo, lines, fn, recode=None):
115 def _readtags(ui, repo, lines, fn, recode=None):
116 '''Read tag definitions from a file (or any source of lines).
116 '''Read tag definitions from a file (or any source of lines).
117 Return a mapping from tag name to (node, hist): node is the node id
117 Return a mapping from tag name to (node, hist): node is the node id
118 from the last line read for that name, and hist is the list of node
118 from the last line read for that name, and hist is the list of node
119 ids previously associated with it (in file order). All node ids are
119 ids previously associated with it (in file order). All node ids are
120 binary, not hex.'''
120 binary, not hex.'''
121
121
122 filetags = {} # map tag name to (node, hist)
122 filetags = {} # map tag name to (node, hist)
123 count = 0
123 count = 0
124
124
125 def warn(msg):
125 def warn(msg):
126 ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
126 ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
127
127
128 for line in lines:
128 for line in lines:
129 count += 1
129 count += 1
130 if not line:
130 if not line:
131 continue
131 continue
132 try:
132 try:
133 (nodehex, name) = line.split(" ", 1)
133 (nodehex, name) = line.split(" ", 1)
134 except ValueError:
134 except ValueError:
135 warn(_("cannot parse entry"))
135 warn(_("cannot parse entry"))
136 continue
136 continue
137 name = name.strip()
137 name = name.strip()
138 if recode:
138 if recode:
139 name = recode(name)
139 name = recode(name)
140 try:
140 try:
141 nodebin = bin(nodehex)
141 nodebin = bin(nodehex)
142 except TypeError:
142 except TypeError:
143 warn(_("node '%s' is not well formed") % nodehex)
143 warn(_("node '%s' is not well formed") % nodehex)
144 continue
144 continue
145 if nodebin not in repo.changelog.nodemap:
145 if nodebin not in repo.changelog.nodemap:
146 # silently ignore as pull -r might cause this
146 # silently ignore as pull -r might cause this
147 continue
147 continue
148
148
149 # update filetags
149 # update filetags
150 hist = []
150 hist = []
151 if name in filetags:
151 if name in filetags:
152 n, hist = filetags[name]
152 n, hist = filetags[name]
153 hist.append(n)
153 hist.append(n)
154 filetags[name] = (nodebin, hist)
154 filetags[name] = (nodebin, hist)
155 return filetags
155 return filetags
156
156
157 def _updatetags(filetags, tagtype, alltags, tagtypes):
157 def _updatetags(filetags, tagtype, alltags, tagtypes):
158 '''Incorporate the tag info read from one file into the two
158 '''Incorporate the tag info read from one file into the two
159 dictionaries, alltags and tagtypes, that contain all tag
159 dictionaries, alltags and tagtypes, that contain all tag
160 info (global across all heads plus local).'''
160 info (global across all heads plus local).'''
161
161
162 for name, nodehist in filetags.iteritems():
162 for name, nodehist in filetags.iteritems():
163 if name not in alltags:
163 if name not in alltags:
164 alltags[name] = nodehist
164 alltags[name] = nodehist
165 tagtypes[name] = tagtype
165 tagtypes[name] = tagtype
166 continue
166 continue
167
167
168 # we prefer alltags[name] if:
168 # we prefer alltags[name] if:
169 # it supercedes us OR
169 # it supercedes us OR
170 # mutual supercedes and it has a higher rank
170 # mutual supercedes and it has a higher rank
171 # otherwise we win because we're tip-most
171 # otherwise we win because we're tip-most
172 anode, ahist = nodehist
172 anode, ahist = nodehist
173 bnode, bhist = alltags[name]
173 bnode, bhist = alltags[name]
174 if (bnode != anode and anode in bhist and
174 if (bnode != anode and anode in bhist and
175 (bnode not in ahist or len(bhist) > len(ahist))):
175 (bnode not in ahist or len(bhist) > len(ahist))):
176 anode = bnode
176 anode = bnode
177 ahist.extend([n for n in bhist if n not in ahist])
177 ahist.extend([n for n in bhist if n not in ahist])
178 alltags[name] = anode, ahist
178 alltags[name] = anode, ahist
179 tagtypes[name] = tagtype
179 tagtypes[name] = tagtype
180
180
181
181
182 # The tag cache only stores info about heads, not the tag contents
182 # The tag cache only stores info about heads, not the tag contents
183 # from each head. I.e. it doesn't try to squeeze out the maximum
183 # from each head. I.e. it doesn't try to squeeze out the maximum
184 # performance, but is simpler has a better chance of actually
184 # performance, but is simpler has a better chance of actually
185 # working correctly. And this gives the biggest performance win: it
185 # working correctly. And this gives the biggest performance win: it
186 # avoids looking up .hgtags in the manifest for every head, and it
186 # avoids looking up .hgtags in the manifest for every head, and it
187 # can avoid calling heads() at all if there have been no changes to
187 # can avoid calling heads() at all if there have been no changes to
188 # the repo.
188 # the repo.
189
189
190 def _readtagcache(ui, repo):
190 def _readtagcache(ui, repo):
191 '''Read the tag cache and return a tuple (heads, fnodes, cachetags,
191 '''Read the tag cache and return a tuple (heads, fnodes, cachetags,
192 shouldwrite). If the cache is completely up-to-date, cachetags is a
192 shouldwrite). If the cache is completely up-to-date, cachetags is a
193 dict of the form returned by _readtags(); otherwise, it is None and
193 dict of the form returned by _readtags(); otherwise, it is None and
194 heads and fnodes are set. In that case, heads is the list of all
194 heads and fnodes are set. In that case, heads is the list of all
195 heads currently in the repository (ordered from tip to oldest) and
195 heads currently in the repository (ordered from tip to oldest) and
196 fnodes is a mapping from head to .hgtags filenode. If those two are
196 fnodes is a mapping from head to .hgtags filenode. If those two are
197 set, caller is responsible for reading tag info from each head.'''
197 set, caller is responsible for reading tag info from each head.'''
198
198
199 try:
199 try:
200 cachefile = repo.opener('tags.cache', 'r')
200 cachefile = repo.opener('tags.cache', 'r')
201 _debug(ui, 'reading tag cache from %s\n' % cachefile.name)
201 _debug(ui, 'reading tag cache from %s\n' % cachefile.name)
202 except IOError:
202 except IOError:
203 cachefile = None
203 cachefile = None
204
204
205 # The cache file consists of lines like
205 # The cache file consists of lines like
206 # <headrev> <headnode> [<tagnode>]
206 # <headrev> <headnode> [<tagnode>]
207 # where <headrev> and <headnode> redundantly identify a repository
207 # where <headrev> and <headnode> redundantly identify a repository
208 # head from the time the cache was written, and <tagnode> is the
208 # head from the time the cache was written, and <tagnode> is the
209 # filenode of .hgtags on that head. Heads with no .hgtags file will
209 # filenode of .hgtags on that head. Heads with no .hgtags file will
210 # have no <tagnode>. The cache is ordered from tip to oldest (which
210 # have no <tagnode>. The cache is ordered from tip to oldest (which
211 # is part of why <headrev> is there: a quick visual check is all
211 # is part of why <headrev> is there: a quick visual check is all
212 # that's required to ensure correct order).
212 # that's required to ensure correct order).
213 #
213 #
214 # This information is enough to let us avoid the most expensive part
214 # This information is enough to let us avoid the most expensive part
215 # of finding global tags, which is looking up <tagnode> in the
215 # of finding global tags, which is looking up <tagnode> in the
216 # manifest for each head.
216 # manifest for each head.
217 cacherevs = [] # list of headrev
217 cacherevs = [] # list of headrev
218 cacheheads = [] # list of headnode
218 cacheheads = [] # list of headnode
219 cachefnode = {} # map headnode to filenode
219 cachefnode = {} # map headnode to filenode
220 if cachefile:
220 if cachefile:
221 for line in cachefile:
221 for line in cachefile:
222 if line == "\n":
222 if line == "\n":
223 break
223 break
224 line = line.rstrip().split()
224 line = line.rstrip().split()
225 cacherevs.append(int(line[0]))
225 cacherevs.append(int(line[0]))
226 headnode = bin(line[1])
226 headnode = bin(line[1])
227 cacheheads.append(headnode)
227 cacheheads.append(headnode)
228 if len(line) == 3:
228 if len(line) == 3:
229 fnode = bin(line[2])
229 fnode = bin(line[2])
230 cachefnode[headnode] = fnode
230 cachefnode[headnode] = fnode
231
231
232 tipnode = repo.changelog.tip()
232 tipnode = repo.changelog.tip()
233 tiprev = len(repo.changelog) - 1
233 tiprev = len(repo.changelog) - 1
234
234
235 # Case 1 (common): tip is the same, so nothing has changed.
235 # Case 1 (common): tip is the same, so nothing has changed.
236 # (Unchanged tip trivially means no changesets have been added.
236 # (Unchanged tip trivially means no changesets have been added.
237 # But, thanks to localrepository.destroyed(), it also means none
237 # But, thanks to localrepository.destroyed(), it also means none
238 # have been destroyed by strip or rollback.)
238 # have been destroyed by strip or rollback.)
239 if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev:
239 if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev:
240 _debug(ui, "tag cache: tip unchanged\n")
240 _debug(ui, "tag cache: tip unchanged\n")
241 tags = _readtags(ui, repo, cachefile, cachefile.name)
241 tags = _readtags(ui, repo, cachefile, cachefile.name)
242 cachefile.close()
242 cachefile.close()
243 return (None, None, tags, False)
243 return (None, None, tags, False)
244 if cachefile:
244 if cachefile:
245 cachefile.close() # ignore rest of file
245 cachefile.close() # ignore rest of file
246
246
247 repoheads = repo.heads()
247 repoheads = repo.heads()
248 # Case 2 (uncommon): empty repo; get out quickly and don't bother
248 # Case 2 (uncommon): empty repo; get out quickly and don't bother
249 # writing an empty cache.
249 # writing an empty cache.
250 if repoheads == [nullid]:
250 if repoheads == [nullid]:
251 return ([], {}, {}, False)
251 return ([], {}, {}, False)
252
252
253 # Case 3 (uncommon): cache file missing or empty.
253 # Case 3 (uncommon): cache file missing or empty.
254 if not cacheheads:
254 if not cacheheads:
255 _debug(ui, 'tag cache: cache file missing or empty\n')
255 _debug(ui, 'tag cache: cache file missing or empty\n')
256
256
257 # Case 4 (uncommon): tip rev decreased. This should only happen
257 # Case 4 (uncommon): tip rev decreased. This should only happen
258 # when we're called from localrepository.destroyed(). Refresh the
258 # when we're called from localrepository.destroyed(). Refresh the
259 # cache so future invocations will not see disappeared heads in the
259 # cache so future invocations will not see disappeared heads in the
260 # cache.
260 # cache.
261 elif cacheheads and tiprev < cacherevs[0]:
261 elif cacheheads and tiprev < cacherevs[0]:
262 _debug(ui,
262 _debug(ui,
263 'tag cache: tip rev decremented (from %d to %d), '
263 'tag cache: tip rev decremented (from %d to %d), '
264 'so we must be destroying nodes\n'
264 'so we must be destroying nodes\n'
265 % (cacherevs[0], tiprev))
265 % (cacherevs[0], tiprev))
266
266
267 # Case 5 (common): tip has changed, so we've added/replaced heads.
267 # Case 5 (common): tip has changed, so we've added/replaced heads.
268 else:
268 else:
269 _debug(ui,
269 _debug(ui,
270 'tag cache: tip has changed (%d:%s); must find new heads\n'
270 'tag cache: tip has changed (%d:%s); must find new heads\n'
271 % (tiprev, short(tipnode)))
271 % (tiprev, short(tipnode)))
272
272
273 # Luckily, the code to handle cases 3, 4, 5 is the same. So the
273 # Luckily, the code to handle cases 3, 4, 5 is the same. So the
274 # above if/elif/else can disappear once we're confident this thing
274 # above if/elif/else can disappear once we're confident this thing
275 # actually works and we don't need the debug output.
275 # actually works and we don't need the debug output.
276
276
277 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
277 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
278 # exposed".
278 # exposed".
279 newheads = [head
279 newheads = [head
280 for head in repoheads
280 for head in repoheads
281 if head not in set(cacheheads)]
281 if head not in set(cacheheads)]
282 _debug(ui, 'tag cache: found %d head(s) not in cache: %s\n'
282 _debug(ui, 'tag cache: found %d head(s) not in cache: %s\n'
283 % (len(newheads), map(short, newheads)))
283 % (len(newheads), map(short, newheads)))
284
284
285 # Now we have to lookup the .hgtags filenode for every new head.
285 # Now we have to lookup the .hgtags filenode for every new head.
286 # This is the most expensive part of finding tags, so performance
286 # This is the most expensive part of finding tags, so performance
287 # depends primarily on the size of newheads. Worst case: no cache
287 # depends primarily on the size of newheads. Worst case: no cache
288 # file, so newheads == repoheads.
288 # file, so newheads == repoheads.
289 for head in newheads:
289 for head in newheads:
290 cctx = repo[head]
290 cctx = repo[head]
291 try:
291 try:
292 fnode = cctx.filenode('.hgtags')
292 fnode = cctx.filenode('.hgtags')
293 cachefnode[head] = fnode
293 cachefnode[head] = fnode
294 except error.LookupError:
294 except error.LookupError:
295 # no .hgtags file on this head
295 # no .hgtags file on this head
296 pass
296 pass
297
297
298 # Caller has to iterate over all heads, but can use the filenodes in
298 # Caller has to iterate over all heads, but can use the filenodes in
299 # cachefnode to get to each .hgtags revision quickly.
299 # cachefnode to get to each .hgtags revision quickly.
300 return (repoheads, cachefnode, None, True)
300 return (repoheads, cachefnode, None, True)
301
301
302 def _writetagcache(ui, repo, heads, tagfnode, cachetags):
302 def _writetagcache(ui, repo, heads, tagfnode, cachetags):
303
303
304 cachefile = repo.opener('tags.cache', 'w', atomictemp=True)
304 try:
305 cachefile = repo.opener('tags.cache', 'w', atomictemp=True)
306 except (OSError, IOError):
307 return
305 _debug(ui, 'writing cache file %s\n' % cachefile.name)
308 _debug(ui, 'writing cache file %s\n' % cachefile.name)
306
309
307 realheads = repo.heads() # for sanity checks below
310 realheads = repo.heads() # for sanity checks below
308 for head in heads:
311 for head in heads:
309 # temporary sanity checks; these can probably be removed
312 # temporary sanity checks; these can probably be removed
310 # once this code has been in crew for a few weeks
313 # once this code has been in crew for a few weeks
311 assert head in repo.changelog.nodemap, \
314 assert head in repo.changelog.nodemap, \
312 'trying to write non-existent node %s to tag cache' % short(head)
315 'trying to write non-existent node %s to tag cache' % short(head)
313 assert head in realheads, \
316 assert head in realheads, \
314 'trying to write non-head %s to tag cache' % short(head)
317 'trying to write non-head %s to tag cache' % short(head)
315 assert head != nullid, \
318 assert head != nullid, \
316 'trying to write nullid to tag cache'
319 'trying to write nullid to tag cache'
317
320
318 # This can't fail because of the first assert above. When/if we
321 # This can't fail because of the first assert above. When/if we
319 # remove that assert, we might want to catch LookupError here
322 # remove that assert, we might want to catch LookupError here
320 # and downgrade it to a warning.
323 # and downgrade it to a warning.
321 rev = repo.changelog.rev(head)
324 rev = repo.changelog.rev(head)
322
325
323 fnode = tagfnode.get(head)
326 fnode = tagfnode.get(head)
324 if fnode:
327 if fnode:
325 cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode)))
328 cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode)))
326 else:
329 else:
327 cachefile.write('%d %s\n' % (rev, hex(head)))
330 cachefile.write('%d %s\n' % (rev, hex(head)))
328
331
329 # Tag names in the cache are in UTF-8 -- which is the whole reason
332 # Tag names in the cache are in UTF-8 -- which is the whole reason
330 # we keep them in UTF-8 throughout this module. If we converted
333 # we keep them in UTF-8 throughout this module. If we converted
331 # them local encoding on input, we would lose info writing them to
334 # them local encoding on input, we would lose info writing them to
332 # the cache.
335 # the cache.
333 cachefile.write('\n')
336 cachefile.write('\n')
334 for (name, (node, hist)) in cachetags.iteritems():
337 for (name, (node, hist)) in cachetags.iteritems():
335 cachefile.write("%s %s\n" % (hex(node), name))
338 cachefile.write("%s %s\n" % (hex(node), name))
336
339
337 cachefile.rename()
340 cachefile.rename()
338 cachefile.close()
341 cachefile.close()
@@ -1,206 +1,214 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cacheexists() {
3 cacheexists() {
4 [ -f .hg/tags.cache ] && echo "tag cache exists" || echo "no tag cache"
4 [ -f .hg/tags.cache ] && echo "tag cache exists" || echo "no tag cache"
5 }
5 }
6
6
7 # XXX need to test that the tag cache works when we strip an old head
7 # XXX need to test that the tag cache works when we strip an old head
8 # and add a new one rooted off non-tip: i.e. node and rev of tip are the
8 # and add a new one rooted off non-tip: i.e. node and rev of tip are the
9 # same, but stuff has changed behind tip.
9 # same, but stuff has changed behind tip.
10
10
11 echo "% setup"
11 echo "% setup"
12 mkdir t
12 mkdir t
13 cd t
13 cd t
14 hg init
14 hg init
15 cacheexists
15 cacheexists
16 hg id
16 hg id
17 cacheexists
17 cacheexists
18 echo a > a
18 echo a > a
19 hg add a
19 hg add a
20 hg commit -m "test"
20 hg commit -m "test"
21 hg co
21 hg co
22 hg identify
22 hg identify
23 cacheexists
23 cacheexists
24
24
25 echo "% create local tag with long name"
25 echo "% create local tag with long name"
26 T=`hg identify --debug --id`
26 T=`hg identify --debug --id`
27 hg tag -l "This is a local tag with a really long name!"
27 hg tag -l "This is a local tag with a really long name!"
28 hg tags
28 hg tags
29 rm .hg/localtags
29 rm .hg/localtags
30
30
31 echo "% create a tag behind hg's back"
31 echo "% create a tag behind hg's back"
32 echo "$T first" > .hgtags
32 echo "$T first" > .hgtags
33 cat .hgtags
33 cat .hgtags
34 hg add .hgtags
34 hg add .hgtags
35 hg commit -m "add tags"
35 hg commit -m "add tags"
36 hg tags
36 hg tags
37 hg identify
37 hg identify
38
38
39 # repeat with cold tag cache
39 # repeat with cold tag cache
40 echo "% identify with cold cache"
40 rm -f .hg/tags.cache
41 rm -f .hg/tags.cache
41 hg identify
42 hg identify
42
43
44 # and again, but now unable to write tag cache
45 echo "% identify with unwritable cache"
46 rm -f .hg/tags.cache
47 chmod 555 .hg
48 hg identify
49 chmod 755 .hg
50
43 echo "% create a branch"
51 echo "% create a branch"
44 echo bb > a
52 echo bb > a
45 hg status
53 hg status
46 hg identify
54 hg identify
47 hg co first
55 hg co first
48 hg id
56 hg id
49 hg -v id
57 hg -v id
50 hg status
58 hg status
51 echo 1 > b
59 echo 1 > b
52 hg add b
60 hg add b
53 hg commit -m "branch"
61 hg commit -m "branch"
54 hg id
62 hg id
55
63
56 echo "% merge the two heads"
64 echo "% merge the two heads"
57 hg merge 1
65 hg merge 1
58 hg id
66 hg id
59 hg status
67 hg status
60
68
61 hg commit -m "merge"
69 hg commit -m "merge"
62
70
63 echo "% create fake head, make sure tag not visible afterwards"
71 echo "% create fake head, make sure tag not visible afterwards"
64 cp .hgtags tags
72 cp .hgtags tags
65 hg tag last
73 hg tag last
66 hg rm .hgtags
74 hg rm .hgtags
67 hg commit -m "remove"
75 hg commit -m "remove"
68
76
69 mv tags .hgtags
77 mv tags .hgtags
70 hg add .hgtags
78 hg add .hgtags
71 hg commit -m "readd"
79 hg commit -m "readd"
72
80
73 hg tags
81 hg tags
74
82
75 echo "% add invalid tags"
83 echo "% add invalid tags"
76 echo "spam" >> .hgtags
84 echo "spam" >> .hgtags
77 echo >> .hgtags
85 echo >> .hgtags
78 echo "foo bar" >> .hgtags
86 echo "foo bar" >> .hgtags
79 echo "$T invalid" | sed "s/..../a5a5/" >> .hg/localtags
87 echo "$T invalid" | sed "s/..../a5a5/" >> .hg/localtags
80 echo "committing .hgtags:"
88 echo "committing .hgtags:"
81 cat .hgtags
89 cat .hgtags
82 hg commit -m "tags"
90 hg commit -m "tags"
83
91
84 echo "% report tag parse error on other head"
92 echo "% report tag parse error on other head"
85 hg up 3
93 hg up 3
86 echo 'x y' >> .hgtags
94 echo 'x y' >> .hgtags
87 hg commit -m "head"
95 hg commit -m "head"
88
96
89 hg tags
97 hg tags
90 hg tip
98 hg tip
91
99
92 echo "% test tag precedence rules"
100 echo "% test tag precedence rules"
93 cd ..
101 cd ..
94 hg init t2
102 hg init t2
95 cd t2
103 cd t2
96 echo foo > foo
104 echo foo > foo
97 hg add foo
105 hg add foo
98 hg ci -m 'add foo' # rev 0
106 hg ci -m 'add foo' # rev 0
99 hg tag bar # rev 1
107 hg tag bar # rev 1
100 echo >> foo
108 echo >> foo
101 hg ci -m 'change foo 1' # rev 2
109 hg ci -m 'change foo 1' # rev 2
102 hg up -C 1
110 hg up -C 1
103 hg tag -r 1 -f bar # rev 3
111 hg tag -r 1 -f bar # rev 3
104 hg up -C 1
112 hg up -C 1
105 echo >> foo
113 echo >> foo
106 hg ci -m 'change foo 2' # rev 4
114 hg ci -m 'change foo 2' # rev 4
107 hg tags
115 hg tags
108 hg tags # repeat in case of cache effects
116 hg tags # repeat in case of cache effects
109
117
110 dumptags() {
118 dumptags() {
111 rev=$1
119 rev=$1
112 echo "rev $rev: .hgtags:"
120 echo "rev $rev: .hgtags:"
113 hg cat -r$rev .hgtags
121 hg cat -r$rev .hgtags
114 }
122 }
115
123
116 echo "% detailed dump of tag info"
124 echo "% detailed dump of tag info"
117 echo "heads:"
125 echo "heads:"
118 hg heads -q # expect 4, 3, 2
126 hg heads -q # expect 4, 3, 2
119 dumptags 2
127 dumptags 2
120 dumptags 3
128 dumptags 3
121 dumptags 4
129 dumptags 4
122 echo ".hg/tags.cache:"
130 echo ".hg/tags.cache:"
123 [ -f .hg/tags.cache ] && cat .hg/tags.cache || echo "no such file"
131 [ -f .hg/tags.cache ] && cat .hg/tags.cache || echo "no such file"
124
132
125 echo "% test tag removal"
133 echo "% test tag removal"
126 hg tag --remove bar # rev 5
134 hg tag --remove bar # rev 5
127 hg tip -vp
135 hg tip -vp
128 hg tags
136 hg tags
129 hg tags # again, try to expose cache bugs
137 hg tags # again, try to expose cache bugs
130
138
131 echo '% remove nonexistent tag'
139 echo '% remove nonexistent tag'
132 hg tag --remove foobar
140 hg tag --remove foobar
133 hg tip
141 hg tip
134
142
135 echo "% rollback undoes tag operation"
143 echo "% rollback undoes tag operation"
136 hg rollback # destroy rev 5 (restore bar)
144 hg rollback # destroy rev 5 (restore bar)
137 hg tags
145 hg tags
138 hg tags
146 hg tags
139
147
140 echo "% test tag rank"
148 echo "% test tag rank"
141 cd ..
149 cd ..
142 hg init t3
150 hg init t3
143 cd t3
151 cd t3
144 echo foo > foo
152 echo foo > foo
145 hg add foo
153 hg add foo
146 hg ci -m 'add foo' # rev 0
154 hg ci -m 'add foo' # rev 0
147 hg tag -f bar # rev 1 bar -> 0
155 hg tag -f bar # rev 1 bar -> 0
148 hg tag -f bar # rev 2 bar -> 1
156 hg tag -f bar # rev 2 bar -> 1
149 hg tag -fr 0 bar # rev 3 bar -> 0
157 hg tag -fr 0 bar # rev 3 bar -> 0
150 hg tag -fr 1 bar # rev 4 bar -> 1
158 hg tag -fr 1 bar # rev 4 bar -> 1
151 hg tag -fr 0 bar # rev 5 bar -> 0
159 hg tag -fr 0 bar # rev 5 bar -> 0
152 hg tags
160 hg tags
153 hg co 3
161 hg co 3
154 echo barbar > foo
162 echo barbar > foo
155 hg ci -m 'change foo' # rev 6
163 hg ci -m 'change foo' # rev 6
156 hg tags
164 hg tags
157
165
158 echo "% don't allow moving tag without -f"
166 echo "% don't allow moving tag without -f"
159 hg tag -r 3 bar
167 hg tag -r 3 bar
160 hg tags
168 hg tags
161
169
162 echo "% strip 1: expose an old head"
170 echo "% strip 1: expose an old head"
163 hg --config extensions.mq= strip 5 > /dev/null 2>&1
171 hg --config extensions.mq= strip 5 > /dev/null 2>&1
164 hg tags # partly stale cache
172 hg tags # partly stale cache
165 hg tags # up-to-date cache
173 hg tags # up-to-date cache
166 echo "% strip 2: destroy whole branch, no old head exposed"
174 echo "% strip 2: destroy whole branch, no old head exposed"
167 hg --config extensions.mq= strip 4 > /dev/null 2>&1
175 hg --config extensions.mq= strip 4 > /dev/null 2>&1
168 hg tags # partly stale
176 hg tags # partly stale
169 rm -f .hg/tags.cache
177 rm -f .hg/tags.cache
170 hg tags # cold cache
178 hg tags # cold cache
171
179
172 echo "% test tag rank with 3 heads"
180 echo "% test tag rank with 3 heads"
173 cd ..
181 cd ..
174 hg init t4
182 hg init t4
175 cd t4
183 cd t4
176 echo foo > foo
184 echo foo > foo
177 hg add
185 hg add
178 hg ci -m 'add foo' # rev 0
186 hg ci -m 'add foo' # rev 0
179 hg tag bar # rev 1 bar -> 0
187 hg tag bar # rev 1 bar -> 0
180 hg tag -f bar # rev 2 bar -> 1
188 hg tag -f bar # rev 2 bar -> 1
181 hg up -qC 0
189 hg up -qC 0
182 hg tag -fr 2 bar # rev 3 bar -> 2
190 hg tag -fr 2 bar # rev 3 bar -> 2
183 hg tags
191 hg tags
184 hg up -qC 0
192 hg up -qC 0
185 hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
193 hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
186 echo "% bar should still point to rev 2"
194 echo "% bar should still point to rev 2"
187 hg tags
195 hg tags
188
196
189
197
190 echo "% remove local as global and global as local"
198 echo "% remove local as global and global as local"
191 # test that removing global/local tags does not get confused when trying
199 # test that removing global/local tags does not get confused when trying
192 # to remove a tag of type X which actually only exists as a type Y
200 # to remove a tag of type X which actually only exists as a type Y
193 cd ..
201 cd ..
194 hg init t5
202 hg init t5
195 cd t5
203 cd t5
196 echo foo > foo
204 echo foo > foo
197 hg add
205 hg add
198 hg ci -m 'add foo' # rev 0
206 hg ci -m 'add foo' # rev 0
199
207
200 hg tag -r 0 -l localtag
208 hg tag -r 0 -l localtag
201 hg tag --remove localtag
209 hg tag --remove localtag
202
210
203 hg tag -r 0 globaltag
211 hg tag -r 0 globaltag
204 hg tag --remove -l globaltag
212 hg tag --remove -l globaltag
205 hg tags -v
213 hg tags -v
206 exit 0
214 exit 0
@@ -1,150 +1,153 b''
1 % setup
1 % setup
2 no tag cache
2 no tag cache
3 000000000000 tip
3 000000000000 tip
4 no tag cache
4 no tag cache
5 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
6 acb14030fe0a tip
6 acb14030fe0a tip
7 tag cache exists
7 tag cache exists
8 % create local tag with long name
8 % create local tag with long name
9 tip 0:acb14030fe0a
9 tip 0:acb14030fe0a
10 This is a local tag with a really long name! 0:acb14030fe0a
10 This is a local tag with a really long name! 0:acb14030fe0a
11 % create a tag behind hg's back
11 % create a tag behind hg's back
12 acb14030fe0a21b60322c440ad2d20cf7685a376 first
12 acb14030fe0a21b60322c440ad2d20cf7685a376 first
13 tip 1:b9154636be93
13 tip 1:b9154636be93
14 first 0:acb14030fe0a
14 first 0:acb14030fe0a
15 b9154636be93 tip
15 b9154636be93 tip
16 % identify with cold cache
17 b9154636be93 tip
18 % identify with unwritable cache
16 b9154636be93 tip
19 b9154636be93 tip
17 % create a branch
20 % create a branch
18 M a
21 M a
19 b9154636be93+ tip
22 b9154636be93+ tip
20 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
23 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
21 acb14030fe0a+ first
24 acb14030fe0a+ first
22 acb14030fe0a+ first
25 acb14030fe0a+ first
23 M a
26 M a
24 created new head
27 created new head
25 c8edf04160c7 tip
28 c8edf04160c7 tip
26 % merge the two heads
29 % merge the two heads
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 (branch merge, don't forget to commit)
31 (branch merge, don't forget to commit)
29 c8edf04160c7+b9154636be93+ tip
32 c8edf04160c7+b9154636be93+ tip
30 M .hgtags
33 M .hgtags
31 % create fake head, make sure tag not visible afterwards
34 % create fake head, make sure tag not visible afterwards
32 tip 6:35ff301afafe
35 tip 6:35ff301afafe
33 first 0:acb14030fe0a
36 first 0:acb14030fe0a
34 % add invalid tags
37 % add invalid tags
35 committing .hgtags:
38 committing .hgtags:
36 acb14030fe0a21b60322c440ad2d20cf7685a376 first
39 acb14030fe0a21b60322c440ad2d20cf7685a376 first
37 spam
40 spam
38
41
39 foo bar
42 foo bar
40 % report tag parse error on other head
43 % report tag parse error on other head
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 created new head
45 created new head
43 .hgtags@75d9f02dfe28, line 2: cannot parse entry
46 .hgtags@75d9f02dfe28, line 2: cannot parse entry
44 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
47 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
45 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
48 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
46 tip 8:c4be69a18c11
49 tip 8:c4be69a18c11
47 first 0:acb14030fe0a
50 first 0:acb14030fe0a
48 changeset: 8:c4be69a18c11
51 changeset: 8:c4be69a18c11
49 tag: tip
52 tag: tip
50 parent: 3:ac5e980c4dc0
53 parent: 3:ac5e980c4dc0
51 user: test
54 user: test
52 date: Thu Jan 01 00:00:00 1970 +0000
55 date: Thu Jan 01 00:00:00 1970 +0000
53 summary: head
56 summary: head
54
57
55 % test tag precedence rules
58 % test tag precedence rules
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 created new head
61 created new head
59 tip 4:0c192d7d5e6b
62 tip 4:0c192d7d5e6b
60 bar 1:78391a272241
63 bar 1:78391a272241
61 tip 4:0c192d7d5e6b
64 tip 4:0c192d7d5e6b
62 bar 1:78391a272241
65 bar 1:78391a272241
63 % detailed dump of tag info
66 % detailed dump of tag info
64 heads:
67 heads:
65 4:0c192d7d5e6b
68 4:0c192d7d5e6b
66 3:6fa450212aeb
69 3:6fa450212aeb
67 2:7a94127795a3
70 2:7a94127795a3
68 rev 2: .hgtags:
71 rev 2: .hgtags:
69 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
72 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
70 rev 3: .hgtags:
73 rev 3: .hgtags:
71 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
74 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
72 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
75 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
73 78391a272241d70354aa14c874552cad6b51bb42 bar
76 78391a272241d70354aa14c874552cad6b51bb42 bar
74 rev 4: .hgtags:
77 rev 4: .hgtags:
75 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
78 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
76 .hg/tags.cache:
79 .hg/tags.cache:
77 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
80 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
78 3 6fa450212aeb2a21ed616a54aea39a4a27894cd7 7d3b718c964ef37b89e550ebdafd5789e76ce1b0
81 3 6fa450212aeb2a21ed616a54aea39a4a27894cd7 7d3b718c964ef37b89e550ebdafd5789e76ce1b0
79 2 7a94127795a33c10a370c93f731fd9fea0b79af6 0c04f2a8af31de17fab7422878ee5a2dadbc943d
82 2 7a94127795a33c10a370c93f731fd9fea0b79af6 0c04f2a8af31de17fab7422878ee5a2dadbc943d
80
83
81 78391a272241d70354aa14c874552cad6b51bb42 bar
84 78391a272241d70354aa14c874552cad6b51bb42 bar
82 % test tag removal
85 % test tag removal
83 changeset: 5:5f6e8655b1c7
86 changeset: 5:5f6e8655b1c7
84 tag: tip
87 tag: tip
85 user: test
88 user: test
86 date: Thu Jan 01 00:00:00 1970 +0000
89 date: Thu Jan 01 00:00:00 1970 +0000
87 files: .hgtags
90 files: .hgtags
88 description:
91 description:
89 Removed tag bar
92 Removed tag bar
90
93
91
94
92 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
95 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
93 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
96 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
94 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
97 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
95 @@ -1,1 +1,3 @@
98 @@ -1,1 +1,3 @@
96 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
99 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
97 +78391a272241d70354aa14c874552cad6b51bb42 bar
100 +78391a272241d70354aa14c874552cad6b51bb42 bar
98 +0000000000000000000000000000000000000000 bar
101 +0000000000000000000000000000000000000000 bar
99
102
100 tip 5:5f6e8655b1c7
103 tip 5:5f6e8655b1c7
101 tip 5:5f6e8655b1c7
104 tip 5:5f6e8655b1c7
102 % remove nonexistent tag
105 % remove nonexistent tag
103 abort: tag 'foobar' does not exist
106 abort: tag 'foobar' does not exist
104 changeset: 5:5f6e8655b1c7
107 changeset: 5:5f6e8655b1c7
105 tag: tip
108 tag: tip
106 user: test
109 user: test
107 date: Thu Jan 01 00:00:00 1970 +0000
110 date: Thu Jan 01 00:00:00 1970 +0000
108 summary: Removed tag bar
111 summary: Removed tag bar
109
112
110 % rollback undoes tag operation
113 % rollback undoes tag operation
111 rolling back last transaction
114 rolling back last transaction
112 tip 4:0c192d7d5e6b
115 tip 4:0c192d7d5e6b
113 bar 1:78391a272241
116 bar 1:78391a272241
114 tip 4:0c192d7d5e6b
117 tip 4:0c192d7d5e6b
115 bar 1:78391a272241
118 bar 1:78391a272241
116 % test tag rank
119 % test tag rank
117 tip 5:85f05169d91d
120 tip 5:85f05169d91d
118 bar 0:bbd179dfa0a7
121 bar 0:bbd179dfa0a7
119 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 created new head
123 created new head
121 tip 6:735c3ca72986
124 tip 6:735c3ca72986
122 bar 0:bbd179dfa0a7
125 bar 0:bbd179dfa0a7
123 % don't allow moving tag without -f
126 % don't allow moving tag without -f
124 abort: tag 'bar' already exists (use -f to force)
127 abort: tag 'bar' already exists (use -f to force)
125 tip 6:735c3ca72986
128 tip 6:735c3ca72986
126 bar 0:bbd179dfa0a7
129 bar 0:bbd179dfa0a7
127 % strip 1: expose an old head
130 % strip 1: expose an old head
128 tip 5:735c3ca72986
131 tip 5:735c3ca72986
129 bar 1:78391a272241
132 bar 1:78391a272241
130 tip 5:735c3ca72986
133 tip 5:735c3ca72986
131 bar 1:78391a272241
134 bar 1:78391a272241
132 % strip 2: destroy whole branch, no old head exposed
135 % strip 2: destroy whole branch, no old head exposed
133 tip 4:735c3ca72986
136 tip 4:735c3ca72986
134 bar 0:bbd179dfa0a7
137 bar 0:bbd179dfa0a7
135 tip 4:735c3ca72986
138 tip 4:735c3ca72986
136 bar 0:bbd179dfa0a7
139 bar 0:bbd179dfa0a7
137 % test tag rank with 3 heads
140 % test tag rank with 3 heads
138 adding foo
141 adding foo
139 tip 3:197c21bbbf2c
142 tip 3:197c21bbbf2c
140 bar 2:6fa450212aeb
143 bar 2:6fa450212aeb
141 % bar should still point to rev 2
144 % bar should still point to rev 2
142 tip 4:3b4b14ed0202
145 tip 4:3b4b14ed0202
143 bar 2:6fa450212aeb
146 bar 2:6fa450212aeb
144 % remove local as global and global as local
147 % remove local as global and global as local
145 adding foo
148 adding foo
146 abort: tag 'localtag' is not a global tag
149 abort: tag 'localtag' is not a global tag
147 abort: tag 'globaltag' is not a local tag
150 abort: tag 'globaltag' is not a local tag
148 tip 1:a0b6fe111088
151 tip 1:a0b6fe111088
149 localtag 0:bbd179dfa0a7 local
152 localtag 0:bbd179dfa0a7 local
150 globaltag 0:bbd179dfa0a7
153 globaltag 0:bbd179dfa0a7
General Comments 0
You need to be logged in to leave comments. Login now