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