##// END OF EJS Templates
Merge with hg
Brendan Cully -
r9167:b67adc2d merge default
parent child Browse files
Show More
@@ -0,0 +1,339 b''
1 # tags.py - read tag info from local repository
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com>
4 # Copyright 2009 Greg Ward <greg@gerg.ca>
5 #
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.
8
9 # Currently this module only deals with reading and caching tags.
10 # Eventually, it could take care of updating (adding/removing/moving)
11 # tags too.
12
13 import os
14 from node import nullid, bin, hex, short
15 from i18n import _
16 import encoding
17 import error
18
19 def _debugalways(ui, *msg):
20 ui.write(*msg)
21
22 def _debugconditional(ui, *msg):
23 ui.debug(*msg)
24
25 def _debugnever(ui, *msg):
26 pass
27
28 _debug = _debugalways
29 _debug = _debugnever
30
31 def findglobaltags1(ui, repo, alltags, tagtypes):
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
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
36 case).'''
37
38 seen = set()
39 fctx = None
40 ctxs = [] # list of filectx
41 for node in repo.heads():
42 try:
43 fnode = repo[node].filenode('.hgtags')
44 except error.LookupError:
45 continue
46 if fnode not in seen:
47 seen.add(fnode)
48 if not fctx:
49 fctx = repo.filectx('.hgtags', fileid=fnode)
50 else:
51 fctx = fctx.filectx(fnode)
52 ctxs.append(fctx)
53
54 # read the tags file from each head, ending with the tip
55 for fctx in reversed(ctxs):
56 filetags = _readtags(
57 ui, repo, fctx.data().splitlines(), fctx)
58 _updatetags(filetags, "global", alltags, tagtypes)
59
60 def findglobaltags2(ui, repo, alltags, tagtypes):
61 '''Same as findglobaltags1(), but with caching.'''
62 # This is so we can be lazy and assume alltags contains only global
63 # tags when we pass it to _writetagcache().
64 assert len(alltags) == len(tagtypes) == 0, \
65 "findglobaltags() should be called first"
66
67 (heads, tagfnode, cachetags, shouldwrite) = _readtagcache(ui, repo)
68 if cachetags is not None:
69 assert not shouldwrite
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,
72 # because cachetags does not contain rank info?
73 _updatetags(cachetags, 'global', alltags, tagtypes)
74 return
75
76 _debug(ui, "reading tags from %d head(s): %s\n"
77 % (len(heads), map(short, reversed(heads))))
78 seen = set() # set of fnode
79 fctx = None
80 for head in reversed(heads): # oldest to newest
81 assert head in repo.changelog.nodemap, \
82 "tag cache returned bogus head %s" % short(head)
83
84 fnode = tagfnode.get(head)
85 if fnode and fnode not in seen:
86 seen.add(fnode)
87 if not fctx:
88 fctx = repo.filectx('.hgtags', fileid=fnode)
89 else:
90 fctx = fctx.filectx(fnode)
91
92 filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
93 _updatetags(filetags, 'global', alltags, tagtypes)
94
95 # and update the cache (if necessary)
96 if shouldwrite:
97 _writetagcache(ui, repo, heads, tagfnode, alltags)
98
99 # Set this to findglobaltags1 to disable tag caching.
100 findglobaltags = findglobaltags2
101
102 def readlocaltags(ui, repo, alltags, tagtypes):
103 '''Read local tags in repo. Update alltags and tagtypes.'''
104 try:
105 # localtags is in the local encoding; re-encode to UTF-8 on
106 # input for consistency with the rest of this module.
107 data = repo.opener("localtags").read()
108 filetags = _readtags(
109 ui, repo, data.splitlines(), "localtags",
110 recode=encoding.fromlocal)
111 _updatetags(filetags, "local", alltags, tagtypes)
112 except IOError:
113 pass
114
115 def _readtags(ui, repo, lines, fn, recode=None):
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
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
120 binary, not hex.'''
121
122 filetags = {} # map tag name to (node, hist)
123 count = 0
124
125 def warn(msg):
126 ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
127
128 for line in lines:
129 count += 1
130 if not line:
131 continue
132 try:
133 (nodehex, name) = line.split(" ", 1)
134 except ValueError:
135 warn(_("cannot parse entry"))
136 continue
137 name = name.strip()
138 if recode:
139 name = recode(name)
140 try:
141 nodebin = bin(nodehex)
142 except TypeError:
143 warn(_("node '%s' is not well formed") % nodehex)
144 continue
145 if nodebin not in repo.changelog.nodemap:
146 # silently ignore as pull -r might cause this
147 continue
148
149 # update filetags
150 hist = []
151 if name in filetags:
152 n, hist = filetags[name]
153 hist.append(n)
154 filetags[name] = (nodebin, hist)
155 return filetags
156
157 def _updatetags(filetags, tagtype, alltags, tagtypes):
158 '''Incorporate the tag info read from one file into the two
159 dictionaries, alltags and tagtypes, that contain all tag
160 info (global across all heads plus local).'''
161
162 for name, nodehist in filetags.iteritems():
163 if name not in alltags:
164 alltags[name] = nodehist
165 tagtypes[name] = tagtype
166 continue
167
168 # we prefer alltags[name] if:
169 # it supercedes us OR
170 # mutual supercedes and it has a higher rank
171 # otherwise we win because we're tip-most
172 anode, ahist = nodehist
173 bnode, bhist = alltags[name]
174 if (bnode != anode and anode in bhist and
175 (bnode not in ahist or len(bhist) > len(ahist))):
176 anode = bnode
177 ahist.extend([n for n in bhist if n not in ahist])
178 alltags[name] = anode, ahist
179 tagtypes[name] = tagtype
180
181
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
184 # performance, but is simpler has a better chance of actually
185 # working correctly. And this gives the biggest performance win: 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
188 # the repo.
189
190 def _readtagcache(ui, repo):
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
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
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
197 set, caller is responsible for reading tag info from each head.'''
198
199 try:
200 cachefile = repo.opener('tags.cache', 'r')
201 _debug(ui, 'reading tag cache from %s\n' % cachefile.name)
202 except IOError:
203 cachefile = None
204
205 # The cache file consists of lines like
206 # <headrev> <headnode> [<tagnode>]
207 # where <headrev> and <headnode> redundantly identify a repository
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
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
212 # that's required to ensure correct order).
213 #
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
216 # manifest for each head.
217 cacherevs = [] # list of headrev
218 cacheheads = [] # list of headnode
219 cachefnode = {} # map headnode to filenode
220 if cachefile:
221 for line in cachefile:
222 if line == "\n":
223 break
224 line = line.rstrip().split()
225 cacherevs.append(int(line[0]))
226 headnode = bin(line[1])
227 cacheheads.append(headnode)
228 if len(line) == 3:
229 fnode = bin(line[2])
230 cachefnode[headnode] = fnode
231
232 tipnode = repo.changelog.tip()
233 tiprev = len(repo.changelog) - 1
234
235 # Case 1 (common): tip is the same, so nothing has changed.
236 # (Unchanged tip trivially means no changesets have been added.
237 # But, thanks to localrepository.destroyed(), it also means none
238 # have been destroyed by strip or rollback.)
239 if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev:
240 _debug(ui, "tag cache: tip unchanged\n")
241 tags = _readtags(ui, repo, cachefile, cachefile.name)
242 cachefile.close()
243 return (None, None, tags, False)
244 if cachefile:
245 cachefile.close() # ignore rest of file
246
247 repoheads = repo.heads()
248
249 # Case 2 (uncommon): empty repo; get out quickly and don't bother
250 # writing an empty cache.
251 if repoheads == [nullid]:
252 return ([], {}, {}, False)
253
254 # Case 3 (uncommon): cache file missing or empty.
255 if not cacheheads:
256 _debug(ui, 'tag cache: cache file missing or empty\n')
257
258 # Case 4 (uncommon): tip rev decreased. This should only happen
259 # when we're called from localrepository.destroyed(). Refresh the
260 # cache so future invocations will not see disappeared heads in the
261 # cache.
262 elif cacheheads and tiprev < cacherevs[0]:
263 _debug(ui,
264 'tag cache: tip rev decremented (from %d to %d), '
265 'so we must be destroying nodes\n'
266 % (cacherevs[0], tiprev))
267
268 # Case 5 (common): tip has changed, so we've added/replaced heads.
269 else:
270 _debug(ui,
271 'tag cache: tip has changed (%d:%s); must find new heads\n'
272 % (tiprev, short(tipnode)))
273
274 # Luckily, the code to handle cases 3, 4, 5 is the same. So the
275 # above if/elif/else can disappear once we're confident this thing
276 # actually works and we don't need the debug output.
277
278 # N.B. in case 4 (nodes destroyed), "new head" really means "newly
279 # exposed".
280 newheads = [head
281 for head in repoheads
282 if head not in set(cacheheads)]
283 _debug(ui, 'tag cache: found %d head(s) not in cache: %s\n'
284 % (len(newheads), map(short, newheads)))
285
286 # Now we have to lookup the .hgtags filenode for every new head.
287 # This is the most expensive part of finding tags, so performance
288 # depends primarily on the size of newheads. Worst case: no cache
289 # file, so newheads == repoheads.
290 for head in newheads:
291 cctx = repo[head]
292 try:
293 fnode = cctx.filenode('.hgtags')
294 cachefnode[head] = fnode
295 except error.LookupError:
296 # no .hgtags file on this head
297 pass
298
299 # Caller has to iterate over all heads, but can use the filenodes in
300 # cachefnode to get to each .hgtags revision quickly.
301 return (repoheads, cachefnode, None, True)
302
303 def _writetagcache(ui, repo, heads, tagfnode, cachetags):
304
305 cachefile = repo.opener('tags.cache', 'w', atomictemp=True)
306 _debug(ui, 'writing cache file %s\n' % cachefile.name)
307
308 realheads = repo.heads() # for sanity checks below
309 for head in heads:
310 # temporary sanity checks; these can probably be removed
311 # once this code has been in crew for a few weeks
312 assert head in repo.changelog.nodemap, \
313 'trying to write non-existent node %s to tag cache' % short(head)
314 assert head in realheads, \
315 'trying to write non-head %s to tag cache' % short(head)
316 assert head != nullid, \
317 'trying to write nullid to tag cache'
318
319 # This can't fail because of the first assert above. When/if we
320 # remove that assert, we might want to catch LookupError here
321 # and downgrade it to a warning.
322 rev = repo.changelog.rev(head)
323
324 fnode = tagfnode.get(head)
325 if fnode:
326 cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode)))
327 else:
328 cachefile.write('%d %s\n' % (rev, hex(head)))
329
330 # Tag names in the cache are in UTF-8 -- which is the whole reason
331 # we keep them in UTF-8 throughout this module. If we converted
332 # them local encoding on input, we would lose info writing them to
333 # the cache.
334 cachefile.write('\n')
335 for (name, (node, hist)) in cachetags.iteritems():
336 cachefile.write("%s %s\n" % (hex(node), name))
337
338 cachefile.rename()
339 cachefile.close()
@@ -51,7 +51,7 b' def perftags(ui, repo):'
51 def t():
51 def t():
52 repo.changelog = mercurial.changelog.changelog(repo.sopener)
52 repo.changelog = mercurial.changelog.changelog(repo.sopener)
53 repo.manifest = mercurial.manifest.manifest(repo.sopener)
53 repo.manifest = mercurial.manifest.manifest(repo.sopener)
54 repo.tagscache = None
54 repo._tags = None
55 return len(repo.tags())
55 return len(repo.tags())
56 timer(t)
56 timer(t)
57
57
@@ -293,14 +293,11 b' def reposetup(ui, repo):'
293 write(self, marks)
293 write(self, marks)
294 return result
294 return result
295
295
296 def tags(self):
296 def _findtags(self):
297 """Merge bookmarks with normal tags"""
297 """Merge bookmarks with normal tags"""
298 if self.tagscache:
298 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
299 return self.tagscache
299 tags.update(parse(self))
300
300 return (tags, tagtypes)
301 tagscache = super(bookmark_repo, self).tags()
302 tagscache.update(parse(self))
303 return tagscache
304
301
305 repo.__class__ = bookmark_repo
302 repo.__class__ = bookmark_repo
306
303
@@ -2415,34 +2415,33 b' def reposetup(ui, repo):'
2415 raise util.Abort(_('source has mq patches applied'))
2415 raise util.Abort(_('source has mq patches applied'))
2416 return super(mqrepo, self).push(remote, force, revs)
2416 return super(mqrepo, self).push(remote, force, revs)
2417
2417
2418 def tags(self):
2418 def _findtags(self):
2419 if self.tagscache:
2419 '''augment tags from base class with patch tags'''
2420 return self.tagscache
2420 result = super(mqrepo, self)._findtags()
2421
2422 tagscache = super(mqrepo, self).tags()
2423
2421
2424 q = self.mq
2422 q = self.mq
2425 if not q.applied:
2423 if not q.applied:
2426 return tagscache
2424 return result
2427
2425
2428 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2426 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2429
2427
2430 if mqtags[-1][0] not in self.changelog.nodemap:
2428 if mqtags[-1][0] not in self.changelog.nodemap:
2431 self.ui.warn(_('mq status file refers to unknown node %s\n')
2429 self.ui.warn(_('mq status file refers to unknown node %s\n')
2432 % short(mqtags[-1][0]))
2430 % short(mqtags[-1][0]))
2433 return tagscache
2431 return result
2434
2432
2435 mqtags.append((mqtags[-1][0], 'qtip'))
2433 mqtags.append((mqtags[-1][0], 'qtip'))
2436 mqtags.append((mqtags[0][0], 'qbase'))
2434 mqtags.append((mqtags[0][0], 'qbase'))
2437 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2435 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2436 tags = result[0]
2438 for patch in mqtags:
2437 for patch in mqtags:
2439 if patch[1] in tagscache:
2438 if patch[1] in tags:
2440 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2439 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2441 % patch[1])
2440 % patch[1])
2442 else:
2441 else:
2443 tagscache[patch[1]] = patch[0]
2442 tags[patch[1]] = patch[0]
2444
2443
2445 return tagscache
2444 return result
2446
2445
2447 def _branchtags(self, partial, lrev):
2446 def _branchtags(self, partial, lrev):
2448 q = self.mq
2447 q = self.mq
@@ -49,6 +49,9 b' def decode(arg):'
49 return tuple(map(decode, arg))
49 return tuple(map(decode, arg))
50 elif isinstance(arg, list):
50 elif isinstance(arg, list):
51 return map(decode, arg)
51 return map(decode, arg)
52 elif isinstance(arg, dict):
53 for k, v in arg.items():
54 arg[k] = decode(v)
52 return arg
55 return arg
53
56
54 def encode(arg):
57 def encode(arg):
@@ -58,29 +61,50 b' def encode(arg):'
58 return tuple(map(encode, arg))
61 return tuple(map(encode, arg))
59 elif isinstance(arg, list):
62 elif isinstance(arg, list):
60 return map(encode, arg)
63 return map(encode, arg)
64 elif isinstance(arg, dict):
65 for k, v in arg.items():
66 arg[k] = encode(v)
61 return arg
67 return arg
62
68
63 def wrapper(func, args):
69 def appendsep(s):
70 # ensure the path ends with os.sep, appending it if necessary.
71 try:
72 us = decode(s)
73 except UnicodeError:
74 us = s
75 if us and us[-1] not in ':/\\':
76 s += os.sep
77 return s
78
79 def wrapper(func, args, kwds):
64 # check argument is unicode, then call original
80 # check argument is unicode, then call original
65 for arg in args:
81 for arg in args:
66 if isinstance(arg, unicode):
82 if isinstance(arg, unicode):
67 return func(*args)
83 return func(*args, **kwds)
68
84
69 try:
85 try:
70 # convert arguments to unicode, call func, then convert back
86 # convert arguments to unicode, call func, then convert back
71 return encode(func(*decode(args)))
87 return encode(func(*decode(args), **decode(kwds)))
72 except UnicodeError:
88 except UnicodeError:
73 # If not encoded with encoding.encoding, report it then
89 raise util.Abort(_("[win32mbcs] filename conversion failed with"
74 # continue with calling original function.
75 raise util.Abort(_("[win32mbcs] filename conversion fail with"
76 " %s encoding\n") % (encoding.encoding))
90 " %s encoding\n") % (encoding.encoding))
77
91
78 def wrapname(name):
92 def wrapperforlistdir(func, args, kwds):
93 # Ensure 'path' argument ends with os.sep to avoids
94 # misinterpreting last 0x5c of MBCS 2nd byte as path separator.
95 if args:
96 args = list(args)
97 args[0] = appendsep(args[0])
98 if kwds.has_key('path'):
99 kwds['path'] = appendsep(kwds['path'])
100 return func(*args, **kwds)
101
102 def wrapname(name, wrapper):
79 module, name = name.rsplit('.', 1)
103 module, name = name.rsplit('.', 1)
80 module = sys.modules[module]
104 module = sys.modules[module]
81 func = getattr(module, name)
105 func = getattr(module, name)
82 def f(*args):
106 def f(*args, **kwds):
83 return wrapper(func, args)
107 return wrapper(func, args, kwds)
84 try:
108 try:
85 f.__name__ = func.__name__ # fail with python23
109 f.__name__ = func.__name__ # fail with python23
86 except Exception:
110 except Exception:
@@ -110,7 +134,8 b' def reposetup(ui, repo):'
110 # fake is only for relevant environment.
134 # fake is only for relevant environment.
111 if encoding.encoding.lower() in problematic_encodings.split():
135 if encoding.encoding.lower() in problematic_encodings.split():
112 for f in funcs.split():
136 for f in funcs.split():
113 wrapname(f)
137 wrapname(f, wrapper)
138 wrapname("mercurial.osutil.listdir", wrapperforlistdir)
114 ui.debug(_("[win32mbcs] activated with encoding: %s\n")
139 ui.debug(_("[win32mbcs] activated with encoding: %s\n")
115 % encoding.encoding)
140 % encoding.encoding)
116
141
@@ -13,6 +13,7 b' import lock, transaction, store, encodin'
13 import util, extensions, hook, error
13 import util, extensions, hook, error
14 import match as match_
14 import match as match_
15 import merge as merge_
15 import merge as merge_
16 import tags as tags_
16 from lock import release
17 from lock import release
17 import weakref, stat, errno, os, time, inspect
18 import weakref, stat, errno, os, time, inspect
18 propertycache = util.propertycache
19 propertycache = util.propertycache
@@ -89,8 +90,14 b' class localrepository(repo.repository):'
89 self.sjoin = self.store.join
90 self.sjoin = self.store.join
90 self.opener.createmode = self.store.createmode
91 self.opener.createmode = self.store.createmode
91
92
92 self.tagscache = None
93 # These two define the set of tags for this repository. _tags
93 self._tagstypecache = None
94 # maps tag name to node; _tagtypes maps tag name to 'global' or
95 # 'local'. (Global tags are defined by .hgtags across all
96 # heads, and local tags are defined in .hg/localtags.) They
97 # constitute the in-memory cache of tags.
98 self._tags = None
99 self._tagtypes = None
100
94 self.branchcache = None
101 self.branchcache = None
95 self._ubranchcache = None # UTF-8 version of branchcache
102 self._ubranchcache = None # UTF-8 version of branchcache
96 self._branchcachetip = None
103 self._branchcachetip = None
@@ -160,8 +167,8 b' class localrepository(repo.repository):'
160 fp.write('\n')
167 fp.write('\n')
161 for name in names:
168 for name in names:
162 m = munge and munge(name) or name
169 m = munge and munge(name) or name
163 if self._tagstypecache and name in self._tagstypecache:
170 if self._tagtypes and name in self._tagtypes:
164 old = self.tagscache.get(name, nullid)
171 old = self._tags.get(name, nullid)
165 fp.write('%s %s\n' % (hex(old), m))
172 fp.write('%s %s\n' % (hex(old), m))
166 fp.write('%s %s\n' % (hex(node), m))
173 fp.write('%s %s\n' % (hex(node), m))
167 fp.close()
174 fp.close()
@@ -233,100 +240,43 b' class localrepository(repo.repository):'
233
240
234 def tags(self):
241 def tags(self):
235 '''return a mapping of tag to node'''
242 '''return a mapping of tag to node'''
236 if self.tagscache:
243 if self._tags is None:
237 return self.tagscache
244 (self._tags, self._tagtypes) = self._findtags()
245
246 return self._tags
238
247
239 globaltags = {}
248 def _findtags(self):
249 '''Do the hard work of finding tags. Return a pair of dicts
250 (tags, tagtypes) where tags maps tag name to node, and tagtypes
251 maps tag name to a string like \'global\' or \'local\'.
252 Subclasses or extensions are free to add their own tags, but
253 should be aware that the returned dicts will be retained for the
254 duration of the localrepo object.'''
255
256 # XXX what tagtype should subclasses/extensions use? Currently
257 # mq and bookmarks add tags, but do not set the tagtype at all.
258 # Should each extension invent its own tag type? Should there
259 # be one tagtype for all such "virtual" tags? Or is the status
260 # quo fine?
261
262 alltags = {} # map tag name to (node, hist)
240 tagtypes = {}
263 tagtypes = {}
241
264
242 def readtags(lines, fn, tagtype):
265 tags_.findglobaltags(self.ui, self, alltags, tagtypes)
243 filetags = {}
266 tags_.readlocaltags(self.ui, self, alltags, tagtypes)
244 count = 0
245
246 def warn(msg):
247 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
248
249 for l in lines:
250 count += 1
251 if not l:
252 continue
253 s = l.split(" ", 1)
254 if len(s) != 2:
255 warn(_("cannot parse entry"))
256 continue
257 node, key = s
258 key = encoding.tolocal(key.strip()) # stored in UTF-8
259 try:
260 bin_n = bin(node)
261 except TypeError:
262 warn(_("node '%s' is not well formed") % node)
263 continue
264 if bin_n not in self.changelog.nodemap:
265 # silently ignore as pull -r might cause this
266 continue
267
268 h = []
269 if key in filetags:
270 n, h = filetags[key]
271 h.append(n)
272 filetags[key] = (bin_n, h)
273
274 for k, nh in filetags.iteritems():
275 if k not in globaltags:
276 globaltags[k] = nh
277 tagtypes[k] = tagtype
278 continue
279
267
280 # we prefer the global tag if:
268 # Build the return dicts. Have to re-encode tag names because
281 # it supercedes us OR
269 # the tags module always uses UTF-8 (in order not to lose info
282 # mutual supercedes and it has a higher rank
270 # writing to the cache), but the rest of Mercurial wants them in
283 # otherwise we win because we're tip-most
271 # local encoding.
284 an, ah = nh
272 tags = {}
285 bn, bh = globaltags[k]
273 for (name, (node, hist)) in alltags.iteritems():
286 if (bn != an and an in bh and
274 if node != nullid:
287 (bn not in ah or len(bh) > len(ah))):
275 tags[encoding.tolocal(name)] = node
288 an = bn
276 tags['tip'] = self.changelog.tip()
289 ah.extend([n for n in bh if n not in ah])
277 tagtypes = dict([(encoding.tolocal(name), value)
290 globaltags[k] = an, ah
278 for (name, value) in tagtypes.iteritems()])
291 tagtypes[k] = tagtype
279 return (tags, tagtypes)
292
293 seen = set()
294 f = None
295 ctxs = []
296 for node in self.heads():
297 try:
298 fnode = self[node].filenode('.hgtags')
299 except error.LookupError:
300 continue
301 if fnode not in seen:
302 seen.add(fnode)
303 if not f:
304 f = self.filectx('.hgtags', fileid=fnode)
305 else:
306 f = f.filectx(fnode)
307 ctxs.append(f)
308
309 # read the tags file from each head, ending with the tip
310 for f in reversed(ctxs):
311 readtags(f.data().splitlines(), f, "global")
312
313 try:
314 data = encoding.fromlocal(self.opener("localtags").read())
315 # localtags are stored in the local character set
316 # while the internal tag table is stored in UTF-8
317 readtags(data.splitlines(), "localtags", "local")
318 except IOError:
319 pass
320
321 self.tagscache = {}
322 self._tagstypecache = {}
323 for k, nh in globaltags.iteritems():
324 n = nh[0]
325 if n != nullid:
326 self.tagscache[k] = n
327 self._tagstypecache[k] = tagtypes[k]
328 self.tagscache['tip'] = self.changelog.tip()
329 return self.tagscache
330
280
331 def tagtype(self, tagname):
281 def tagtype(self, tagname):
332 '''
282 '''
@@ -339,7 +289,7 b' class localrepository(repo.repository):'
339
289
340 self.tags()
290 self.tags()
341
291
342 return self._tagstypecache.get(tagname)
292 return self._tagtypes.get(tagname)
343
293
344 def tagslist(self):
294 def tagslist(self):
345 '''return a list of tags ordered by revision'''
295 '''return a list of tags ordered by revision'''
@@ -668,6 +618,7 b' class localrepository(repo.repository):'
668 % encoding.tolocal(self.dirstate.branch()))
618 % encoding.tolocal(self.dirstate.branch()))
669 self.invalidate()
619 self.invalidate()
670 self.dirstate.invalidate()
620 self.dirstate.invalidate()
621 self.destroyed()
671 else:
622 else:
672 self.ui.warn(_("no rollback information available\n"))
623 self.ui.warn(_("no rollback information available\n"))
673 finally:
624 finally:
@@ -677,8 +628,8 b' class localrepository(repo.repository):'
677 for a in "changelog manifest".split():
628 for a in "changelog manifest".split():
678 if a in self.__dict__:
629 if a in self.__dict__:
679 delattr(self, a)
630 delattr(self, a)
680 self.tagscache = None
631 self._tags = None
681 self._tagstypecache = None
632 self._tagtypes = None
682 self.nodetagscache = None
633 self.nodetagscache = None
683 self.branchcache = None
634 self.branchcache = None
684 self._ubranchcache = None
635 self._ubranchcache = None
@@ -966,6 +917,25 b' class localrepository(repo.repository):'
966 del tr
917 del tr
967 lock.release()
918 lock.release()
968
919
920 def destroyed(self):
921 '''Inform the repository that nodes have been destroyed.
922 Intended for use by strip and rollback, so there's a common
923 place for anything that has to be done after destroying history.'''
924 # XXX it might be nice if we could take the list of destroyed
925 # nodes, but I don't see an easy way for rollback() to do that
926
927 # Ensure the persistent tag cache is updated. Doing it now
928 # means that the tag cache only has to worry about destroyed
929 # heads immediately after a strip/rollback. That in turn
930 # guarantees that "cachetip == currenttip" (comparing both rev
931 # and node) always means no nodes have been added or destroyed.
932
933 # XXX this is suboptimal when qrefresh'ing: we strip the current
934 # head, refresh the tag cache, then immediately add a new head.
935 # But I think doing it this way is necessary for the "instant
936 # tag cache retrieval" case to work.
937 tags_.findglobaltags(self.ui, self, {}, {})
938
969 def walk(self, match, node=None):
939 def walk(self, match, node=None):
970 '''
940 '''
971 walk recursively through the directory tree or a given
941 walk recursively through the directory tree or a given
@@ -142,3 +142,4 b' def strip(ui, repo, node, backup="all"):'
142 if backup != "strip":
142 if backup != "strip":
143 os.unlink(chgrpfile)
143 os.unlink(chgrpfile)
144
144
145 repo.destroyed()
@@ -114,7 +114,7 b' class statichttprepository(localrepo.loc'
114
114
115 self.manifest = manifest.manifest(self.sopener)
115 self.manifest = manifest.manifest(self.sopener)
116 self.changelog = changelog.changelog(self.sopener)
116 self.changelog = changelog.changelog(self.sopener)
117 self.tagscache = None
117 self._tags = None
118 self.nodetagscache = None
118 self.nodetagscache = None
119 self.encodepats = None
119 self.encodepats = None
120 self.decodepats = None
120 self.decodepats = None
@@ -284,16 +284,17 b' class fncachestore(basicstore):'
284 self.pathjoiner = pathjoiner
284 self.pathjoiner = pathjoiner
285 self.path = self.pathjoiner(path, 'store')
285 self.path = self.pathjoiner(path, 'store')
286 self.createmode = _calcmode(self.path)
286 self.createmode = _calcmode(self.path)
287 self._op = opener(self.path)
287 op = opener(self.path)
288 self._op.createmode = self.createmode
288 op.createmode = self.createmode
289 self.fncache = fncache(self._op)
289 fnc = fncache(op)
290 self.fncache = fnc
290
291
291 def fncacheopener(path, mode='r', *args, **kw):
292 def fncacheopener(path, mode='r', *args, **kw):
292 if (mode not in ('r', 'rb')
293 if (mode not in ('r', 'rb')
293 and path.startswith('data/')
294 and path.startswith('data/')
294 and path not in self.fncache):
295 and path not in fnc):
295 self.fncache.add(path)
296 fnc.add(path)
296 return self._op(hybridencode(path), mode, *args, **kw)
297 return op(hybridencode(path), mode, *args, **kw)
297 self.opener = fncacheopener
298 self.opener = fncacheopener
298
299
299 def join(self, f):
300 def join(self, f):
@@ -349,3 +349,33 b' class ui(object):'
349 self.config("ui", "editor") or
349 self.config("ui", "editor") or
350 os.environ.get("VISUAL") or
350 os.environ.get("VISUAL") or
351 os.environ.get("EDITOR", "vi"))
351 os.environ.get("EDITOR", "vi"))
352
353 def progress(self, topic, pos, item="", unit="", total=None):
354 '''show a progress message
355
356 With stock hg, this is simply a debug message that is hidden
357 by default, but with extensions or GUI tools it may be
358 visible. 'topic' is the current operation, 'item' is a
359 non-numeric marker of the current position (ie the currently
360 in-process file), 'pos' is the current numeric position (ie
361 revision, bytes, etc.), units is a corresponding unit label,
362 and total is the highest expected pos.
363
364 Multiple nested topics may be active at a time. All topics
365 should be marked closed by setting pos to None at termination.
366 '''
367
368 if pos == None or not self.debugflag:
369 return
370
371 if units:
372 units = ' ' + units
373 if item:
374 item = ' ' + item
375
376 if total:
377 pct = 100.0 * pos / total
378 ui.debug('%s:%s %s/%s%s (%4.2g%%)\n'
379 % (topic, item, pos, total, units, pct))
380 else:
381 ui.debug('%s:%s %s%s\n' % (topic, item, pos, units))
@@ -107,9 +107,18 b' echo % qpop'
107 hg qpop
107 hg qpop
108 checkundo qpop
108 checkundo qpop
109
109
110 echo % qpush
110 echo % qpush with dump of tag cache
111
111
112 # Dump the tag cache to ensure that it has exactly one head after qpush.
113 rm -f .hg/tags.cache
114 hg tags > /dev/null
115 echo ".hg/tags.cache (pre qpush):"
116 sed 's/ [0-9a-f]*//' .hg/tags.cache
112 hg qpush
117 hg qpush
118 hg tags > /dev/null
119 echo ".hg/tags.cache (post qpush):"
120 sed 's/ [0-9a-f]*//' .hg/tags.cache
121
113 checkundo qpush
122 checkundo qpush
114
123
115 cd ..
124 cd ..
@@ -110,9 +110,15 b' working dir diff:'
110 % qpop
110 % qpop
111 popping test.patch
111 popping test.patch
112 patch queue now empty
112 patch queue now empty
113 % qpush
113 % qpush with dump of tag cache
114 .hg/tags.cache (pre qpush):
115 1
116
114 applying test.patch
117 applying test.patch
115 now at: test.patch
118 now at: test.patch
119 .hg/tags.cache (post qpush):
120 2
121
116 % pop/push outside repo
122 % pop/push outside repo
117 popping test.patch
123 popping test.patch
118 patch queue now empty
124 patch queue now empty
@@ -1,24 +1,46 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cacheexists() {
4 [ -f .hg/tags.cache ] && echo "tag cache exists" || echo "no tag cache"
5 }
6
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
9 # same, but stuff has changed behind tip.
10
11 echo "% setup"
3 mkdir t
12 mkdir t
4 cd t
13 cd t
5 hg init
14 hg init
15 cacheexists
6 hg id
16 hg id
17 cacheexists
7 echo a > a
18 echo a > a
8 hg add a
19 hg add a
9 hg commit -m "test" -d "1000000 0"
20 hg commit -m "test"
10 hg co
21 hg co
11 hg identify
22 hg identify
12 T=`hg tip --debug | head -n 1 | cut -d : -f 3`
23 cacheexists
24
25 echo "% create local tag with long name"
26 T=`hg identify --debug --id`
13 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!"
14 hg tags
28 hg tags
15 rm .hg/localtags
29 rm .hg/localtags
30
31 echo "% create a tag behind hg's back"
16 echo "$T first" > .hgtags
32 echo "$T first" > .hgtags
17 cat .hgtags
33 cat .hgtags
18 hg add .hgtags
34 hg add .hgtags
19 hg commit -m "add tags" -d "1000000 0"
35 hg commit -m "add tags"
20 hg tags
36 hg tags
21 hg identify
37 hg identify
38
39 # repeat with cold tag cache
40 rm -f .hg/tags.cache
41 hg identify
42
43 echo "% create a branch"
22 echo bb > a
44 echo bb > a
23 hg status
45 hg status
24 hg identify
46 hg identify
@@ -28,89 +50,126 b' hg -v id'
28 hg status
50 hg status
29 echo 1 > b
51 echo 1 > b
30 hg add b
52 hg add b
31 hg commit -m "branch" -d "1000000 0"
53 hg commit -m "branch"
32 hg id
54 hg id
55
56 echo "% merge the two heads"
33 hg merge 1
57 hg merge 1
34 hg id
58 hg id
35 hg status
59 hg status
36
60
37 hg commit -m "merge" -d "1000000 0"
61 hg commit -m "merge"
38
62
39 # create fake head, make sure tag not visible afterwards
63 echo "% create fake head, make sure tag not visible afterwards"
40 cp .hgtags tags
64 cp .hgtags tags
41 hg tag -d "1000000 0" last
65 hg tag last
42 hg rm .hgtags
66 hg rm .hgtags
43 hg commit -m "remove" -d "1000000 0"
67 hg commit -m "remove"
44
68
45 mv tags .hgtags
69 mv tags .hgtags
46 hg add .hgtags
70 hg add .hgtags
47 hg commit -m "readd" -d "1000000 0"
71 hg commit -m "readd"
48
72
49 hg tags
73 hg tags
50
74
51 # invalid tags
75 echo "% add invalid tags"
52 echo "spam" >> .hgtags
76 echo "spam" >> .hgtags
53 echo >> .hgtags
77 echo >> .hgtags
54 echo "foo bar" >> .hgtags
78 echo "foo bar" >> .hgtags
55 echo "$T invalid" | sed "s/..../a5a5/" >> .hg/localtags
79 echo "$T invalid" | sed "s/..../a5a5/" >> .hg/localtags
56 hg commit -m "tags" -d "1000000 0"
80 echo "committing .hgtags:"
81 cat .hgtags
82 hg commit -m "tags"
57
83
58 # report tag parse error on other head
84 echo "% report tag parse error on other head"
59 hg up 3
85 hg up 3
60 echo 'x y' >> .hgtags
86 echo 'x y' >> .hgtags
61 hg commit -m "head" -d "1000000 0"
87 hg commit -m "head"
62
88
63 hg tags
89 hg tags
64 hg tip
90 hg tip
65
91
66 # test tag precedence rules
92 echo "% test tag precedence rules"
67 cd ..
93 cd ..
68 hg init t2
94 hg init t2
69 cd t2
95 cd t2
70 echo foo > foo
96 echo foo > foo
71 hg add foo
97 hg add foo
72 hg ci -m 'add foo' -d '1000000 0' # rev 0
98 hg ci -m 'add foo' # rev 0
73 hg tag -d '1000000 0' bar # rev 1
99 hg tag bar # rev 1
74 echo >> foo
100 echo >> foo
75 hg ci -m 'change foo 1' -d '1000000 0' # rev 2
101 hg ci -m 'change foo 1' # rev 2
76 hg up -C 1
102 hg up -C 1
77 hg tag -r 1 -d '1000000 0' -f bar # rev 3
103 hg tag -r 1 -f bar # rev 3
78 hg up -C 1
104 hg up -C 1
79 echo >> foo
105 echo >> foo
80 hg ci -m 'change foo 2' -d '1000000 0' # rev 4
106 hg ci -m 'change foo 2' # rev 4
107 hg tags
108 hg tags # repeat in case of cache effects
109
110 dumptags() {
111 rev=$1
112 echo "rev $rev: .hgtags:"
113 hg cat -r$rev .hgtags
114 }
115
116 echo "% detailed dump of tag info"
117 echo "heads:"
118 hg heads -q # expect 4, 3, 2
119 dumptags 2
120 dumptags 3
121 dumptags 4
122 echo ".hg/tags.cache:"
123 [ -f .hg/tags.cache ] && cat .hg/tags.cache || echo "no such file"
124
125 echo "% test tag removal"
126 hg tag --remove bar # rev 5
127 hg tip -vp
128 hg tags
129 hg tags # again, try to expose cache bugs
130
131 echo '% remove nonexistent tag'
132 hg tag --remove foobar
133 hg tip
134
135 echo "% rollback undoes tag operation"
136 hg rollback # destroy rev 5 (restore bar)
137 hg tags
81 hg tags
138 hg tags
82
139
83 # test tag removal
140 echo "% test tag rank"
84 hg tag --remove -d '1000000 0' bar
85 hg tip
86 hg tags
87
88 echo '% remove nonexistent tag'
89 hg tag --remove -d '1000000 0' foobar
90 hg tip
91
92 # test tag rank
93 cd ..
141 cd ..
94 hg init t3
142 hg init t3
95 cd t3
143 cd t3
96 echo foo > foo
144 echo foo > foo
97 hg add foo
145 hg add foo
98 hg ci -m 'add foo' -d '1000000 0' # rev 0
146 hg ci -m 'add foo' # rev 0
99 hg tag -d '1000000 0' -f bar # rev 1 bar -> 0
147 hg tag -f bar # rev 1 bar -> 0
100 hg tag -d '1000000 0' -f bar # rev 2 bar -> 1
148 hg tag -f bar # rev 2 bar -> 1
101 hg tag -d '1000000 0' -fr 0 bar # rev 3 bar -> 0
149 hg tag -fr 0 bar # rev 3 bar -> 0
102 hg tag -d '1000000 0' -fr 1 bar # rev 3 bar -> 1
150 hg tag -fr 1 bar # rev 4 bar -> 1
103 hg tag -d '1000000 0' -fr 0 bar # rev 4 bar -> 0
151 hg tag -fr 0 bar # rev 5 bar -> 0
104 hg tags
152 hg tags
105 hg co 3
153 hg co 3
106 echo barbar > foo
154 echo barbar > foo
107 hg ci -m 'change foo' -d '1000000 0' # rev 0
155 hg ci -m 'change foo' # rev 6
156 hg tags
157
158 echo "% don't allow moving tag without -f"
159 hg tag -r 3 bar
108 hg tags
160 hg tags
109
161
110 hg tag -d '1000000 0' -r 3 bar # should complain
162 echo "% strip 1: expose an old head"
111 hg tags
163 hg --config extensions.mq= strip 5 > /dev/null 2>&1
164 hg tags # partly stale cache
165 hg tags # up-to-date cache
166 echo "% strip 2: destroy whole branch, no old head exposed"
167 hg --config extensions.mq= strip 4 > /dev/null 2>&1
168 hg tags # partly stale
169 rm -f .hg/tags.cache
170 hg tags # cold cache
112
171
113 # test tag rank with 3 heads
172 echo "% test tag rank with 3 heads"
114 cd ..
173 cd ..
115 hg init t4
174 hg init t4
116 cd t4
175 cd t4
@@ -124,10 +183,11 b' hg tag -fr 2 bar # rev'
124 hg tags
183 hg tags
125 hg up -qC 0
184 hg up -qC 0
126 hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
185 hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
127 echo % bar should still point to rev 2
186 echo "% bar should still point to rev 2"
128 hg tags
187 hg tags
129
188
130
189
190 echo "% remove local as global and global as local"
131 # test that removing global/local tags does not get confused when trying
191 # test that removing global/local tags does not get confused when trying
132 # to remove a tag of type X which actually only exists as a type Y
192 # to remove a tag of type X which actually only exists as a type Y
133 cd ..
193 cd ..
@@ -1,78 +1,147 b''
1 % setup
2 no tag cache
1 000000000000 tip
3 000000000000 tip
4 no tag cache
2 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
3 0acdaf898367 tip
6 acb14030fe0a tip
4 tip 0:0acdaf898367
7 tag cache exists
5 This is a local tag with a really long name! 0:0acdaf898367
8 % create local tag with long name
6 0acdaf8983679e0aac16e811534eb49d7ee1f2b4 first
9 tip 0:acb14030fe0a
7 tip 1:8a3ca90d111d
10 This is a local tag with a really long name! 0:acb14030fe0a
8 first 0:0acdaf898367
11 % create a tag behind hg's back
9 8a3ca90d111d tip
12 acb14030fe0a21b60322c440ad2d20cf7685a376 first
13 tip 1:b9154636be93
14 first 0:acb14030fe0a
15 b9154636be93 tip
16 b9154636be93 tip
17 % create a branch
10 M a
18 M a
11 8a3ca90d111d+ tip
19 b9154636be93+ tip
12 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
20 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
13 0acdaf898367+ first
21 acb14030fe0a+ first
14 0acdaf898367+ first
22 acb14030fe0a+ first
15 M a
23 M a
16 created new head
24 created new head
17 8216907a933d tip
25 c8edf04160c7 tip
26 % merge the two heads
18 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
19 (branch merge, don't forget to commit)
28 (branch merge, don't forget to commit)
20 8216907a933d+8a3ca90d111d+ tip
29 c8edf04160c7+b9154636be93+ tip
21 M .hgtags
30 M .hgtags
22 tip 6:e2174d339386
31 % create fake head, make sure tag not visible afterwards
23 first 0:0acdaf898367
32 tip 6:35ff301afafe
33 first 0:acb14030fe0a
34 % add invalid tags
35 committing .hgtags:
36 acb14030fe0a21b60322c440ad2d20cf7685a376 first
37 spam
38
39 foo bar
40 % report tag parse error on other head
24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 created new head
42 created new head
26 .hgtags@c071f74ab5eb, line 2: cannot parse entry
43 .hgtags@75d9f02dfe28, line 2: cannot parse entry
27 .hgtags@c071f74ab5eb, line 4: node 'foo' is not well formed
44 .hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
28 .hgtags@4ca6f1b1a68c, line 2: node 'x' is not well formed
45 .hgtags@c4be69a18c11, line 2: node 'x' is not well formed
29 tip 8:4ca6f1b1a68c
46 tip 8:c4be69a18c11
30 first 0:0acdaf898367
47 first 0:acb14030fe0a
31 changeset: 8:4ca6f1b1a68c
48 changeset: 8:c4be69a18c11
32 .hgtags@c071f74ab5eb, line 2: cannot parse entry
33 .hgtags@c071f74ab5eb, line 4: node 'foo' is not well formed
34 .hgtags@4ca6f1b1a68c, line 2: node 'x' is not well formed
35 tag: tip
49 tag: tip
36 parent: 3:b2ef3841386b
50 parent: 3:ac5e980c4dc0
37 user: test
51 user: test
38 date: Mon Jan 12 13:46:40 1970 +0000
52 date: Thu Jan 01 00:00:00 1970 +0000
39 summary: head
53 summary: head
40
54
55 % test tag precedence rules
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 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
43 created new head
58 created new head
44 tip 4:36195b728445
59 tip 4:0c192d7d5e6b
45 bar 1:b204a97e6e8d
60 bar 1:78391a272241
46 changeset: 5:1f98c77278de
61 tip 4:0c192d7d5e6b
62 bar 1:78391a272241
63 % detailed dump of tag info
64 heads:
65 4:0c192d7d5e6b
66 3:6fa450212aeb
67 2:7a94127795a3
68 rev 2: .hgtags:
69 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
70 rev 3: .hgtags:
71 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
72 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
73 78391a272241d70354aa14c874552cad6b51bb42 bar
74 rev 4: .hgtags:
75 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
76 .hg/tags.cache:
77 4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
78 3 6fa450212aeb2a21ed616a54aea39a4a27894cd7 7d3b718c964ef37b89e550ebdafd5789e76ce1b0
79 2 7a94127795a33c10a370c93f731fd9fea0b79af6 0c04f2a8af31de17fab7422878ee5a2dadbc943d
80
81 78391a272241d70354aa14c874552cad6b51bb42 bar
82 % test tag removal
83 changeset: 5:5f6e8655b1c7
47 tag: tip
84 tag: tip
48 user: test
85 user: test
49 date: Mon Jan 12 13:46:40 1970 +0000
86 date: Thu Jan 01 00:00:00 1970 +0000
50 summary: Removed tag bar
87 files: .hgtags
88 description:
89 Removed tag bar
90
51
91
52 tip 5:1f98c77278de
92 diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
93 --- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
94 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
95 @@ -1,1 +1,3 @@
96 bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
97 +78391a272241d70354aa14c874552cad6b51bb42 bar
98 +0000000000000000000000000000000000000000 bar
99
100 tip 5:5f6e8655b1c7
101 tip 5:5f6e8655b1c7
53 % remove nonexistent tag
102 % remove nonexistent tag
54 abort: tag 'foobar' does not exist
103 abort: tag 'foobar' does not exist
55 changeset: 5:1f98c77278de
104 changeset: 5:5f6e8655b1c7
56 tag: tip
105 tag: tip
57 user: test
106 user: test
58 date: Mon Jan 12 13:46:40 1970 +0000
107 date: Thu Jan 01 00:00:00 1970 +0000
59 summary: Removed tag bar
108 summary: Removed tag bar
60
109
61 tip 5:e86d7ed95fd3
110 % rollback undoes tag operation
62 bar 0:b409d9da318e
111 rolling back last transaction
112 tip 4:0c192d7d5e6b
113 bar 1:78391a272241
114 tip 4:0c192d7d5e6b
115 bar 1:78391a272241
116 % test tag rank
117 tip 5:85f05169d91d
118 bar 0:bbd179dfa0a7
63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
119 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 created new head
120 created new head
65 tip 6:b744fbe1f6dd
121 tip 6:735c3ca72986
66 bar 0:b409d9da318e
122 bar 0:bbd179dfa0a7
123 % don't allow moving tag without -f
67 abort: tag 'bar' already exists (use -f to force)
124 abort: tag 'bar' already exists (use -f to force)
68 tip 6:b744fbe1f6dd
125 tip 6:735c3ca72986
69 bar 0:b409d9da318e
126 bar 0:bbd179dfa0a7
127 % strip 1: expose an old head
128 tip 5:735c3ca72986
129 bar 1:78391a272241
130 tip 5:735c3ca72986
131 bar 1:78391a272241
132 % strip 2: destroy whole branch, no old head exposed
133 tip 4:735c3ca72986
134 bar 0:bbd179dfa0a7
135 tip 4:735c3ca72986
136 bar 0:bbd179dfa0a7
137 % test tag rank with 3 heads
70 adding foo
138 adding foo
71 tip 3:197c21bbbf2c
139 tip 3:197c21bbbf2c
72 bar 2:6fa450212aeb
140 bar 2:6fa450212aeb
73 % bar should still point to rev 2
141 % bar should still point to rev 2
74 tip 4:3b4b14ed0202
142 tip 4:3b4b14ed0202
75 bar 2:6fa450212aeb
143 bar 2:6fa450212aeb
144 % remove local as global and global as local
76 adding foo
145 adding foo
77 abort: tag 'localtag' is not a global tag
146 abort: tag 'localtag' is not a global tag
78 abort: tag 'globaltag' is not a local tag
147 abort: tag 'globaltag' is not a local tag
General Comments 0
You need to be logged in to leave comments. Login now