mirror of
https://github.com/facebook/sapling.git
synced 2024-10-07 23:38:50 +03:00
Merge with hg
This commit is contained in:
commit
09920a4acd
@ -51,7 +51,7 @@ def perftags(ui, repo):
|
||||
def t():
|
||||
repo.changelog = mercurial.changelog.changelog(repo.sopener)
|
||||
repo.manifest = mercurial.manifest.manifest(repo.sopener)
|
||||
repo.tagscache = None
|
||||
repo._tags = None
|
||||
return len(repo.tags())
|
||||
timer(t)
|
||||
|
||||
|
@ -293,14 +293,11 @@ def reposetup(ui, repo):
|
||||
write(self, marks)
|
||||
return result
|
||||
|
||||
def tags(self):
|
||||
def _findtags(self):
|
||||
"""Merge bookmarks with normal tags"""
|
||||
if self.tagscache:
|
||||
return self.tagscache
|
||||
|
||||
tagscache = super(bookmark_repo, self).tags()
|
||||
tagscache.update(parse(self))
|
||||
return tagscache
|
||||
(tags, tagtypes) = super(bookmark_repo, self)._findtags()
|
||||
tags.update(parse(self))
|
||||
return (tags, tagtypes)
|
||||
|
||||
repo.__class__ = bookmark_repo
|
||||
|
||||
|
19
hgext/mq.py
19
hgext/mq.py
@ -2415,34 +2415,33 @@ def reposetup(ui, repo):
|
||||
raise util.Abort(_('source has mq patches applied'))
|
||||
return super(mqrepo, self).push(remote, force, revs)
|
||||
|
||||
def tags(self):
|
||||
if self.tagscache:
|
||||
return self.tagscache
|
||||
|
||||
tagscache = super(mqrepo, self).tags()
|
||||
def _findtags(self):
|
||||
'''augment tags from base class with patch tags'''
|
||||
result = super(mqrepo, self)._findtags()
|
||||
|
||||
q = self.mq
|
||||
if not q.applied:
|
||||
return tagscache
|
||||
return result
|
||||
|
||||
mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
|
||||
|
||||
if mqtags[-1][0] not in self.changelog.nodemap:
|
||||
self.ui.warn(_('mq status file refers to unknown node %s\n')
|
||||
% short(mqtags[-1][0]))
|
||||
return tagscache
|
||||
return result
|
||||
|
||||
mqtags.append((mqtags[-1][0], 'qtip'))
|
||||
mqtags.append((mqtags[0][0], 'qbase'))
|
||||
mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
|
||||
tags = result[0]
|
||||
for patch in mqtags:
|
||||
if patch[1] in tagscache:
|
||||
if patch[1] in tags:
|
||||
self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
|
||||
% patch[1])
|
||||
else:
|
||||
tagscache[patch[1]] = patch[0]
|
||||
tags[patch[1]] = patch[0]
|
||||
|
||||
return tagscache
|
||||
return result
|
||||
|
||||
def _branchtags(self, partial, lrev):
|
||||
q = self.mq
|
||||
|
@ -49,6 +49,9 @@ def decode(arg):
|
||||
return tuple(map(decode, arg))
|
||||
elif isinstance(arg, list):
|
||||
return map(decode, arg)
|
||||
elif isinstance(arg, dict):
|
||||
for k, v in arg.items():
|
||||
arg[k] = decode(v)
|
||||
return arg
|
||||
|
||||
def encode(arg):
|
||||
@ -58,29 +61,50 @@ def encode(arg):
|
||||
return tuple(map(encode, arg))
|
||||
elif isinstance(arg, list):
|
||||
return map(encode, arg)
|
||||
elif isinstance(arg, dict):
|
||||
for k, v in arg.items():
|
||||
arg[k] = encode(v)
|
||||
return arg
|
||||
|
||||
def wrapper(func, args):
|
||||
def appendsep(s):
|
||||
# ensure the path ends with os.sep, appending it if necessary.
|
||||
try:
|
||||
us = decode(s)
|
||||
except UnicodeError:
|
||||
us = s
|
||||
if us and us[-1] not in ':/\\':
|
||||
s += os.sep
|
||||
return s
|
||||
|
||||
def wrapper(func, args, kwds):
|
||||
# check argument is unicode, then call original
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
return func(*args)
|
||||
return func(*args, **kwds)
|
||||
|
||||
try:
|
||||
# convert arguments to unicode, call func, then convert back
|
||||
return encode(func(*decode(args)))
|
||||
return encode(func(*decode(args), **decode(kwds)))
|
||||
except UnicodeError:
|
||||
# If not encoded with encoding.encoding, report it then
|
||||
# continue with calling original function.
|
||||
raise util.Abort(_("[win32mbcs] filename conversion fail with"
|
||||
raise util.Abort(_("[win32mbcs] filename conversion failed with"
|
||||
" %s encoding\n") % (encoding.encoding))
|
||||
|
||||
def wrapname(name):
|
||||
def wrapperforlistdir(func, args, kwds):
|
||||
# Ensure 'path' argument ends with os.sep to avoids
|
||||
# misinterpreting last 0x5c of MBCS 2nd byte as path separator.
|
||||
if args:
|
||||
args = list(args)
|
||||
args[0] = appendsep(args[0])
|
||||
if kwds.has_key('path'):
|
||||
kwds['path'] = appendsep(kwds['path'])
|
||||
return func(*args, **kwds)
|
||||
|
||||
def wrapname(name, wrapper):
|
||||
module, name = name.rsplit('.', 1)
|
||||
module = sys.modules[module]
|
||||
func = getattr(module, name)
|
||||
def f(*args):
|
||||
return wrapper(func, args)
|
||||
def f(*args, **kwds):
|
||||
return wrapper(func, args, kwds)
|
||||
try:
|
||||
f.__name__ = func.__name__ # fail with python23
|
||||
except Exception:
|
||||
@ -110,7 +134,8 @@ def reposetup(ui, repo):
|
||||
# fake is only for relevant environment.
|
||||
if encoding.encoding.lower() in problematic_encodings.split():
|
||||
for f in funcs.split():
|
||||
wrapname(f)
|
||||
wrapname(f, wrapper)
|
||||
wrapname("mercurial.osutil.listdir", wrapperforlistdir)
|
||||
ui.debug(_("[win32mbcs] activated with encoding: %s\n")
|
||||
% encoding.encoding)
|
||||
|
||||
|
@ -13,6 +13,7 @@ import lock, transaction, store, encoding
|
||||
import util, extensions, hook, error
|
||||
import match as match_
|
||||
import merge as merge_
|
||||
import tags as tags_
|
||||
from lock import release
|
||||
import weakref, stat, errno, os, time, inspect
|
||||
propertycache = util.propertycache
|
||||
@ -89,8 +90,14 @@ class localrepository(repo.repository):
|
||||
self.sjoin = self.store.join
|
||||
self.opener.createmode = self.store.createmode
|
||||
|
||||
self.tagscache = None
|
||||
self._tagstypecache = None
|
||||
# These two define the set of tags for this repository. _tags
|
||||
# maps tag name to node; _tagtypes maps tag name to 'global' or
|
||||
# 'local'. (Global tags are defined by .hgtags across all
|
||||
# heads, and local tags are defined in .hg/localtags.) They
|
||||
# constitute the in-memory cache of tags.
|
||||
self._tags = None
|
||||
self._tagtypes = None
|
||||
|
||||
self.branchcache = None
|
||||
self._ubranchcache = None # UTF-8 version of branchcache
|
||||
self._branchcachetip = None
|
||||
@ -160,8 +167,8 @@ class localrepository(repo.repository):
|
||||
fp.write('\n')
|
||||
for name in names:
|
||||
m = munge and munge(name) or name
|
||||
if self._tagstypecache and name in self._tagstypecache:
|
||||
old = self.tagscache.get(name, nullid)
|
||||
if self._tagtypes and name in self._tagtypes:
|
||||
old = self._tags.get(name, nullid)
|
||||
fp.write('%s %s\n' % (hex(old), m))
|
||||
fp.write('%s %s\n' % (hex(node), m))
|
||||
fp.close()
|
||||
@ -233,100 +240,43 @@ class localrepository(repo.repository):
|
||||
|
||||
def tags(self):
|
||||
'''return a mapping of tag to node'''
|
||||
if self.tagscache:
|
||||
return self.tagscache
|
||||
if self._tags is None:
|
||||
(self._tags, self._tagtypes) = self._findtags()
|
||||
|
||||
globaltags = {}
|
||||
return self._tags
|
||||
|
||||
def _findtags(self):
|
||||
'''Do the hard work of finding tags. Return a pair of dicts
|
||||
(tags, tagtypes) where tags maps tag name to node, and tagtypes
|
||||
maps tag name to a string like \'global\' or \'local\'.
|
||||
Subclasses or extensions are free to add their own tags, but
|
||||
should be aware that the returned dicts will be retained for the
|
||||
duration of the localrepo object.'''
|
||||
|
||||
# XXX what tagtype should subclasses/extensions use? Currently
|
||||
# mq and bookmarks add tags, but do not set the tagtype at all.
|
||||
# Should each extension invent its own tag type? Should there
|
||||
# be one tagtype for all such "virtual" tags? Or is the status
|
||||
# quo fine?
|
||||
|
||||
alltags = {} # map tag name to (node, hist)
|
||||
tagtypes = {}
|
||||
|
||||
def readtags(lines, fn, tagtype):
|
||||
filetags = {}
|
||||
count = 0
|
||||
tags_.findglobaltags(self.ui, self, alltags, tagtypes)
|
||||
tags_.readlocaltags(self.ui, self, alltags, tagtypes)
|
||||
|
||||
def warn(msg):
|
||||
self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
|
||||
|
||||
for l in lines:
|
||||
count += 1
|
||||
if not l:
|
||||
continue
|
||||
s = l.split(" ", 1)
|
||||
if len(s) != 2:
|
||||
warn(_("cannot parse entry"))
|
||||
continue
|
||||
node, key = s
|
||||
key = encoding.tolocal(key.strip()) # stored in UTF-8
|
||||
try:
|
||||
bin_n = bin(node)
|
||||
except TypeError:
|
||||
warn(_("node '%s' is not well formed") % node)
|
||||
continue
|
||||
if bin_n not in self.changelog.nodemap:
|
||||
# silently ignore as pull -r might cause this
|
||||
continue
|
||||
|
||||
h = []
|
||||
if key in filetags:
|
||||
n, h = filetags[key]
|
||||
h.append(n)
|
||||
filetags[key] = (bin_n, h)
|
||||
|
||||
for k, nh in filetags.iteritems():
|
||||
if k not in globaltags:
|
||||
globaltags[k] = nh
|
||||
tagtypes[k] = tagtype
|
||||
continue
|
||||
|
||||
# we prefer the global tag if:
|
||||
# it supercedes us OR
|
||||
# mutual supercedes and it has a higher rank
|
||||
# otherwise we win because we're tip-most
|
||||
an, ah = nh
|
||||
bn, bh = globaltags[k]
|
||||
if (bn != an and an in bh and
|
||||
(bn not in ah or len(bh) > len(ah))):
|
||||
an = bn
|
||||
ah.extend([n for n in bh if n not in ah])
|
||||
globaltags[k] = an, ah
|
||||
tagtypes[k] = tagtype
|
||||
|
||||
seen = set()
|
||||
f = None
|
||||
ctxs = []
|
||||
for node in self.heads():
|
||||
try:
|
||||
fnode = self[node].filenode('.hgtags')
|
||||
except error.LookupError:
|
||||
continue
|
||||
if fnode not in seen:
|
||||
seen.add(fnode)
|
||||
if not f:
|
||||
f = self.filectx('.hgtags', fileid=fnode)
|
||||
else:
|
||||
f = f.filectx(fnode)
|
||||
ctxs.append(f)
|
||||
|
||||
# read the tags file from each head, ending with the tip
|
||||
for f in reversed(ctxs):
|
||||
readtags(f.data().splitlines(), f, "global")
|
||||
|
||||
try:
|
||||
data = encoding.fromlocal(self.opener("localtags").read())
|
||||
# localtags are stored in the local character set
|
||||
# while the internal tag table is stored in UTF-8
|
||||
readtags(data.splitlines(), "localtags", "local")
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
self.tagscache = {}
|
||||
self._tagstypecache = {}
|
||||
for k, nh in globaltags.iteritems():
|
||||
n = nh[0]
|
||||
if n != nullid:
|
||||
self.tagscache[k] = n
|
||||
self._tagstypecache[k] = tagtypes[k]
|
||||
self.tagscache['tip'] = self.changelog.tip()
|
||||
return self.tagscache
|
||||
# Build the return dicts. Have to re-encode tag names because
|
||||
# the tags module always uses UTF-8 (in order not to lose info
|
||||
# writing to the cache), but the rest of Mercurial wants them in
|
||||
# local encoding.
|
||||
tags = {}
|
||||
for (name, (node, hist)) in alltags.iteritems():
|
||||
if node != nullid:
|
||||
tags[encoding.tolocal(name)] = node
|
||||
tags['tip'] = self.changelog.tip()
|
||||
tagtypes = dict([(encoding.tolocal(name), value)
|
||||
for (name, value) in tagtypes.iteritems()])
|
||||
return (tags, tagtypes)
|
||||
|
||||
def tagtype(self, tagname):
|
||||
'''
|
||||
@ -339,7 +289,7 @@ class localrepository(repo.repository):
|
||||
|
||||
self.tags()
|
||||
|
||||
return self._tagstypecache.get(tagname)
|
||||
return self._tagtypes.get(tagname)
|
||||
|
||||
def tagslist(self):
|
||||
'''return a list of tags ordered by revision'''
|
||||
@ -668,6 +618,7 @@ class localrepository(repo.repository):
|
||||
% encoding.tolocal(self.dirstate.branch()))
|
||||
self.invalidate()
|
||||
self.dirstate.invalidate()
|
||||
self.destroyed()
|
||||
else:
|
||||
self.ui.warn(_("no rollback information available\n"))
|
||||
finally:
|
||||
@ -677,8 +628,8 @@ class localrepository(repo.repository):
|
||||
for a in "changelog manifest".split():
|
||||
if a in self.__dict__:
|
||||
delattr(self, a)
|
||||
self.tagscache = None
|
||||
self._tagstypecache = None
|
||||
self._tags = None
|
||||
self._tagtypes = None
|
||||
self.nodetagscache = None
|
||||
self.branchcache = None
|
||||
self._ubranchcache = None
|
||||
@ -966,6 +917,25 @@ class localrepository(repo.repository):
|
||||
del tr
|
||||
lock.release()
|
||||
|
||||
def destroyed(self):
|
||||
'''Inform the repository that nodes have been destroyed.
|
||||
Intended for use by strip and rollback, so there's a common
|
||||
place for anything that has to be done after destroying history.'''
|
||||
# XXX it might be nice if we could take the list of destroyed
|
||||
# nodes, but I don't see an easy way for rollback() to do that
|
||||
|
||||
# Ensure the persistent tag cache is updated. Doing it now
|
||||
# means that the tag cache only has to worry about destroyed
|
||||
# heads immediately after a strip/rollback. That in turn
|
||||
# guarantees that "cachetip == currenttip" (comparing both rev
|
||||
# and node) always means no nodes have been added or destroyed.
|
||||
|
||||
# XXX this is suboptimal when qrefresh'ing: we strip the current
|
||||
# head, refresh the tag cache, then immediately add a new head.
|
||||
# But I think doing it this way is necessary for the "instant
|
||||
# tag cache retrieval" case to work.
|
||||
tags_.findglobaltags(self.ui, self, {}, {})
|
||||
|
||||
def walk(self, match, node=None):
|
||||
'''
|
||||
walk recursively through the directory tree or a given
|
||||
|
@ -142,3 +142,4 @@ def strip(ui, repo, node, backup="all"):
|
||||
if backup != "strip":
|
||||
os.unlink(chgrpfile)
|
||||
|
||||
repo.destroyed()
|
||||
|
@ -114,7 +114,7 @@ class statichttprepository(localrepo.localrepository):
|
||||
|
||||
self.manifest = manifest.manifest(self.sopener)
|
||||
self.changelog = changelog.changelog(self.sopener)
|
||||
self.tagscache = None
|
||||
self._tags = None
|
||||
self.nodetagscache = None
|
||||
self.encodepats = None
|
||||
self.decodepats = None
|
||||
|
@ -284,16 +284,17 @@ class fncachestore(basicstore):
|
||||
self.pathjoiner = pathjoiner
|
||||
self.path = self.pathjoiner(path, 'store')
|
||||
self.createmode = _calcmode(self.path)
|
||||
self._op = opener(self.path)
|
||||
self._op.createmode = self.createmode
|
||||
self.fncache = fncache(self._op)
|
||||
op = opener(self.path)
|
||||
op.createmode = self.createmode
|
||||
fnc = fncache(op)
|
||||
self.fncache = fnc
|
||||
|
||||
def fncacheopener(path, mode='r', *args, **kw):
|
||||
if (mode not in ('r', 'rb')
|
||||
and path.startswith('data/')
|
||||
and path not in self.fncache):
|
||||
self.fncache.add(path)
|
||||
return self._op(hybridencode(path), mode, *args, **kw)
|
||||
and path not in fnc):
|
||||
fnc.add(path)
|
||||
return op(hybridencode(path), mode, *args, **kw)
|
||||
self.opener = fncacheopener
|
||||
|
||||
def join(self, f):
|
||||
|
339
mercurial/tags.py
Normal file
339
mercurial/tags.py
Normal file
@ -0,0 +1,339 @@
|
||||
# tags.py - read tag info from local repository
|
||||
#
|
||||
# Copyright 2009 Matt Mackall <mpm@selenic.com>
|
||||
# Copyright 2009 Greg Ward <greg@gerg.ca>
|
||||
#
|
||||
# This software may be used and distributed according to the terms of the
|
||||
# GNU General Public License version 2, incorporated herein by reference.
|
||||
|
||||
# Currently this module only deals with reading and caching tags.
|
||||
# Eventually, it could take care of updating (adding/removing/moving)
|
||||
# tags too.
|
||||
|
||||
import os
|
||||
from node import nullid, bin, hex, short
|
||||
from i18n import _
|
||||
import encoding
|
||||
import error
|
||||
|
||||
def _debugalways(ui, *msg):
|
||||
ui.write(*msg)
|
||||
|
||||
def _debugconditional(ui, *msg):
|
||||
ui.debug(*msg)
|
||||
|
||||
def _debugnever(ui, *msg):
|
||||
pass
|
||||
|
||||
_debug = _debugalways
|
||||
_debug = _debugnever
|
||||
|
||||
def findglobaltags1(ui, repo, alltags, tagtypes):
|
||||
'''Find global tags in repo by reading .hgtags from every head that
|
||||
has a distinct version of it. Updates the dicts alltags, tagtypes
|
||||
in place: alltags maps tag name to (node, hist) pair (see _readtags()
|
||||
below), and tagtypes maps tag name to tag type ('global' in this
|
||||
case).'''
|
||||
|
||||
seen = set()
|
||||
fctx = None
|
||||
ctxs = [] # list of filectx
|
||||
for node in repo.heads():
|
||||
try:
|
||||
fnode = repo[node].filenode('.hgtags')
|
||||
except error.LookupError:
|
||||
continue
|
||||
if fnode not in seen:
|
||||
seen.add(fnode)
|
||||
if not fctx:
|
||||
fctx = repo.filectx('.hgtags', fileid=fnode)
|
||||
else:
|
||||
fctx = fctx.filectx(fnode)
|
||||
ctxs.append(fctx)
|
||||
|
||||
# read the tags file from each head, ending with the tip
|
||||
for fctx in reversed(ctxs):
|
||||
filetags = _readtags(
|
||||
ui, repo, fctx.data().splitlines(), fctx)
|
||||
_updatetags(filetags, "global", alltags, tagtypes)
|
||||
|
||||
def findglobaltags2(ui, repo, alltags, tagtypes):
|
||||
'''Same as findglobaltags1(), but with caching.'''
|
||||
# This is so we can be lazy and assume alltags contains only global
|
||||
# tags when we pass it to _writetagcache().
|
||||
assert len(alltags) == len(tagtypes) == 0, \
|
||||
"findglobaltags() should be called first"
|
||||
|
||||
(heads, tagfnode, cachetags, shouldwrite) = _readtagcache(ui, repo)
|
||||
if cachetags is not None:
|
||||
assert not shouldwrite
|
||||
# XXX is this really 100% correct? are there oddball special
|
||||
# cases where a global tag should outrank a local tag but won't,
|
||||
# because cachetags does not contain rank info?
|
||||
_updatetags(cachetags, 'global', alltags, tagtypes)
|
||||
return
|
||||
|
||||
_debug(ui, "reading tags from %d head(s): %s\n"
|
||||
% (len(heads), map(short, reversed(heads))))
|
||||
seen = set() # set of fnode
|
||||
fctx = None
|
||||
for head in reversed(heads): # oldest to newest
|
||||
assert head in repo.changelog.nodemap, \
|
||||
"tag cache returned bogus head %s" % short(head)
|
||||
|
||||
fnode = tagfnode.get(head)
|
||||
if fnode and fnode not in seen:
|
||||
seen.add(fnode)
|
||||
if not fctx:
|
||||
fctx = repo.filectx('.hgtags', fileid=fnode)
|
||||
else:
|
||||
fctx = fctx.filectx(fnode)
|
||||
|
||||
filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
|
||||
_updatetags(filetags, 'global', alltags, tagtypes)
|
||||
|
||||
# and update the cache (if necessary)
|
||||
if shouldwrite:
|
||||
_writetagcache(ui, repo, heads, tagfnode, alltags)
|
||||
|
||||
# Set this to findglobaltags1 to disable tag caching.
|
||||
findglobaltags = findglobaltags2
|
||||
|
||||
def readlocaltags(ui, repo, alltags, tagtypes):
|
||||
'''Read local tags in repo. Update alltags and tagtypes.'''
|
||||
try:
|
||||
# localtags is in the local encoding; re-encode to UTF-8 on
|
||||
# input for consistency with the rest of this module.
|
||||
data = repo.opener("localtags").read()
|
||||
filetags = _readtags(
|
||||
ui, repo, data.splitlines(), "localtags",
|
||||
recode=encoding.fromlocal)
|
||||
_updatetags(filetags, "local", alltags, tagtypes)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def _readtags(ui, repo, lines, fn, recode=None):
|
||||
'''Read tag definitions from a file (or any source of lines).
|
||||
Return a mapping from tag name to (node, hist): node is the node id
|
||||
from the last line read for that name, and hist is the list of node
|
||||
ids previously associated with it (in file order). All node ids are
|
||||
binary, not hex.'''
|
||||
|
||||
filetags = {} # map tag name to (node, hist)
|
||||
count = 0
|
||||
|
||||
def warn(msg):
|
||||
ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
|
||||
|
||||
for line in lines:
|
||||
count += 1
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
(nodehex, name) = line.split(" ", 1)
|
||||
except ValueError:
|
||||
warn(_("cannot parse entry"))
|
||||
continue
|
||||
name = name.strip()
|
||||
if recode:
|
||||
name = recode(name)
|
||||
try:
|
||||
nodebin = bin(nodehex)
|
||||
except TypeError:
|
||||
warn(_("node '%s' is not well formed") % nodehex)
|
||||
continue
|
||||
if nodebin not in repo.changelog.nodemap:
|
||||
# silently ignore as pull -r might cause this
|
||||
continue
|
||||
|
||||
# update filetags
|
||||
hist = []
|
||||
if name in filetags:
|
||||
n, hist = filetags[name]
|
||||
hist.append(n)
|
||||
filetags[name] = (nodebin, hist)
|
||||
return filetags
|
||||
|
||||
def _updatetags(filetags, tagtype, alltags, tagtypes):
|
||||
'''Incorporate the tag info read from one file into the two
|
||||
dictionaries, alltags and tagtypes, that contain all tag
|
||||
info (global across all heads plus local).'''
|
||||
|
||||
for name, nodehist in filetags.iteritems():
|
||||
if name not in alltags:
|
||||
alltags[name] = nodehist
|
||||
tagtypes[name] = tagtype
|
||||
continue
|
||||
|
||||
# we prefer alltags[name] if:
|
||||
# it supercedes us OR
|
||||
# mutual supercedes and it has a higher rank
|
||||
# otherwise we win because we're tip-most
|
||||
anode, ahist = nodehist
|
||||
bnode, bhist = alltags[name]
|
||||
if (bnode != anode and anode in bhist and
|
||||
(bnode not in ahist or len(bhist) > len(ahist))):
|
||||
anode = bnode
|
||||
ahist.extend([n for n in bhist if n not in ahist])
|
||||
alltags[name] = anode, ahist
|
||||
tagtypes[name] = tagtype
|
||||
|
||||
|
||||
# The tag cache only stores info about heads, not the tag contents
|
||||
# from each head. I.e. it doesn't try to squeeze out the maximum
|
||||
# performance, but is simpler has a better chance of actually
|
||||
# working correctly. And this gives the biggest performance win: it
|
||||
# avoids looking up .hgtags in the manifest for every head, and it
|
||||
# can avoid calling heads() at all if there have been no changes to
|
||||
# the repo.
|
||||
|
||||
def _readtagcache(ui, repo):
|
||||
'''Read the tag cache and return a tuple (heads, fnodes, cachetags,
|
||||
shouldwrite). If the cache is completely up-to-date, cachetags is a
|
||||
dict of the form returned by _readtags(); otherwise, it is None and
|
||||
heads and fnodes are set. In that case, heads is the list of all
|
||||
heads currently in the repository (ordered from tip to oldest) and
|
||||
fnodes is a mapping from head to .hgtags filenode. If those two are
|
||||
set, caller is responsible for reading tag info from each head.'''
|
||||
|
||||
try:
|
||||
cachefile = repo.opener('tags.cache', 'r')
|
||||
_debug(ui, 'reading tag cache from %s\n' % cachefile.name)
|
||||
except IOError:
|
||||
cachefile = None
|
||||
|
||||
# The cache file consists of lines like
|
||||
# <headrev> <headnode> [<tagnode>]
|
||||
# where <headrev> and <headnode> redundantly identify a repository
|
||||
# head from the time the cache was written, and <tagnode> is the
|
||||
# filenode of .hgtags on that head. Heads with no .hgtags file will
|
||||
# have no <tagnode>. The cache is ordered from tip to oldest (which
|
||||
# is part of why <headrev> is there: a quick visual check is all
|
||||
# that's required to ensure correct order).
|
||||
#
|
||||
# This information is enough to let us avoid the most expensive part
|
||||
# of finding global tags, which is looking up <tagnode> in the
|
||||
# manifest for each head.
|
||||
cacherevs = [] # list of headrev
|
||||
cacheheads = [] # list of headnode
|
||||
cachefnode = {} # map headnode to filenode
|
||||
if cachefile:
|
||||
for line in cachefile:
|
||||
if line == "\n":
|
||||
break
|
||||
line = line.rstrip().split()
|
||||
cacherevs.append(int(line[0]))
|
||||
headnode = bin(line[1])
|
||||
cacheheads.append(headnode)
|
||||
if len(line) == 3:
|
||||
fnode = bin(line[2])
|
||||
cachefnode[headnode] = fnode
|
||||
|
||||
tipnode = repo.changelog.tip()
|
||||
tiprev = len(repo.changelog) - 1
|
||||
|
||||
# Case 1 (common): tip is the same, so nothing has changed.
|
||||
# (Unchanged tip trivially means no changesets have been added.
|
||||
# But, thanks to localrepository.destroyed(), it also means none
|
||||
# have been destroyed by strip or rollback.)
|
||||
if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev:
|
||||
_debug(ui, "tag cache: tip unchanged\n")
|
||||
tags = _readtags(ui, repo, cachefile, cachefile.name)
|
||||
cachefile.close()
|
||||
return (None, None, tags, False)
|
||||
if cachefile:
|
||||
cachefile.close() # ignore rest of file
|
||||
|
||||
repoheads = repo.heads()
|
||||
|
||||
# Case 2 (uncommon): empty repo; get out quickly and don't bother
|
||||
# writing an empty cache.
|
||||
if repoheads == [nullid]:
|
||||
return ([], {}, {}, False)
|
||||
|
||||
# Case 3 (uncommon): cache file missing or empty.
|
||||
if not cacheheads:
|
||||
_debug(ui, 'tag cache: cache file missing or empty\n')
|
||||
|
||||
# Case 4 (uncommon): tip rev decreased. This should only happen
|
||||
# when we're called from localrepository.destroyed(). Refresh the
|
||||
# cache so future invocations will not see disappeared heads in the
|
||||
# cache.
|
||||
elif cacheheads and tiprev < cacherevs[0]:
|
||||
_debug(ui,
|
||||
'tag cache: tip rev decremented (from %d to %d), '
|
||||
'so we must be destroying nodes\n'
|
||||
% (cacherevs[0], tiprev))
|
||||
|
||||
# Case 5 (common): tip has changed, so we've added/replaced heads.
|
||||
else:
|
||||
_debug(ui,
|
||||
'tag cache: tip has changed (%d:%s); must find new heads\n'
|
||||
% (tiprev, short(tipnode)))
|
||||
|
||||
# Luckily, the code to handle cases 3, 4, 5 is the same. So the
|
||||
# above if/elif/else can disappear once we're confident this thing
|
||||
# actually works and we don't need the debug output.
|
||||
|
||||
# N.B. in case 4 (nodes destroyed), "new head" really means "newly
|
||||
# exposed".
|
||||
newheads = [head
|
||||
for head in repoheads
|
||||
if head not in set(cacheheads)]
|
||||
_debug(ui, 'tag cache: found %d head(s) not in cache: %s\n'
|
||||
% (len(newheads), map(short, newheads)))
|
||||
|
||||
# Now we have to lookup the .hgtags filenode for every new head.
|
||||
# This is the most expensive part of finding tags, so performance
|
||||
# depends primarily on the size of newheads. Worst case: no cache
|
||||
# file, so newheads == repoheads.
|
||||
for head in newheads:
|
||||
cctx = repo[head]
|
||||
try:
|
||||
fnode = cctx.filenode('.hgtags')
|
||||
cachefnode[head] = fnode
|
||||
except error.LookupError:
|
||||
# no .hgtags file on this head
|
||||
pass
|
||||
|
||||
# Caller has to iterate over all heads, but can use the filenodes in
|
||||
# cachefnode to get to each .hgtags revision quickly.
|
||||
return (repoheads, cachefnode, None, True)
|
||||
|
||||
def _writetagcache(ui, repo, heads, tagfnode, cachetags):
|
||||
|
||||
cachefile = repo.opener('tags.cache', 'w', atomictemp=True)
|
||||
_debug(ui, 'writing cache file %s\n' % cachefile.name)
|
||||
|
||||
realheads = repo.heads() # for sanity checks below
|
||||
for head in heads:
|
||||
# temporary sanity checks; these can probably be removed
|
||||
# once this code has been in crew for a few weeks
|
||||
assert head in repo.changelog.nodemap, \
|
||||
'trying to write non-existent node %s to tag cache' % short(head)
|
||||
assert head in realheads, \
|
||||
'trying to write non-head %s to tag cache' % short(head)
|
||||
assert head != nullid, \
|
||||
'trying to write nullid to tag cache'
|
||||
|
||||
# This can't fail because of the first assert above. When/if we
|
||||
# remove that assert, we might want to catch LookupError here
|
||||
# and downgrade it to a warning.
|
||||
rev = repo.changelog.rev(head)
|
||||
|
||||
fnode = tagfnode.get(head)
|
||||
if fnode:
|
||||
cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode)))
|
||||
else:
|
||||
cachefile.write('%d %s\n' % (rev, hex(head)))
|
||||
|
||||
# Tag names in the cache are in UTF-8 -- which is the whole reason
|
||||
# we keep them in UTF-8 throughout this module. If we converted
|
||||
# them local encoding on input, we would lose info writing them to
|
||||
# the cache.
|
||||
cachefile.write('\n')
|
||||
for (name, (node, hist)) in cachetags.iteritems():
|
||||
cachefile.write("%s %s\n" % (hex(node), name))
|
||||
|
||||
cachefile.rename()
|
||||
cachefile.close()
|
@ -349,3 +349,33 @@ class ui(object):
|
||||
self.config("ui", "editor") or
|
||||
os.environ.get("VISUAL") or
|
||||
os.environ.get("EDITOR", "vi"))
|
||||
|
||||
def progress(self, topic, pos, item="", unit="", total=None):
|
||||
'''show a progress message
|
||||
|
||||
With stock hg, this is simply a debug message that is hidden
|
||||
by default, but with extensions or GUI tools it may be
|
||||
visible. 'topic' is the current operation, 'item' is a
|
||||
non-numeric marker of the current position (ie the currently
|
||||
in-process file), 'pos' is the current numeric position (ie
|
||||
revision, bytes, etc.), units is a corresponding unit label,
|
||||
and total is the highest expected pos.
|
||||
|
||||
Multiple nested topics may be active at a time. All topics
|
||||
should be marked closed by setting pos to None at termination.
|
||||
'''
|
||||
|
||||
if pos == None or not self.debugflag:
|
||||
return
|
||||
|
||||
if units:
|
||||
units = ' ' + units
|
||||
if item:
|
||||
item = ' ' + item
|
||||
|
||||
if total:
|
||||
pct = 100.0 * pos / total
|
||||
ui.debug('%s:%s %s/%s%s (%4.2g%%)\n'
|
||||
% (topic, item, pos, total, units, pct))
|
||||
else:
|
||||
ui.debug('%s:%s %s%s\n' % (topic, item, pos, units))
|
||||
|
@ -107,9 +107,18 @@ echo % qpop
|
||||
hg qpop
|
||||
checkundo qpop
|
||||
|
||||
echo % qpush
|
||||
echo % qpush with dump of tag cache
|
||||
|
||||
# Dump the tag cache to ensure that it has exactly one head after qpush.
|
||||
rm -f .hg/tags.cache
|
||||
hg tags > /dev/null
|
||||
echo ".hg/tags.cache (pre qpush):"
|
||||
sed 's/ [0-9a-f]*//' .hg/tags.cache
|
||||
hg qpush
|
||||
hg tags > /dev/null
|
||||
echo ".hg/tags.cache (post qpush):"
|
||||
sed 's/ [0-9a-f]*//' .hg/tags.cache
|
||||
|
||||
checkundo qpush
|
||||
|
||||
cd ..
|
||||
|
@ -110,9 +110,15 @@ working dir diff:
|
||||
% qpop
|
||||
popping test.patch
|
||||
patch queue now empty
|
||||
% qpush
|
||||
% qpush with dump of tag cache
|
||||
.hg/tags.cache (pre qpush):
|
||||
1
|
||||
|
||||
applying test.patch
|
||||
now at: test.patch
|
||||
.hg/tags.cache (post qpush):
|
||||
2
|
||||
|
||||
% pop/push outside repo
|
||||
popping test.patch
|
||||
patch queue now empty
|
||||
|
128
tests/test-tags
128
tests/test-tags
@ -1,24 +1,46 @@
|
||||
#!/bin/sh
|
||||
|
||||
cacheexists() {
|
||||
[ -f .hg/tags.cache ] && echo "tag cache exists" || echo "no tag cache"
|
||||
}
|
||||
|
||||
# XXX need to test that the tag cache works when we strip an old head
|
||||
# and add a new one rooted off non-tip: i.e. node and rev of tip are the
|
||||
# same, but stuff has changed behind tip.
|
||||
|
||||
echo "% setup"
|
||||
mkdir t
|
||||
cd t
|
||||
hg init
|
||||
cacheexists
|
||||
hg id
|
||||
cacheexists
|
||||
echo a > a
|
||||
hg add a
|
||||
hg commit -m "test" -d "1000000 0"
|
||||
hg commit -m "test"
|
||||
hg co
|
||||
hg identify
|
||||
T=`hg tip --debug | head -n 1 | cut -d : -f 3`
|
||||
cacheexists
|
||||
|
||||
echo "% create local tag with long name"
|
||||
T=`hg identify --debug --id`
|
||||
hg tag -l "This is a local tag with a really long name!"
|
||||
hg tags
|
||||
rm .hg/localtags
|
||||
|
||||
echo "% create a tag behind hg's back"
|
||||
echo "$T first" > .hgtags
|
||||
cat .hgtags
|
||||
hg add .hgtags
|
||||
hg commit -m "add tags" -d "1000000 0"
|
||||
hg commit -m "add tags"
|
||||
hg tags
|
||||
hg identify
|
||||
|
||||
# repeat with cold tag cache
|
||||
rm -f .hg/tags.cache
|
||||
hg identify
|
||||
|
||||
echo "% create a branch"
|
||||
echo bb > a
|
||||
hg status
|
||||
hg identify
|
||||
@ -28,89 +50,126 @@ hg -v id
|
||||
hg status
|
||||
echo 1 > b
|
||||
hg add b
|
||||
hg commit -m "branch" -d "1000000 0"
|
||||
hg commit -m "branch"
|
||||
hg id
|
||||
|
||||
echo "% merge the two heads"
|
||||
hg merge 1
|
||||
hg id
|
||||
hg status
|
||||
|
||||
hg commit -m "merge" -d "1000000 0"
|
||||
hg commit -m "merge"
|
||||
|
||||
# create fake head, make sure tag not visible afterwards
|
||||
echo "% create fake head, make sure tag not visible afterwards"
|
||||
cp .hgtags tags
|
||||
hg tag -d "1000000 0" last
|
||||
hg tag last
|
||||
hg rm .hgtags
|
||||
hg commit -m "remove" -d "1000000 0"
|
||||
hg commit -m "remove"
|
||||
|
||||
mv tags .hgtags
|
||||
hg add .hgtags
|
||||
hg commit -m "readd" -d "1000000 0"
|
||||
hg commit -m "readd"
|
||||
|
||||
hg tags
|
||||
|
||||
# invalid tags
|
||||
echo "% add invalid tags"
|
||||
echo "spam" >> .hgtags
|
||||
echo >> .hgtags
|
||||
echo "foo bar" >> .hgtags
|
||||
echo "$T invalid" | sed "s/..../a5a5/" >> .hg/localtags
|
||||
hg commit -m "tags" -d "1000000 0"
|
||||
echo "committing .hgtags:"
|
||||
cat .hgtags
|
||||
hg commit -m "tags"
|
||||
|
||||
# report tag parse error on other head
|
||||
echo "% report tag parse error on other head"
|
||||
hg up 3
|
||||
echo 'x y' >> .hgtags
|
||||
hg commit -m "head" -d "1000000 0"
|
||||
hg commit -m "head"
|
||||
|
||||
hg tags
|
||||
hg tip
|
||||
|
||||
# test tag precedence rules
|
||||
echo "% test tag precedence rules"
|
||||
cd ..
|
||||
hg init t2
|
||||
cd t2
|
||||
echo foo > foo
|
||||
hg add foo
|
||||
hg ci -m 'add foo' -d '1000000 0' # rev 0
|
||||
hg tag -d '1000000 0' bar # rev 1
|
||||
hg ci -m 'add foo' # rev 0
|
||||
hg tag bar # rev 1
|
||||
echo >> foo
|
||||
hg ci -m 'change foo 1' -d '1000000 0' # rev 2
|
||||
hg ci -m 'change foo 1' # rev 2
|
||||
hg up -C 1
|
||||
hg tag -r 1 -d '1000000 0' -f bar # rev 3
|
||||
hg tag -r 1 -f bar # rev 3
|
||||
hg up -C 1
|
||||
echo >> foo
|
||||
hg ci -m 'change foo 2' -d '1000000 0' # rev 4
|
||||
hg ci -m 'change foo 2' # rev 4
|
||||
hg tags
|
||||
hg tags # repeat in case of cache effects
|
||||
|
||||
# test tag removal
|
||||
hg tag --remove -d '1000000 0' bar
|
||||
hg tip
|
||||
dumptags() {
|
||||
rev=$1
|
||||
echo "rev $rev: .hgtags:"
|
||||
hg cat -r$rev .hgtags
|
||||
}
|
||||
|
||||
echo "% detailed dump of tag info"
|
||||
echo "heads:"
|
||||
hg heads -q # expect 4, 3, 2
|
||||
dumptags 2
|
||||
dumptags 3
|
||||
dumptags 4
|
||||
echo ".hg/tags.cache:"
|
||||
[ -f .hg/tags.cache ] && cat .hg/tags.cache || echo "no such file"
|
||||
|
||||
echo "% test tag removal"
|
||||
hg tag --remove bar # rev 5
|
||||
hg tip -vp
|
||||
hg tags
|
||||
hg tags # again, try to expose cache bugs
|
||||
|
||||
echo '% remove nonexistent tag'
|
||||
hg tag --remove -d '1000000 0' foobar
|
||||
hg tag --remove foobar
|
||||
hg tip
|
||||
|
||||
# test tag rank
|
||||
echo "% rollback undoes tag operation"
|
||||
hg rollback # destroy rev 5 (restore bar)
|
||||
hg tags
|
||||
hg tags
|
||||
|
||||
echo "% test tag rank"
|
||||
cd ..
|
||||
hg init t3
|
||||
cd t3
|
||||
echo foo > foo
|
||||
hg add foo
|
||||
hg ci -m 'add foo' -d '1000000 0' # rev 0
|
||||
hg tag -d '1000000 0' -f bar # rev 1 bar -> 0
|
||||
hg tag -d '1000000 0' -f bar # rev 2 bar -> 1
|
||||
hg tag -d '1000000 0' -fr 0 bar # rev 3 bar -> 0
|
||||
hg tag -d '1000000 0' -fr 1 bar # rev 3 bar -> 1
|
||||
hg tag -d '1000000 0' -fr 0 bar # rev 4 bar -> 0
|
||||
hg ci -m 'add foo' # rev 0
|
||||
hg tag -f bar # rev 1 bar -> 0
|
||||
hg tag -f bar # rev 2 bar -> 1
|
||||
hg tag -fr 0 bar # rev 3 bar -> 0
|
||||
hg tag -fr 1 bar # rev 4 bar -> 1
|
||||
hg tag -fr 0 bar # rev 5 bar -> 0
|
||||
hg tags
|
||||
hg co 3
|
||||
echo barbar > foo
|
||||
hg ci -m 'change foo' -d '1000000 0' # rev 0
|
||||
hg ci -m 'change foo' # rev 6
|
||||
hg tags
|
||||
|
||||
hg tag -d '1000000 0' -r 3 bar # should complain
|
||||
echo "% don't allow moving tag without -f"
|
||||
hg tag -r 3 bar
|
||||
hg tags
|
||||
|
||||
# test tag rank with 3 heads
|
||||
echo "% strip 1: expose an old head"
|
||||
hg --config extensions.mq= strip 5 > /dev/null 2>&1
|
||||
hg tags # partly stale cache
|
||||
hg tags # up-to-date cache
|
||||
echo "% strip 2: destroy whole branch, no old head exposed"
|
||||
hg --config extensions.mq= strip 4 > /dev/null 2>&1
|
||||
hg tags # partly stale
|
||||
rm -f .hg/tags.cache
|
||||
hg tags # cold cache
|
||||
|
||||
echo "% test tag rank with 3 heads"
|
||||
cd ..
|
||||
hg init t4
|
||||
cd t4
|
||||
@ -124,10 +183,11 @@ hg tag -fr 2 bar # rev 3 bar -> 2
|
||||
hg tags
|
||||
hg up -qC 0
|
||||
hg tag -m 'retag rev 0' -fr 0 bar # rev 4 bar -> 0, but bar stays at 2
|
||||
echo % bar should still point to rev 2
|
||||
echo "% bar should still point to rev 2"
|
||||
hg tags
|
||||
|
||||
|
||||
echo "% remove local as global and global as local"
|
||||
# test that removing global/local tags does not get confused when trying
|
||||
# to remove a tag of type X which actually only exists as a type Y
|
||||
cd ..
|
||||
|
@ -1,78 +1,147 @@
|
||||
% setup
|
||||
no tag cache
|
||||
000000000000 tip
|
||||
no tag cache
|
||||
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
0acdaf898367 tip
|
||||
tip 0:0acdaf898367
|
||||
This is a local tag with a really long name! 0:0acdaf898367
|
||||
0acdaf8983679e0aac16e811534eb49d7ee1f2b4 first
|
||||
tip 1:8a3ca90d111d
|
||||
first 0:0acdaf898367
|
||||
8a3ca90d111d tip
|
||||
acb14030fe0a tip
|
||||
tag cache exists
|
||||
% create local tag with long name
|
||||
tip 0:acb14030fe0a
|
||||
This is a local tag with a really long name! 0:acb14030fe0a
|
||||
% create a tag behind hg's back
|
||||
acb14030fe0a21b60322c440ad2d20cf7685a376 first
|
||||
tip 1:b9154636be93
|
||||
first 0:acb14030fe0a
|
||||
b9154636be93 tip
|
||||
b9154636be93 tip
|
||||
% create a branch
|
||||
M a
|
||||
8a3ca90d111d+ tip
|
||||
b9154636be93+ tip
|
||||
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
|
||||
0acdaf898367+ first
|
||||
0acdaf898367+ first
|
||||
acb14030fe0a+ first
|
||||
acb14030fe0a+ first
|
||||
M a
|
||||
created new head
|
||||
8216907a933d tip
|
||||
c8edf04160c7 tip
|
||||
% merge the two heads
|
||||
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
(branch merge, don't forget to commit)
|
||||
8216907a933d+8a3ca90d111d+ tip
|
||||
c8edf04160c7+b9154636be93+ tip
|
||||
M .hgtags
|
||||
tip 6:e2174d339386
|
||||
first 0:0acdaf898367
|
||||
% create fake head, make sure tag not visible afterwards
|
||||
tip 6:35ff301afafe
|
||||
first 0:acb14030fe0a
|
||||
% add invalid tags
|
||||
committing .hgtags:
|
||||
acb14030fe0a21b60322c440ad2d20cf7685a376 first
|
||||
spam
|
||||
|
||||
foo bar
|
||||
% report tag parse error on other head
|
||||
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
created new head
|
||||
.hgtags@c071f74ab5eb, line 2: cannot parse entry
|
||||
.hgtags@c071f74ab5eb, line 4: node 'foo' is not well formed
|
||||
.hgtags@4ca6f1b1a68c, line 2: node 'x' is not well formed
|
||||
tip 8:4ca6f1b1a68c
|
||||
first 0:0acdaf898367
|
||||
changeset: 8:4ca6f1b1a68c
|
||||
.hgtags@c071f74ab5eb, line 2: cannot parse entry
|
||||
.hgtags@c071f74ab5eb, line 4: node 'foo' is not well formed
|
||||
.hgtags@4ca6f1b1a68c, line 2: node 'x' is not well formed
|
||||
.hgtags@75d9f02dfe28, line 2: cannot parse entry
|
||||
.hgtags@75d9f02dfe28, line 4: node 'foo' is not well formed
|
||||
.hgtags@c4be69a18c11, line 2: node 'x' is not well formed
|
||||
tip 8:c4be69a18c11
|
||||
first 0:acb14030fe0a
|
||||
changeset: 8:c4be69a18c11
|
||||
tag: tip
|
||||
parent: 3:b2ef3841386b
|
||||
parent: 3:ac5e980c4dc0
|
||||
user: test
|
||||
date: Mon Jan 12 13:46:40 1970 +0000
|
||||
date: Thu Jan 01 00:00:00 1970 +0000
|
||||
summary: head
|
||||
|
||||
% test tag precedence rules
|
||||
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
created new head
|
||||
tip 4:36195b728445
|
||||
bar 1:b204a97e6e8d
|
||||
changeset: 5:1f98c77278de
|
||||
tip 4:0c192d7d5e6b
|
||||
bar 1:78391a272241
|
||||
tip 4:0c192d7d5e6b
|
||||
bar 1:78391a272241
|
||||
% detailed dump of tag info
|
||||
heads:
|
||||
4:0c192d7d5e6b
|
||||
3:6fa450212aeb
|
||||
2:7a94127795a3
|
||||
rev 2: .hgtags:
|
||||
bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
|
||||
rev 3: .hgtags:
|
||||
bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
|
||||
bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
|
||||
78391a272241d70354aa14c874552cad6b51bb42 bar
|
||||
rev 4: .hgtags:
|
||||
bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
|
||||
.hg/tags.cache:
|
||||
4 0c192d7d5e6b78a714de54a2e9627952a877e25a 0c04f2a8af31de17fab7422878ee5a2dadbc943d
|
||||
3 6fa450212aeb2a21ed616a54aea39a4a27894cd7 7d3b718c964ef37b89e550ebdafd5789e76ce1b0
|
||||
2 7a94127795a33c10a370c93f731fd9fea0b79af6 0c04f2a8af31de17fab7422878ee5a2dadbc943d
|
||||
|
||||
78391a272241d70354aa14c874552cad6b51bb42 bar
|
||||
% test tag removal
|
||||
changeset: 5:5f6e8655b1c7
|
||||
tag: tip
|
||||
user: test
|
||||
date: Mon Jan 12 13:46:40 1970 +0000
|
||||
summary: Removed tag bar
|
||||
date: Thu Jan 01 00:00:00 1970 +0000
|
||||
files: .hgtags
|
||||
description:
|
||||
Removed tag bar
|
||||
|
||||
tip 5:1f98c77278de
|
||||
|
||||
diff -r 0c192d7d5e6b -r 5f6e8655b1c7 .hgtags
|
||||
--- a/.hgtags Thu Jan 01 00:00:00 1970 +0000
|
||||
+++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
|
||||
@@ -1,1 +1,3 @@
|
||||
bbd179dfa0a71671c253b3ae0aa1513b60d199fa bar
|
||||
+78391a272241d70354aa14c874552cad6b51bb42 bar
|
||||
+0000000000000000000000000000000000000000 bar
|
||||
|
||||
tip 5:5f6e8655b1c7
|
||||
tip 5:5f6e8655b1c7
|
||||
% remove nonexistent tag
|
||||
abort: tag 'foobar' does not exist
|
||||
changeset: 5:1f98c77278de
|
||||
changeset: 5:5f6e8655b1c7
|
||||
tag: tip
|
||||
user: test
|
||||
date: Mon Jan 12 13:46:40 1970 +0000
|
||||
date: Thu Jan 01 00:00:00 1970 +0000
|
||||
summary: Removed tag bar
|
||||
|
||||
tip 5:e86d7ed95fd3
|
||||
bar 0:b409d9da318e
|
||||
% rollback undoes tag operation
|
||||
rolling back last transaction
|
||||
tip 4:0c192d7d5e6b
|
||||
bar 1:78391a272241
|
||||
tip 4:0c192d7d5e6b
|
||||
bar 1:78391a272241
|
||||
% test tag rank
|
||||
tip 5:85f05169d91d
|
||||
bar 0:bbd179dfa0a7
|
||||
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
created new head
|
||||
tip 6:b744fbe1f6dd
|
||||
bar 0:b409d9da318e
|
||||
tip 6:735c3ca72986
|
||||
bar 0:bbd179dfa0a7
|
||||
% don't allow moving tag without -f
|
||||
abort: tag 'bar' already exists (use -f to force)
|
||||
tip 6:b744fbe1f6dd
|
||||
bar 0:b409d9da318e
|
||||
tip 6:735c3ca72986
|
||||
bar 0:bbd179dfa0a7
|
||||
% strip 1: expose an old head
|
||||
tip 5:735c3ca72986
|
||||
bar 1:78391a272241
|
||||
tip 5:735c3ca72986
|
||||
bar 1:78391a272241
|
||||
% strip 2: destroy whole branch, no old head exposed
|
||||
tip 4:735c3ca72986
|
||||
bar 0:bbd179dfa0a7
|
||||
tip 4:735c3ca72986
|
||||
bar 0:bbd179dfa0a7
|
||||
% test tag rank with 3 heads
|
||||
adding foo
|
||||
tip 3:197c21bbbf2c
|
||||
bar 2:6fa450212aeb
|
||||
% bar should still point to rev 2
|
||||
tip 4:3b4b14ed0202
|
||||
bar 2:6fa450212aeb
|
||||
% remove local as global and global as local
|
||||
adding foo
|
||||
abort: tag 'localtag' is not a global tag
|
||||
abort: tag 'globaltag' is not a local tag
|
||||
|
Loading…
Reference in New Issue
Block a user