summaryrefslogtreecommitdiff
path: root/mercurial/localrepo.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/localrepo.py')
-rw-r--r--mercurial/localrepo.py1250
1 files changed, 318 insertions, 932 deletions
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
index 89c1edd..6c1f6b7 100644
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -4,168 +4,72 @@
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
+
from node import bin, hex, nullid, nullrev, short
from i18n import _
-import peer, changegroup, subrepo, discovery, pushkey, obsolete
-import changelog, dirstate, filelog, manifest, context, bookmarks, phases
-import lock, transaction, store, encoding, base85
-import scmutil, util, extensions, hook, error, revset
+import repo, changegroup, subrepo, discovery, pushkey
+import changelog, dirstate, filelog, manifest, context, bookmarks
+import lock, transaction, store, encoding
+import scmutil, util, extensions, hook, error
import match as matchmod
import merge as mergemod
import tags as tagsmod
from lock import release
import weakref, errno, os, time, inspect
propertycache = util.propertycache
-filecache = scmutil.filecache
-
-class storecache(filecache):
- """filecache for files in the store"""
- def join(self, obj, fname):
- return obj.sjoin(fname)
-
-MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
-LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
-
-class localpeer(peer.peerrepository):
- '''peer for a local repo; reflects only the most recent API'''
-
- def __init__(self, repo, caps=MODERNCAPS):
- peer.peerrepository.__init__(self)
- self._repo = repo
- self.ui = repo.ui
- self._caps = repo._restrictcapabilities(caps)
- self.requirements = repo.requirements
- self.supportedformats = repo.supportedformats
-
- def close(self):
- self._repo.close()
-
- def _capabilities(self):
- return self._caps
-
- def local(self):
- return self._repo
-
- def canpush(self):
- return True
-
- def url(self):
- return self._repo.url()
-
- def lookup(self, key):
- return self._repo.lookup(key)
-
- def branchmap(self):
- return discovery.visiblebranchmap(self._repo)
-
- def heads(self):
- return discovery.visibleheads(self._repo)
-
- def known(self, nodes):
- return self._repo.known(nodes)
-
- def getbundle(self, source, heads=None, common=None):
- return self._repo.getbundle(source, heads=heads, common=common)
-
- # TODO We might want to move the next two calls into legacypeer and add
- # unbundle instead.
-
- def lock(self):
- return self._repo.lock()
-
- def addchangegroup(self, cg, source, url):
- return self._repo.addchangegroup(cg, source, url)
-
- def pushkey(self, namespace, key, old, new):
- return self._repo.pushkey(namespace, key, old, new)
-
- def listkeys(self, namespace):
- return self._repo.listkeys(namespace)
-
- def debugwireargs(self, one, two, three=None, four=None, five=None):
- '''used to test argument passing over the wire'''
- return "%s %s %s %s %s" % (one, two, three, four, five)
-
-class locallegacypeer(localpeer):
- '''peer extension which implements legacy methods too; used for tests with
- restricted capabilities'''
-
- def __init__(self, repo):
- localpeer.__init__(self, repo, caps=LEGACYCAPS)
-
- def branches(self, nodes):
- return self._repo.branches(nodes)
-
- def between(self, pairs):
- return self._repo.between(pairs)
-
- def changegroup(self, basenodes, source):
- return self._repo.changegroup(basenodes, source)
-
- def changegroupsubset(self, bases, heads, source):
- return self._repo.changegroupsubset(bases, heads, source)
-
-class localrepository(object):
+class localrepository(repo.repository):
+ capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
+ 'known', 'getbundle'))
supportedformats = set(('revlogv1', 'generaldelta'))
supported = supportedformats | set(('store', 'fncache', 'shared',
'dotencode'))
- openerreqs = set(('revlogv1', 'generaldelta'))
- requirements = ['revlogv1']
-
- def _baserequirements(self, create):
- return self.requirements[:]
def __init__(self, baseui, path=None, create=False):
- self.wopener = scmutil.opener(path, expand=True)
- self.wvfs = self.wopener
- self.root = self.wvfs.base
- self.path = self.wvfs.join(".hg")
+ repo.repository.__init__(self)
+ self.root = os.path.realpath(util.expandpath(path))
+ self.path = os.path.join(self.root, ".hg")
self.origroot = path
self.auditor = scmutil.pathauditor(self.root, self._checknested)
self.opener = scmutil.opener(self.path)
- self.vfs = self.opener
+ self.wopener = scmutil.opener(self.root)
self.baseui = baseui
self.ui = baseui.copy()
- # A list of callback to shape the phase if no data were found.
- # Callback are in the form: func(repo, roots) --> processed root.
- # This list it to be filled by extension during repo setup
- self._phasedefaults = []
+
try:
self.ui.readconfig(self.join("hgrc"), self.root)
extensions.loadall(self.ui)
except IOError:
pass
- if not self.vfs.isdir():
+ if not os.path.isdir(self.path):
if create:
- if not self.wvfs.exists():
- self.wvfs.makedirs()
- self.vfs.makedir(notindexed=True)
- requirements = self._baserequirements(create)
+ if not os.path.exists(path):
+ util.makedirs(path)
+ util.makedir(self.path, notindexed=True)
+ requirements = ["revlogv1"]
if self.ui.configbool('format', 'usestore', True):
- self.vfs.mkdir("store")
+ os.mkdir(os.path.join(self.path, "store"))
requirements.append("store")
if self.ui.configbool('format', 'usefncache', True):
requirements.append("fncache")
if self.ui.configbool('format', 'dotencode', True):
requirements.append('dotencode')
# create an invalid changelog
- self.vfs.append(
+ self.opener.append(
"00changelog.i",
'\0\0\0\2' # represents revlogv2
' dummy changelog to prevent using the old repo layout'
)
if self.ui.configbool('format', 'generaldelta', False):
requirements.append("generaldelta")
- requirements = set(requirements)
else:
raise error.RepoError(_("repository %s not found") % path)
elif create:
raise error.RepoError(_("repository %s already exists") % path)
else:
try:
- requirements = scmutil.readrequires(self.vfs, self.supported)
+ requirements = scmutil.readrequires(self.opener, self.supported)
except IOError, inst:
if inst.errno != errno.ENOENT:
raise
@@ -173,7 +77,7 @@ class localrepository(object):
self.sharedpath = self.path
try:
- s = os.path.realpath(self.opener.read("sharedpath").rstrip('\n'))
+ s = os.path.realpath(self.opener.read("sharedpath"))
if not os.path.exists(s):
raise error.RepoError(
_('.hg/sharedpath points to nonexistent directory %s') % s)
@@ -185,36 +89,32 @@ class localrepository(object):
self.store = store.store(requirements, self.sharedpath, scmutil.opener)
self.spath = self.store.path
self.sopener = self.store.opener
- self.svfs = self.sopener
self.sjoin = self.store.join
self.opener.createmode = self.store.createmode
self._applyrequirements(requirements)
if create:
self._writerequirements()
+ # 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._branchcachetip = None
+ self.nodetagscache = None
self.filterpats = {}
self._datafilters = {}
self._transref = self._lockref = self._wlockref = None
- # A cache for various files under .hg/ that tracks file changes,
- # (used by the filecache decorator)
- #
- # Maps a property name to its util.filecacheentry
- self._filecache = {}
-
- def close(self):
- pass
-
- def _restrictcapabilities(self, caps):
- return caps
-
def _applyrequirements(self, requirements):
self.requirements = requirements
+ openerreqs = set(('revlogv1', 'generaldelta'))
self.sopener.options = dict((r, 1) for r in requirements
- if r in self.openerreqs)
+ if r in openerreqs)
def _writerequirements(self):
reqfile = self.opener("requires", "w")
@@ -227,7 +127,6 @@ class localrepository(object):
if not path.startswith(self.root):
return False
subpath = path[len(self.root) + 1:]
- normsubpath = util.pconvert(subpath)
# XXX: Checking against the current working copy is wrong in
# the sense that it can reject things like
@@ -249,9 +148,9 @@ class localrepository(object):
ctx = self[None]
parts = util.splitpath(subpath)
while parts:
- prefix = '/'.join(parts)
+ prefix = os.sep.join(parts)
if prefix in ctx.substate:
- if prefix == normsubpath:
+ if prefix == subpath:
return True
else:
sub = ctx.sub(prefix)
@@ -260,61 +159,15 @@ class localrepository(object):
parts.pop()
return False
- def peer(self):
- return localpeer(self) # not cached to avoid reference cycle
-
- @filecache('bookmarks')
+ @util.propertycache
def _bookmarks(self):
return bookmarks.read(self)
- @filecache('bookmarks.current')
+ @util.propertycache
def _bookmarkcurrent(self):
return bookmarks.readcurrent(self)
- def _writebookmarks(self, marks):
- bookmarks.write(self)
-
- def bookmarkheads(self, bookmark):
- name = bookmark.split('@', 1)[0]
- heads = []
- for mark, n in self._bookmarks.iteritems():
- if mark.split('@', 1)[0] == name:
- heads.append(n)
- return heads
-
- @storecache('phaseroots')
- def _phasecache(self):
- return phases.phasecache(self, self._phasedefaults)
-
- @storecache('obsstore')
- def obsstore(self):
- store = obsolete.obsstore(self.sopener)
- if store and not obsolete._enabled:
- # message is rare enough to not be translated
- msg = 'obsolete feature not enabled but %i markers found!\n'
- self.ui.warn(msg % len(list(store)))
- return store
-
@propertycache
- def hiddenrevs(self):
- """hiddenrevs: revs that should be hidden by command and tools
-
- This set is carried on the repo to ease initialisation and lazy
- loading it'll probably move back to changelog for efficienty and
- consistency reason
-
- Note that the hiddenrevs will needs invalidations when
- - a new changesets is added (possible unstable above extinct)
- - a new obsolete marker is added (possible new extinct changeset)
- """
- hidden = set()
- if self.obsstore:
- ### hide extinct changeset that are not accessible by any mean
- hiddenquery = 'extinct() - ::(. + bookmark() + tagged())'
- hidden.update(self.revs(hiddenquery))
- return hidden
-
- @storecache('00changelog.i')
def changelog(self):
c = changelog.changelog(self.sopener)
if 'HG_PENDING' in os.environ:
@@ -323,11 +176,11 @@ class localrepository(object):
c.readpending('00changelog.i.a')
return c
- @storecache('00manifest.i')
+ @propertycache
def manifest(self):
return manifest.manifest(self.sopener)
- @filecache('dirstate')
+ @propertycache
def dirstate(self):
warned = [0]
def validate(node):
@@ -364,20 +217,6 @@ class localrepository(object):
for i in xrange(len(self)):
yield i
- def revs(self, expr, *args):
- '''Return a list of revisions matching the given revset'''
- expr = revset.formatspec(expr, *args)
- m = revset.match(None, expr)
- return [r for r in m(self, range(len(self)))]
-
- def set(self, expr, *args):
- '''
- Yield a context for each matching revision, after doing arg
- replacement via revset.formatspec
- '''
- for r in self.revs(expr, *args):
- yield self[r]
-
def url(self):
return 'file:' + self.root
@@ -410,9 +249,8 @@ class localrepository(object):
fp.write('\n')
for name in names:
m = munge and munge(name) or name
- if (self._tagscache.tagtypes and
- name in self._tagscache.tagtypes):
- old = self.tags().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()
@@ -446,8 +284,6 @@ class localrepository(object):
fp.close()
- self.invalidatecaches()
-
if '.hgtags' not in self.dirstate:
self[None].add(['.hgtags'])
@@ -489,40 +325,12 @@ class localrepository(object):
self.tags() # instantiate the cache
self._tag(names, node, message, local, user, date)
- @propertycache
- def _tagscache(self):
- '''Returns a tagscache object that contains various tags related
- caches.'''
-
- # This simplifies its cache management by having one decorated
- # function (this one) and the rest simply fetch things from it.
- class tagscache(object):
- def __init__(self):
- # 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 = self.tagtypes = None
-
- self.nodetagscache = self.tagslist = None
-
- cache = tagscache()
- cache.tags, cache.tagtypes = self._findtags()
-
- return cache
-
def tags(self):
'''return a mapping of tag to node'''
- t = {}
- for k, v in self._tagscache.tags.iteritems():
- try:
- # ignore tags to unknown nodes
- self.changelog.rev(v)
- t[k] = v
- except (error.LookupError, ValueError):
- pass
- return t
+ if self._tags is None:
+ (self._tags, self._tagtypes) = self._findtags()
+
+ return self._tags
def _findtags(self):
'''Do the hard work of finding tags. Return a pair of dicts
@@ -551,7 +359,12 @@ class localrepository(object):
tags = {}
for (name, (node, hist)) in alltags.iteritems():
if node != nullid:
- tags[encoding.tolocal(name)] = node
+ try:
+ # ignore tags to unknown nodes
+ self.changelog.lookup(node)
+ tags[encoding.tolocal(name)] = node
+ except error.LookupError:
+ pass
tags['tip'] = self.changelog.tip()
tagtypes = dict([(encoding.tolocal(name), value)
for (name, value) in tagtypes.iteritems()])
@@ -566,29 +379,27 @@ class localrepository(object):
None : tag does not exist
'''
- return self._tagscache.tagtypes.get(tagname)
+ self.tags()
+
+ return self._tagtypes.get(tagname)
def tagslist(self):
'''return a list of tags ordered by revision'''
- if not self._tagscache.tagslist:
- l = []
- for t, n in self.tags().iteritems():
- r = self.changelog.rev(n)
- l.append((r, t, n))
- self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
-
- return self._tagscache.tagslist
+ l = []
+ for t, n in self.tags().iteritems():
+ r = self.changelog.rev(n)
+ l.append((r, t, n))
+ return [(t, n) for r, t, n in sorted(l)]
def nodetags(self, node):
'''return the tags associated with a node'''
- if not self._tagscache.nodetagscache:
- nodetagscache = {}
- for t, n in self._tagscache.tags.iteritems():
- nodetagscache.setdefault(n, []).append(t)
- for tags in nodetagscache.itervalues():
+ if not self.nodetagscache:
+ self.nodetagscache = {}
+ for t, n in self.tags().iteritems():
+ self.nodetagscache.setdefault(n, []).append(t)
+ for tags in self.nodetagscache.itervalues():
tags.sort()
- self._tagscache.nodetagscache = nodetagscache
- return self._tagscache.nodetagscache.get(node, [])
+ return self.nodetagscache.get(node, [])
def nodebookmarks(self, node):
marks = []
@@ -610,7 +421,7 @@ class localrepository(object):
def updatebranchcache(self):
tip = self.changelog.tip()
if self._branchcache is not None and self._branchcachetip == tip:
- return
+ return self._branchcache
oldtip = self._branchcachetip
self._branchcachetip = tip
@@ -621,7 +432,7 @@ class localrepository(object):
partial = self._branchcache
self._branchtags(partial, lrev)
- # this private cache holds all heads (not just the branch tips)
+ # this private cache holds all heads (not just tips)
self._branchcache = partial
def branchmap(self):
@@ -629,27 +440,17 @@ class localrepository(object):
self.updatebranchcache()
return self._branchcache
- def _branchtip(self, heads):
- '''return the tipmost branch head in heads'''
- tip = heads[-1]
- for h in reversed(heads):
- if not self[h].closesbranch():
- tip = h
- break
- return tip
-
- def branchtip(self, branch):
- '''return the tip node for a given branch'''
- if branch not in self.branchmap():
- raise error.RepoLookupError(_("unknown branch '%s'") % branch)
- return self._branchtip(self.branchmap()[branch])
-
def branchtags(self):
'''return a dict where branch names map to the tipmost head of
the branch, open heads come before closed'''
bt = {}
for bn, heads in self.branchmap().iteritems():
- bt[bn] = self._branchtip(heads)
+ tip = heads[-1]
+ for h in reversed(heads):
+ if 'close' not in self.changelog.read(h)[5]:
+ tip = h
+ break
+ bt[bn] = tip
return bt
def _readbranchcache(self):
@@ -672,9 +473,6 @@ class localrepository(object):
continue
node, label = l.split(" ", 1)
label = encoding.tolocal(label.strip())
- if not node in self:
- raise ValueError('invalidating branch cache because node '+
- '%s does not exist' % node)
partial.setdefault(label, []).append(bin(node))
except KeyboardInterrupt:
raise
@@ -691,15 +489,11 @@ class localrepository(object):
for label, nodes in branches.iteritems():
for node in nodes:
f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
- f.close()
+ f.rename()
except (IOError, OSError):
pass
def _updatebranchcache(self, partial, ctxgen):
- """Given a branchhead cache, partial, that may have extra nodes or be
- missing heads, and a generator of nodes that are at least a superset of
- heads missing, this function updates partial to be correct.
- """
# collect new branch entries
newbranches = {}
for c in ctxgen:
@@ -709,54 +503,54 @@ class localrepository(object):
# 1 (branch a) -> 2 (branch b) -> 3 (branch a)
for branch, newnodes in newbranches.iteritems():
bheads = partial.setdefault(branch, [])
- # Remove candidate heads that no longer are in the repo (e.g., as
- # the result of a strip that just happened). Avoid using 'node in
- # self' here because that dives down into branchcache code somewhat
- # recrusively.
- bheadrevs = [self.changelog.rev(node) for node in bheads
- if self.changelog.hasnode(node)]
- newheadrevs = [self.changelog.rev(node) for node in newnodes
- if self.changelog.hasnode(node)]
- ctxisnew = bheadrevs and min(newheadrevs) > max(bheadrevs)
- # Remove duplicates - nodes that are in newheadrevs and are already
- # in bheadrevs. This can happen if you strip a node whose parent
- # was already a head (because they're on different branches).
- bheadrevs = sorted(set(bheadrevs).union(newheadrevs))
-
- # Starting from tip means fewer passes over reachable. If we know
- # the new candidates are not ancestors of existing heads, we don't
- # have to examine ancestors of existing heads
- if ctxisnew:
- iterrevs = sorted(newheadrevs)
- else:
- iterrevs = list(bheadrevs)
-
- # This loop prunes out two kinds of heads - heads that are
- # superceded by a head in newheadrevs, and newheadrevs that are not
- # heads because an existing head is their descendant.
- while iterrevs:
- latest = iterrevs.pop()
- if latest not in bheadrevs:
+ bheads.extend(newnodes)
+ if len(bheads) <= 1:
+ continue
+ bheads = sorted(bheads, key=lambda x: self[x].rev())
+ # starting from tip means fewer passes over reachable
+ while newnodes:
+ latest = newnodes.pop()
+ if latest not in bheads:
continue
- ancestors = set(self.changelog.ancestors([latest],
- bheadrevs[0]))
- if ancestors:
- bheadrevs = [b for b in bheadrevs if b not in ancestors]
- partial[branch] = [self.changelog.node(rev) for rev in bheadrevs]
-
- # There may be branches that cease to exist when the last commit in the
- # branch was stripped. This code filters them out. Note that the
- # branch that ceased to exist may not be in newbranches because
- # newbranches is the set of candidate heads, which when you strip the
- # last commit in a branch will be the parent branch.
- for branch in partial.keys():
- nodes = [head for head in partial[branch]
- if self.changelog.hasnode(head)]
- if not nodes:
- del partial[branch]
+ minbhrev = self[bheads[0]].node()
+ reachable = self.changelog.reachable(latest, minbhrev)
+ reachable.remove(latest)
+ if reachable:
+ bheads = [b for b in bheads if b not in reachable]
+ partial[branch] = bheads
def lookup(self, key):
- return self[key].node()
+ if isinstance(key, int):
+ return self.changelog.node(key)
+ elif key == '.':
+ return self.dirstate.p1()
+ elif key == 'null':
+ return nullid
+ elif key == 'tip':
+ return self.changelog.tip()
+ n = self.changelog._match(key)
+ if n:
+ return n
+ if key in self._bookmarks:
+ return self._bookmarks[key]
+ if key in self.tags():
+ return self.tags()[key]
+ if key in self.branchtags():
+ return self.branchtags()[key]
+ n = self.changelog._partialmatch(key)
+ if n:
+ return n
+
+ # can't find key, check if it might have come from damaged dirstate
+ if key in self.dirstate.parents():
+ raise error.Abort(_("working directory has unknown parent '%s'!")
+ % short(key))
+ try:
+ if len(key) == 20:
+ key = hex(key)
+ except TypeError:
+ pass
+ raise error.RepoLookupError(_("unknown revision '%s'") % key)
def lookupbranch(self, key, remote=None):
repo = remote or self
@@ -768,20 +562,11 @@ class localrepository(object):
def known(self, nodes):
nm = self.changelog.nodemap
- pc = self._phasecache
- result = []
- for n in nodes:
- r = nm.get(n)
- resp = not (r is None or pc.phase(self, r) >= phases.secret)
- result.append(resp)
- return result
+ return [(n in nm) for n in nodes]
def local(self):
return self
- def cancopy(self):
- return self.local() # so statichttprepo's override of local() works
-
def join(self, f):
return os.path.join(self.path, f)
@@ -800,17 +585,6 @@ class localrepository(object):
'''get list of changectxs for parents of changeid'''
return self[changeid].parents()
- def setparents(self, p1, p2=nullid):
- copies = self.dirstate.setparents(p1, p2)
- if copies:
- # Adjust copy records, the dirstate cannot do it, it
- # requires access to parents manifests. Preserve them
- # only for entries added to first parent.
- pctx = self[p1]
- for f in copies:
- if f not in pctx and copies[f] in pctx:
- self.dirstate.copy(copies[f], f)
-
def filectx(self, path, changeid=None, fileid=None):
"""changeid can be a changeset revision, node, or tag.
fileid can be a file revision or node."""
@@ -901,8 +675,8 @@ class localrepository(object):
raise error.RepoError(
_("abandoned transaction found - run hg recover"))
- self._writejournal(desc)
- renames = [(x, undoname(x)) for x in self._journalfiles()]
+ journalfiles = self._writejournal(desc)
+ renames = [(x, undoname(x)) for x in journalfiles]
tr = transaction.transaction(self.ui.warn, self.sopener,
self.sjoin("journal"),
@@ -911,26 +685,27 @@ class localrepository(object):
self._transref = weakref.ref(tr)
return tr
- def _journalfiles(self):
- return (self.sjoin('journal'), self.join('journal.dirstate'),
- self.join('journal.branch'), self.join('journal.desc'),
- self.join('journal.bookmarks'),
- self.sjoin('journal.phaseroots'))
-
- def undofiles(self):
- return [undoname(x) for x in self._journalfiles()]
-
def _writejournal(self, desc):
- self.opener.write("journal.dirstate",
- self.opener.tryread("dirstate"))
+ # save dirstate for rollback
+ try:
+ ds = self.opener.read("dirstate")
+ except IOError:
+ ds = ""
+ self.opener.write("journal.dirstate", ds)
self.opener.write("journal.branch",
encoding.fromlocal(self.dirstate.branch()))
self.opener.write("journal.desc",
"%d\n%s\n" % (len(self), desc))
- self.opener.write("journal.bookmarks",
- self.opener.tryread("bookmarks"))
- self.sopener.write("journal.phaseroots",
- self.sopener.tryread("phaseroots"))
+
+ bkname = self.join('bookmarks')
+ if os.path.exists(bkname):
+ util.copyfile(bkname, self.join('journal.bookmarks'))
+ else:
+ self.opener.write('journal.bookmarks', '')
+
+ return (self.sjoin('journal'), self.join('journal.dirstate'),
+ self.join('journal.branch'), self.join('journal.desc'),
+ self.join('journal.bookmarks'))
def recover(self):
lock = self.lock()
@@ -947,127 +722,67 @@ class localrepository(object):
finally:
lock.release()
- def rollback(self, dryrun=False, force=False):
+ def rollback(self, dryrun=False):
wlock = lock = None
try:
wlock = self.wlock()
lock = self.lock()
if os.path.exists(self.sjoin("undo")):
- return self._rollback(dryrun, force)
+ try:
+ args = self.opener.read("undo.desc").splitlines()
+ if len(args) >= 3 and self.ui.verbose:
+ desc = _("repository tip rolled back to revision %s"
+ " (undo %s: %s)\n") % (
+ int(args[0]) - 1, args[1], args[2])
+ elif len(args) >= 2:
+ desc = _("repository tip rolled back to revision %s"
+ " (undo %s)\n") % (
+ int(args[0]) - 1, args[1])
+ except IOError:
+ desc = _("rolling back unknown transaction\n")
+ self.ui.status(desc)
+ if dryrun:
+ return
+ transaction.rollback(self.sopener, self.sjoin("undo"),
+ self.ui.warn)
+ util.rename(self.join("undo.dirstate"), self.join("dirstate"))
+ if os.path.exists(self.join('undo.bookmarks')):
+ util.rename(self.join('undo.bookmarks'),
+ self.join('bookmarks'))
+ try:
+ branch = self.opener.read("undo.branch")
+ self.dirstate.setbranch(branch)
+ except IOError:
+ self.ui.warn(_("named branch could not be reset, "
+ "current branch is still: %s\n")
+ % self.dirstate.branch())
+ self.invalidate()
+ self.dirstate.invalidate()
+ self.destroyed()
+ parents = tuple([p.rev() for p in self.parents()])
+ if len(parents) > 1:
+ self.ui.status(_("working directory now based on "
+ "revisions %d and %d\n") % parents)
+ else:
+ self.ui.status(_("working directory now based on "
+ "revision %d\n") % parents)
else:
self.ui.warn(_("no rollback information available\n"))
return 1
finally:
release(lock, wlock)
- def _rollback(self, dryrun, force):
- ui = self.ui
- try:
- args = self.opener.read('undo.desc').splitlines()
- (oldlen, desc, detail) = (int(args[0]), args[1], None)
- if len(args) >= 3:
- detail = args[2]
- oldtip = oldlen - 1
-
- if detail and ui.verbose:
- msg = (_('repository tip rolled back to revision %s'
- ' (undo %s: %s)\n')
- % (oldtip, desc, detail))
- else:
- msg = (_('repository tip rolled back to revision %s'
- ' (undo %s)\n')
- % (oldtip, desc))
- except IOError:
- msg = _('rolling back unknown transaction\n')
- desc = None
-
- if not force and self['.'] != self['tip'] and desc == 'commit':
- raise util.Abort(
- _('rollback of last commit while not checked out '
- 'may lose data'), hint=_('use -f to force'))
-
- ui.status(msg)
- if dryrun:
- return 0
-
- parents = self.dirstate.parents()
- transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
- if os.path.exists(self.join('undo.bookmarks')):
- util.rename(self.join('undo.bookmarks'),
- self.join('bookmarks'))
- if os.path.exists(self.sjoin('undo.phaseroots')):
- util.rename(self.sjoin('undo.phaseroots'),
- self.sjoin('phaseroots'))
- self.invalidate()
-
- # Discard all cache entries to force reloading everything.
- self._filecache.clear()
-
- parentgone = (parents[0] not in self.changelog.nodemap or
- parents[1] not in self.changelog.nodemap)
- if parentgone:
- util.rename(self.join('undo.dirstate'), self.join('dirstate'))
- try:
- branch = self.opener.read('undo.branch')
- self.dirstate.setbranch(branch)
- except IOError:
- ui.warn(_('named branch could not be reset: '
- 'current branch is still \'%s\'\n')
- % self.dirstate.branch())
-
- self.dirstate.invalidate()
- parents = tuple([p.rev() for p in self.parents()])
- if len(parents) > 1:
- ui.status(_('working directory now based on '
- 'revisions %d and %d\n') % parents)
- else:
- ui.status(_('working directory now based on '
- 'revision %d\n') % parents)
- # TODO: if we know which new heads may result from this rollback, pass
- # them to destroy(), which will prevent the branchhead cache from being
- # invalidated.
- self.destroyed()
- return 0
-
def invalidatecaches(self):
- def delcache(name):
- try:
- delattr(self, name)
- except AttributeError:
- pass
-
- delcache('_tagscache')
-
+ self._tags = None
+ self._tagtypes = None
+ self.nodetagscache = None
self._branchcache = None # in UTF-8
self._branchcachetip = None
- def invalidatedirstate(self):
- '''Invalidates the dirstate, causing the next call to dirstate
- to check if it was modified since the last time it was read,
- rereading it if it has.
-
- This is different to dirstate.invalidate() that it doesn't always
- rereads the dirstate. Use dirstate.invalidate() if you want to
- explicitly read the dirstate again (i.e. restoring it to a previous
- known good state).'''
- if 'dirstate' in self.__dict__:
- for k in self.dirstate._filecache:
- try:
- delattr(self.dirstate, k)
- except AttributeError:
- pass
- delattr(self, 'dirstate')
-
def invalidate(self):
- for k in self._filecache:
- # dirstate is invalidated separately in invalidatedirstate()
- if k == 'dirstate':
- continue
-
- try:
- delattr(self, k)
- except AttributeError:
- pass
+ for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
+ if a in self.__dict__:
+ delattr(self, a)
self.invalidatecaches()
def _lock(self, lockname, wait, releasefn, acquirefn, desc):
@@ -1085,16 +800,6 @@ class localrepository(object):
acquirefn()
return l
- def _afterlock(self, callback):
- """add a callback to the current repository lock.
-
- The callback will be executed on lock release."""
- l = self._lockref and self._lockref()
- if l:
- l.postrelease.append(callback)
- else:
- callback()
-
def lock(self, wait=True):
'''Lock the repository store (.hg/store) and return a weak reference
to the lock. Use this before modifying the store (e.g. committing or
@@ -1104,16 +809,7 @@ class localrepository(object):
l.lock()
return l
- def unlock():
- self.store.write()
- if '_phasecache' in vars(self):
- self._phasecache.write()
- for k, ce in self._filecache.items():
- if k == 'dirstate':
- continue
- ce.refresh()
-
- l = self._lock(self.sjoin("lock"), wait, unlock,
+ l = self._lock(self.sjoin("lock"), wait, self.store.write,
self.invalidate, _('repository %s') % self.origroot)
self._lockref = weakref.ref(l)
return l
@@ -1127,14 +823,8 @@ class localrepository(object):
l.lock()
return l
- def unlock():
- self.dirstate.write()
- ce = self._filecache.get('dirstate')
- if ce:
- ce.refresh()
-
- l = self._lock(self.join("wlock"), wait, unlock,
- self.invalidatedirstate, _('working directory of %s') %
+ l = self._lock(self.join("wlock"), wait, self.dirstate.write,
+ self.dirstate.invalidate, _('working directory of %s') %
self.origroot)
self._wlockref = weakref.ref(l)
return l
@@ -1255,58 +945,36 @@ class localrepository(object):
# check subrepos
subs = []
- commitsubs = set()
- newstate = wctx.substate.copy()
- # only manage subrepos and .hgsubstate if .hgsub is present
+ removedsubs = set()
if '.hgsub' in wctx:
- # we'll decide whether to track this ourselves, thanks
- if '.hgsubstate' in changes[0]:
- changes[0].remove('.hgsubstate')
- if '.hgsubstate' in changes[2]:
- changes[2].remove('.hgsubstate')
-
- # compare current state to last committed state
- # build new substate based on last committed state
- oldstate = wctx.p1().substate
- for s in sorted(newstate.keys()):
- if not match(s):
- # ignore working copy, use old state if present
- if s in oldstate:
- newstate[s] = oldstate[s]
- continue
- if not force:
- raise util.Abort(
- _("commit with new subrepo %s excluded") % s)
- if wctx.sub(s).dirty(True):
- if not self.ui.configbool('ui', 'commitsubrepos'):
- raise util.Abort(
- _("uncommitted changes in subrepo %s") % s,
- hint=_("use --subrepos for recursive commit"))
- subs.append(s)
- commitsubs.add(s)
- else:
- bs = wctx.sub(s).basestate()
- newstate[s] = (newstate[s][0], bs, newstate[s][2])
- if oldstate.get(s, (None, None, None))[1] != bs:
- subs.append(s)
-
- # check for removed subrepos
+ # only manage subrepos and .hgsubstate if .hgsub is present
for p in wctx.parents():
- r = [s for s in p.substate if s not in newstate]
- subs += [s for s in r if match(s)]
- if subs:
+ removedsubs.update(s for s in p.substate if match(s))
+ for s in wctx.substate:
+ removedsubs.discard(s)
+ if match(s) and wctx.sub(s).dirty():
+ subs.append(s)
+ if (subs or removedsubs):
if (not match('.hgsub') and
'.hgsub' in (wctx.modified() + wctx.added())):
raise util.Abort(
_("can't commit subrepos without .hgsub"))
- changes[0].insert(0, '.hgsubstate')
-
+ if '.hgsubstate' not in changes[0]:
+ changes[0].insert(0, '.hgsubstate')
+ if '.hgsubstate' in changes[2]:
+ changes[2].remove('.hgsubstate')
elif '.hgsub' in changes[2]:
# clean up .hgsubstate when .hgsub is removed
if ('.hgsubstate' in wctx and
'.hgsubstate' not in changes[0] + changes[1] + changes[2]):
changes[2].insert(0, '.hgsubstate')
+ if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
+ changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
+ if changedsubs:
+ raise util.Abort(_("uncommitted changes in subrepo %s")
+ % changedsubs[0])
+
# make sure all explicit patterns are matched
if not force and match.files():
matched = set(changes[0] + changes[1] + changes[2])
@@ -1331,9 +999,6 @@ class localrepository(object):
and wctx.branch() == wctx.p1().branch()):
return None
- if merge and changes[3]:
- raise util.Abort(_("cannot commit merge with missing files"))
-
ms = mergemod.mergestate(self)
for f in changes[0]:
if f in ms and ms[f] == 'u':
@@ -1345,15 +1010,16 @@ class localrepository(object):
cctx._text = editor(self, cctx, subs)
edited = (text != cctx._text)
- # commit subs and write new state
- if subs:
- for s in sorted(commitsubs):
+ # commit subs
+ if subs or removedsubs:
+ state = wctx.substate.copy()
+ for s in sorted(subs):
sub = wctx.sub(s)
self.ui.status(_('committing subrepository %s\n') %
subrepo.subrelpath(sub))
sr = sub.commit(cctx._text, user, date)
- newstate[s] = (newstate[s][0], sr)
- subrepo.writestate(self, newstate)
+ state[s] = (state[s][0], sr)
+ subrepo.writestate(self, state)
# Save commit message in case this transaction gets rolled back
# (e.g. by a pretxncommit hook). Leave the content alone on
@@ -1363,17 +1029,16 @@ class localrepository(object):
p1, p2 = self.dirstate.parents()
hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
try:
- self.hook("precommit", throw=True, parent1=hookp1,
- parent2=hookp2)
+ self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
ret = self.commitctx(cctx, True)
- except: # re-raises
+ except:
if edited:
self.ui.write(
_('note: commit message saved in %s\n') % msgfn)
raise
# update bookmarks, dirstate and mergestate
- bookmarks.update(self, [p1, p2], ret)
+ bookmarks.update(self, p1, ret)
for f in changes[0] + changes[1]:
self.dirstate.normal(f)
for f in changes[2]:
@@ -1383,9 +1048,7 @@ class localrepository(object):
finally:
wlock.release()
- def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
- self.hook("commit", node=node, parent1=parent1, parent2=parent2)
- self._afterlock(commithook)
+ self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
return ret
def commitctx(self, ctx, error=False):
@@ -1452,44 +1115,22 @@ class localrepository(object):
self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
parent2=xp2, pending=p)
self.changelog.finalize(trp)
- # set the new commit is proper phase
- targetphase = phases.newcommitphase(self.ui)
- if targetphase:
- # retract boundary do not alter parent changeset.
- # if a parent have higher the resulting phase will
- # be compliant anyway
- #
- # if minimal phase was 0 we don't need to retract anything
- phases.retractboundary(self, targetphase, [n])
tr.close()
- self.updatebranchcache()
+
+ if self._branchcache:
+ self.updatebranchcache()
return n
finally:
if tr:
tr.release()
lock.release()
- def destroyed(self, newheadnodes=None):
+ 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.
-
- If you know the branchheadcache was uptodate before nodes were removed
- and you also know the set of candidate new heads that may have resulted
- from the destruction, you can set newheadnodes. This will enable the
- code to update the branchheads cache, rather than having future code
- decide it's invalid and regenrating it from scratch.
- '''
- # If we have info, newheadnodes, on how to update the branch cache, do
- # it, Otherwise, since nodes were destroyed, the cache is stale and this
- # will be caught the next time it is read.
- if newheadnodes:
- tiprev = len(self) - 1
- ctxgen = (self[node] for node in newheadnodes
- if self.changelog.hasnode(node))
- self._updatebranchcache(self._branchcache, ctxgen)
- self._writebranchcache(self._branchcache, self.changelog.tip(),
- tiprev)
+ 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
@@ -1503,9 +1144,6 @@ class localrepository(object):
# tag cache retrieval" case to work.
self.invalidatecaches()
- # Discard all cache entries to force reloading everything.
- self._filecache.clear()
-
def walk(self, match, node=None):
'''
walk recursively through the directory tree or a given
@@ -1517,8 +1155,7 @@ class localrepository(object):
def status(self, node1='.', node2=None, match=None,
ignored=False, clean=False, unknown=False,
listsubrepos=False):
- """return status of files between two nodes or node and working
- directory.
+ """return status of files between two nodes or node and working directory
If node1 is None, use the first dirstate parent instead.
If node2 is None, compare node1 with working directory.
@@ -1526,8 +1163,6 @@ class localrepository(object):
def mfmatches(ctx):
mf = ctx.manifest().copy()
- if match.always():
- return mf
for fn in mf.keys():
if not match(fn):
del mf[fn]
@@ -1553,9 +1188,7 @@ class localrepository(object):
if not parentworking:
def bad(f, msg):
- # 'f' may be a directory pattern from 'match.files()',
- # so 'f not in ctx1' is not enough
- if f not in ctx1 and f not in ctx1.dirs():
+ if f not in ctx1:
self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
match.bad = bad
@@ -1613,11 +1246,10 @@ class localrepository(object):
mf2 = mfmatches(ctx2)
modified, added, clean = [], [], []
- withflags = mf1.withflags() | mf2.withflags()
for fn in mf2:
if fn in mf1:
if (fn not in deleted and
- ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
+ (mf1.flags(fn) != mf2.flags(fn) or
(mf1[fn] != mf2[fn] and
(mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
modified.append(fn)
@@ -1628,22 +1260,6 @@ class localrepository(object):
added.append(fn)
removed = mf1.keys()
- if working and modified and not self.dirstate._checklink:
- # Symlink placeholders may get non-symlink-like contents
- # via user error or dereferencing by NFS or Samba servers,
- # so we filter out any placeholders that don't look like a
- # symlink
- sane = []
- for f in modified:
- if ctx2.flags(f) == 'l':
- d = ctx2[f].data()
- if len(d) >= 1024 or '\n' in d or util.binary(d):
- self.ui.debug('ignoring suspect symlink placeholder'
- ' "%s"\n' % f)
- continue
- sane.append(f)
- modified = sane
-
r = modified, added, removed, deleted, unknown, ignored, clean
if listsubrepos:
@@ -1692,7 +1308,8 @@ class localrepository(object):
fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
bheads = [h for h in bheads if h in fbheads]
if not closed:
- bheads = [h for h in bheads if not self[h].closesbranch()]
+ bheads = [h for h in bheads if
+ ('close' not in self.changelog.read(h)[5])]
return bheads
def branches(self, nodes):
@@ -1729,10 +1346,6 @@ class localrepository(object):
return r
def pull(self, remote, heads=None, force=False):
- # don't open transaction for nothing or you break future useful
- # rollback call
- tr = None
- trname = 'pull\n' + util.hidepassword(remote.url())
lock = self.lock()
try:
tmp = discovery.findcommonincoming(self, remote, heads=heads,
@@ -1740,10 +1353,8 @@ class localrepository(object):
common, fetch, rheads = tmp
if not fetch:
self.ui.status(_("no changes found\n"))
- added = []
result = 0
else:
- tr = self.transaction(trname)
if heads is None and list(common) == [nullid]:
self.ui.status(_("requesting all changes\n"))
elif heads is None and remote.capable('changegroupsubset'):
@@ -1761,50 +1372,9 @@ class localrepository(object):
"changegroupsubset."))
else:
cg = remote.changegroupsubset(fetch, heads, 'pull')
- clstart = len(self.changelog)
- result = self.addchangegroup(cg, 'pull', remote.url())
- clend = len(self.changelog)
- added = [self.changelog.node(r) for r in xrange(clstart, clend)]
-
- # compute target subset
- if heads is None:
- # We pulled every thing possible
- # sync on everything common
- subset = common + added
- else:
- # We pulled a specific subset
- # sync on this subset
- subset = heads
-
- # Get remote phases data from remote
- remotephases = remote.listkeys('phases')
- publishing = bool(remotephases.get('publishing', False))
- if remotephases and not publishing:
- # remote is new and unpublishing
- pheads, _dr = phases.analyzeremotephases(self, subset,
- remotephases)
- phases.advanceboundary(self, phases.public, pheads)
- phases.advanceboundary(self, phases.draft, subset)
- else:
- # Remote is old or publishing all common changesets
- # should be seen as public
- phases.advanceboundary(self, phases.public, subset)
-
- if obsolete._enabled:
- self.ui.debug('fetching remote obsolete markers')
- remoteobs = remote.listkeys('obsolete')
- if 'dump0' in remoteobs:
- if tr is None:
- tr = self.transaction(trname)
- for key in sorted(remoteobs, reverse=True):
- if key.startswith('dump'):
- data = base85.b85decode(remoteobs[key])
- self.obsstore.mergemarkers(tr, data)
- if tr is not None:
- tr.close()
+ result = self.addchangegroup(cg, 'pull', remote.url(),
+ lock=lock)
finally:
- if tr is not None:
- tr.release()
lock.release()
return result
@@ -1819,8 +1389,7 @@ class localrepository(object):
def push(self, remote, force=False, revs=None, newbranch=False):
'''Push outgoing changesets (limited by revs) from the current
repository to remote. Return an integer:
- - None means nothing to push
- - 0 means HTTP error
+ - 0 means HTTP error *or* nothing to push
- 1 means we pushed and remote head count is unchanged *or*
we have outgoing changesets but refused to push
- other values as described by addchangegroup()
@@ -1833,152 +1402,33 @@ class localrepository(object):
# unbundle assumes local user cannot lock remote repo (new ssh
# servers, http servers).
- if not remote.canpush():
- raise util.Abort(_("destination does not support push"))
- # get local lock as we might write phase data
- locallock = self.lock()
+ self.checkpush(force, revs)
+ lock = None
+ unbundle = remote.capable('unbundle')
+ if not unbundle:
+ lock = remote.lock()
try:
- self.checkpush(force, revs)
- lock = None
- unbundle = remote.capable('unbundle')
- if not unbundle:
- lock = remote.lock()
- try:
- # discovery
- fci = discovery.findcommonincoming
- commoninc = fci(self, remote, force=force)
- common, inc, remoteheads = commoninc
- fco = discovery.findcommonoutgoing
- outgoing = fco(self, remote, onlyheads=revs,
- commoninc=commoninc, force=force)
-
-
- if not outgoing.missing:
- # nothing to push
- scmutil.nochangesfound(self.ui, self, outgoing.excluded)
- ret = None
- else:
- # something to push
- if not force:
- # if self.obsstore == False --> no obsolete
- # then, save the iteration
- if self.obsstore:
- # this message are here for 80 char limit reason
- mso = _("push includes an obsolete changeset: %s!")
- msu = _("push includes an unstable changeset: %s!")
- # If we are to push if there is at least one
- # obsolete or unstable changeset in missing, at
- # least one of the missinghead will be obsolete or
- # unstable. So checking heads only is ok
- for node in outgoing.missingheads:
- ctx = self[node]
- if ctx.obsolete():
- raise util.Abort(_(mso) % ctx)
- elif ctx.unstable():
- raise util.Abort(_(msu) % ctx)
- discovery.checkheads(self, remote, outgoing,
- remoteheads, newbranch,
- bool(inc))
-
- # create a changegroup from local
- if revs is None and not outgoing.excluded:
- # push everything,
- # use the fast path, no race possible on push
- cg = self._changegroup(outgoing.missing, 'push')
- else:
- cg = self.getlocalbundle('push', outgoing)
-
- # apply changegroup to remote
- if unbundle:
- # local repo finds heads on server, finds out what
- # revs it must push. once revs transferred, if server
- # finds it has different heads (someone else won
- # commit/push race), server aborts.
- if force:
- remoteheads = ['force']
- # ssh: return remote's addchangegroup()
- # http: return remote's addchangegroup() or 0 for error
- ret = remote.unbundle(cg, remoteheads, 'push')
- else:
- # we return an integer indicating remote head count
- # change
- ret = remote.addchangegroup(cg, 'push', self.url())
-
- if ret:
- # push succeed, synchonize target of the push
- cheads = outgoing.missingheads
- elif revs is None:
- # All out push fails. synchronize all common
- cheads = outgoing.commonheads
- else:
- # I want cheads = heads(::missingheads and ::commonheads)
- # (missingheads is revs with secret changeset filtered out)
- #
- # This can be expressed as:
- # cheads = ( (missingheads and ::commonheads)
- # + (commonheads and ::missingheads))"
- # )
- #
- # while trying to push we already computed the following:
- # common = (::commonheads)
- # missing = ((commonheads::missingheads) - commonheads)
- #
- # We can pick:
- # * missingheads part of comon (::commonheads)
- common = set(outgoing.common)
- cheads = [node for node in revs if node in common]
- # and
- # * commonheads parents on missing
- revset = self.set('%ln and parents(roots(%ln))',
- outgoing.commonheads,
- outgoing.missing)
- cheads.extend(c.node() for c in revset)
- # even when we don't push, exchanging phase data is useful
- remotephases = remote.listkeys('phases')
- if not remotephases: # old server or public only repo
- phases.advanceboundary(self, phases.public, cheads)
- # don't push any phase data as there is nothing to push
+ cg, remote_heads = discovery.prepush(self, remote, force, revs,
+ newbranch)
+ ret = remote_heads
+ if cg is not None:
+ if unbundle:
+ # local repo finds heads on server, finds out what
+ # revs it must push. once revs transferred, if server
+ # finds it has different heads (someone else won
+ # commit/push race), server aborts.
+ if force:
+ remote_heads = ['force']
+ # ssh: return remote's addchangegroup()
+ # http: return remote's addchangegroup() or 0 for error
+ ret = remote.unbundle(cg, remote_heads, 'push')
else:
- ana = phases.analyzeremotephases(self, cheads, remotephases)
- pheads, droots = ana
- ### Apply remote phase on local
- if remotephases.get('publishing', False):
- phases.advanceboundary(self, phases.public, cheads)
- else: # publish = False
- phases.advanceboundary(self, phases.public, pheads)
- phases.advanceboundary(self, phases.draft, cheads)
- ### Apply local phase on remote
-
- # Get the list of all revs draft on remote by public here.
- # XXX Beware that revset break if droots is not strictly
- # XXX root we may want to ensure it is but it is costly
- outdated = self.set('heads((%ln::%ln) and public())',
- droots, cheads)
- for newremotehead in outdated:
- r = remote.pushkey('phases',
- newremotehead.hex(),
- str(phases.draft),
- str(phases.public))
- if not r:
- self.ui.warn(_('updating %s to public failed!\n')
- % newremotehead)
- self.ui.debug('try to push obsolete markers to remote\n')
- if (obsolete._enabled and self.obsstore and
- 'obsolete' in remote.listkeys('namespaces')):
- rslts = []
- remotedata = self.listkeys('obsolete')
- for key in sorted(remotedata, reverse=True):
- # reverse sort to ensure we end with dump0
- data = remotedata[key]
- rslts.append(remote.pushkey('obsolete', key, '', data))
- if [r for r in rslts if not r]:
- msg = _('failed to push some obsolete markers!\n')
- self.ui.warn(msg)
- finally:
- if lock is not None:
- lock.release()
+ # we return an integer indicating remote head count change
+ ret = remote.addchangegroup(cg, 'push', self.url(),
+ lock=lock)
finally:
- locallock.release()
+ if lock is not None:
+ lock.release()
self.ui.debug("checking for updated bookmarks\n")
rb = remote.listkeys('bookmarks')
@@ -2024,21 +1474,9 @@ class localrepository(object):
bases = [nullid]
csets, bases, heads = cl.nodesbetween(bases, heads)
# We assume that all ancestors of bases are known
- common = set(cl.ancestors([cl.rev(n) for n in bases]))
+ common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
return self._changegroupsubset(common, csets, heads, source)
- def getlocalbundle(self, source, outgoing):
- """Like getbundle, but taking a discovery.outgoing as an argument.
-
- This is only implemented for local repos and reuses potentially
- precomputed sets in outgoing."""
- if not outgoing.missing:
- return None
- return self._changegroupsubset(outgoing.common,
- outgoing.missing,
- outgoing.missingheads,
- source)
-
def getbundle(self, source, heads=None, common=None):
"""Like changegroupsubset, but returns the set difference between the
ancestors of heads and the ancestors common.
@@ -2056,8 +1494,10 @@ class localrepository(object):
common = [nullid]
if not heads:
heads = cl.heads()
- return self.getlocalbundle(source,
- discovery.outgoing(cl, common, heads))
+ common, missing = cl.findcommonmissing(common, heads)
+ if not missing:
+ return None
+ return self._changegroupsubset(common, missing, heads, source)
def _changegroupsubset(self, commonrevs, csets, heads, source):
@@ -2067,7 +1507,7 @@ class localrepository(object):
fnodes = {} # needed file nodes
changedfiles = set()
fstate = ['', {}]
- count = [0, 0]
+ count = [0]
# can we go through the fast path ?
heads.sort()
@@ -2080,15 +1520,8 @@ class localrepository(object):
# filter any nodes that claim to be part of the known set
def prune(revlog, missing):
- rr, rl = revlog.rev, revlog.linkrev
return [n for n in missing
- if rl(rr(n)) not in commonrevs]
-
- progress = self.ui.progress
- _bundling = _('bundling')
- _changesets = _('changesets')
- _manifests = _('manifests')
- _files = _('files')
+ if revlog.linkrev(revlog.rev(n)) not in commonrevs]
def lookup(revlog, x):
if revlog == cl:
@@ -2096,22 +1529,23 @@ class localrepository(object):
changedfiles.update(c[3])
mfs.setdefault(c[0], x)
count[0] += 1
- progress(_bundling, count[0],
- unit=_changesets, total=count[1])
+ self.ui.progress(_('bundling'), count[0],
+ unit=_('changesets'), total=len(csets))
return x
elif revlog == mf:
clnode = mfs[x]
mdata = mf.readfast(x)
- for f, n in mdata.iteritems():
- if f in changedfiles:
- fnodes[f].setdefault(n, clnode)
+ for f in changedfiles:
+ if f in mdata:
+ fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
count[0] += 1
- progress(_bundling, count[0],
- unit=_manifests, total=count[1])
- return clnode
+ self.ui.progress(_('bundling'), count[0],
+ unit=_('manifests'), total=len(mfs))
+ return mfs[x]
else:
- progress(_bundling, count[0], item=fstate[0],
- unit=_files, total=count[1])
+ self.ui.progress(
+ _('bundling'), count[0], item=fstate[0],
+ unit=_('files'), total=len(changedfiles))
return fstate[1][x]
bundler = changegroup.bundle10(lookup)
@@ -2124,29 +1558,25 @@ class localrepository(object):
def gengroup():
# Create a changenode group generator that will call our functions
# back to lookup the owning changenode and collect information.
- count[:] = [0, len(csets)]
for chunk in cl.group(csets, bundler, reorder=reorder):
yield chunk
- progress(_bundling, None)
+ self.ui.progress(_('bundling'), None)
# Create a generator for the manifestnodes that calls our lookup
# and data collection functions back.
- for f in changedfiles:
- fnodes[f] = {}
- count[:] = [0, len(mfs)]
+ count[0] = 0
for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
yield chunk
- progress(_bundling, None)
+ self.ui.progress(_('bundling'), None)
mfs.clear()
# Go through all our files in order sorted by name.
- count[:] = [0, len(changedfiles)]
+ count[0] = 0
for fname in sorted(changedfiles):
filerevlog = self.file(fname)
if not len(filerevlog):
- raise util.Abort(_("empty or missing revlog for %s")
- % fname)
+ raise util.Abort(_("empty or missing revlog for %s") % fname)
fstate[0] = fname
fstate[1] = fnodes.pop(fname, {})
@@ -2159,7 +1589,7 @@ class localrepository(object):
# Signal that no more groups are left.
yield bundler.close()
- progress(_bundling, None)
+ self.ui.progress(_('bundling'), None)
if csets:
self.hook('outgoing', node=hex(csets[0]), source=source)
@@ -2185,7 +1615,7 @@ class localrepository(object):
mfs = {}
changedfiles = set()
fstate = ['']
- count = [0, 0]
+ count = [0]
self.hook('preoutgoing', throw=True, source=source)
self.changegroupinfo(nodes, source)
@@ -2193,14 +1623,7 @@ class localrepository(object):
revset = set([cl.rev(n) for n in nodes])
def gennodelst(log):
- ln, llr = log.node, log.linkrev
- return [ln(r) for r in log if llr(r) in revset]
-
- progress = self.ui.progress
- _bundling = _('bundling')
- _changesets = _('changesets')
- _manifests = _('manifests')
- _files = _('files')
+ return [log.node(r) for r in log if log.linkrev(r) in revset]
def lookup(revlog, x):
if revlog == cl:
@@ -2208,17 +1631,18 @@ class localrepository(object):
changedfiles.update(c[3])
mfs.setdefault(c[0], x)
count[0] += 1
- progress(_bundling, count[0],
- unit=_changesets, total=count[1])
+ self.ui.progress(_('bundling'), count[0],
+ unit=_('changesets'), total=len(nodes))
return x
elif revlog == mf:
count[0] += 1
- progress(_bundling, count[0],
- unit=_manifests, total=count[1])
+ self.ui.progress(_('bundling'), count[0],
+ unit=_('manifests'), total=len(mfs))
return cl.node(revlog.linkrev(revlog.rev(x)))
else:
- progress(_bundling, count[0], item=fstate[0],
- total=count[1], unit=_files)
+ self.ui.progress(
+ _('bundling'), count[0], item=fstate[0],
+ total=len(changedfiles), unit=_('files'))
return cl.node(revlog.linkrev(revlog.rev(x)))
bundler = changegroup.bundle10(lookup)
@@ -2232,22 +1656,20 @@ class localrepository(object):
'''yield a sequence of changegroup chunks (strings)'''
# construct a list of all changed files
- count[:] = [0, len(nodes)]
for chunk in cl.group(nodes, bundler, reorder=reorder):
yield chunk
- progress(_bundling, None)
+ self.ui.progress(_('bundling'), None)
- count[:] = [0, len(mfs)]
+ count[0] = 0
for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
yield chunk
- progress(_bundling, None)
+ self.ui.progress(_('bundling'), None)
- count[:] = [0, len(changedfiles)]
+ count[0] = 0
for fname in sorted(changedfiles):
filerevlog = self.file(fname)
if not len(filerevlog):
- raise util.Abort(_("empty or missing revlog for %s")
- % fname)
+ raise util.Abort(_("empty or missing revlog for %s") % fname)
fstate[0] = fname
nodelist = gennodelst(filerevlog)
if nodelist:
@@ -2256,17 +1678,19 @@ class localrepository(object):
for chunk in filerevlog.group(nodelist, bundler, reorder):
yield chunk
yield bundler.close()
- progress(_bundling, None)
+ self.ui.progress(_('bundling'), None)
if nodes:
self.hook('outgoing', node=hex(nodes[0]), source=source)
return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
- def addchangegroup(self, source, srctype, url, emptyok=False):
+ def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
"""Add the changegroup returned by source.read() to this repo.
srctype is a string like 'push', 'pull', or 'unbundle'. url is
the URL of the repo where this changegroup is coming from.
+ If lock is not None, the function takes ownership of the lock
+ and releases it after the changegroup is added.
Return an integer summarizing the change to this repo:
- nothing changed or no source: 0
@@ -2314,8 +1738,8 @@ class localrepository(object):
source.callback = pr
source.changelogheader()
- srccontent = cl.addgroup(source, csmap, trp)
- if not (srccontent or emptyok):
+ if (cl.addgroup(source, csmap, trp) is None
+ and not emptyok):
raise util.Abort(_("received changelog group is empty"))
clend = len(cl)
changesets = clend - clstart
@@ -2363,7 +1787,7 @@ class localrepository(object):
pr()
fl = self.file(f)
o = len(fl)
- if not fl.addgroup(source, revmap, trp):
+ if fl.addgroup(source, revmap, trp) is None:
raise util.Abort(_("received file revlog group is empty"))
revisions += len(fl) - o
files += 1
@@ -2392,7 +1816,7 @@ class localrepository(object):
heads = cl.heads()
dh = len(heads) - len(oldheads)
for h in heads:
- if h not in oldheads and self[h].closesbranch():
+ if h not in oldheads and 'close' in self[h].extra():
dh -= 1
htext = ""
if dh:
@@ -2408,46 +1832,26 @@ class localrepository(object):
node=hex(cl.node(clstart)), source=srctype,
url=url, pending=p)
- added = [cl.node(r) for r in xrange(clstart, clend)]
- publishing = self.ui.configbool('phases', 'publish', True)
- if srctype == 'push':
- # Old server can not push the boundary themself.
- # New server won't push the boundary if changeset already
- # existed locally as secrete
- #
- # We should not use added here but the list of all change in
- # the bundle
- if publishing:
- phases.advanceboundary(self, phases.public, srccontent)
- else:
- phases.advanceboundary(self, phases.draft, srccontent)
- phases.retractboundary(self, phases.draft, added)
- elif srctype != 'strip':
- # publishing only alter behavior during push
- #
- # strip should not touch boundary at all
- phases.retractboundary(self, phases.draft, added)
-
# make changelog see real files again
cl.finalize(trp)
tr.close()
-
- if changesets > 0:
- def runhooks():
- # forcefully update the on-disk branch cache
- self.ui.debug("updating the branch cache\n")
- self.updatebranchcache()
- self.hook("changegroup", node=hex(cl.node(clstart)),
- source=srctype, url=url)
-
- for n in added:
- self.hook("incoming", node=hex(n), source=srctype,
- url=url)
- self._afterlock(runhooks)
-
finally:
tr.release()
+ if lock:
+ lock.release()
+
+ if changesets > 0:
+ # forcefully update the on-disk branch cache
+ self.ui.debug("updating the branch cache\n")
+ self.updatebranchcache()
+ self.hook("changegroup", node=hex(cl.node(clstart)),
+ source=srctype, url=url)
+
+ for i in xrange(clstart, clend):
+ self.hook("incoming", node=hex(cl.node(i)),
+ source=srctype, url=url)
+
# never return 0 here:
if dh < 0:
return dh - 1
@@ -2463,7 +1867,7 @@ class localrepository(object):
resp = int(l)
except ValueError:
raise error.ResponseError(
- _('unexpected response from remote server:'), l)
+ _('Unexpected response from remote server:'), l)
if resp == 1:
raise util.Abort(_('operation forbidden by server'))
elif resp == 2:
@@ -2476,11 +1880,9 @@ class localrepository(object):
total_files, total_bytes = map(int, l.split(' ', 1))
except (ValueError, TypeError):
raise error.ResponseError(
- _('unexpected response from remote server:'), l)
+ _('Unexpected response from remote server:'), l)
self.ui.status(_('%d files to transfer, %s of data\n') %
(total_files, util.bytecount(total_bytes)))
- handled_bytes = 0
- self.ui.progress(_('clone'), 0, total=total_bytes)
start = time.time()
for i in xrange(total_files):
# XXX doesn't support '\n' or '\r' in filenames
@@ -2490,28 +1892,21 @@ class localrepository(object):
size = int(size)
except (ValueError, TypeError):
raise error.ResponseError(
- _('unexpected response from remote server:'), l)
- if self.ui.debugflag:
- self.ui.debug('adding %s (%s)\n' %
- (name, util.bytecount(size)))
+ _('Unexpected response from remote server:'), l)
+ self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
# for backwards compat, name was partially encoded
ofp = self.sopener(store.decodedir(name), 'w')
for chunk in util.filechunkiter(fp, limit=size):
- handled_bytes += len(chunk)
- self.ui.progress(_('clone'), handled_bytes,
- total=total_bytes)
ofp.write(chunk)
ofp.close()
elapsed = time.time() - start
if elapsed <= 0:
elapsed = 0.001
- self.ui.progress(_('clone'), None)
self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
(util.bytecount(total_bytes), elapsed,
util.bytecount(total_bytes / elapsed)))
- # new requirements = old non-format requirements +
- # new format-related
+ # new requirements = old non-format requirements + new format-related
# requirements from the streamed-in repository
requirements.update(set(self.requirements) - self.supportedformats)
self._applyrequirements(requirements)
@@ -2537,10 +1932,6 @@ class localrepository(object):
# and format flags on "stream" capability, and use
# uncompressed only if compatible.
- if not stream:
- # if the server explicitely prefer to stream (for fast LANs)
- stream = remote.capable('stream-preferred')
-
if stream and not heads:
# 'stream' means remote revlog format is revlogv1 only
if remote.capable('stream'):
@@ -2557,7 +1948,6 @@ class localrepository(object):
def pushkey(self, namespace, key, old, new):
self.hook('prepushkey', throw=True, namespace=namespace, key=key,
old=old, new=new)
- self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
ret = pushkey.push(self, namespace, key, old, new)
self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
ret=ret)
@@ -2565,7 +1955,6 @@ class localrepository(object):
def listkeys(self, namespace):
self.hook('prelistkeys', throw=True, namespace=namespace)
- self.ui.debug('listing keys for "%s"\n' % namespace)
values = pushkey.list(self, namespace)
self.hook('listkeys', namespace=namespace, values=values)
return values
@@ -2587,10 +1976,7 @@ def aftertrans(files):
renamefiles = [tuple(t) for t in files]
def a():
for src, dest in renamefiles:
- try:
- util.rename(src, dest)
- except OSError: # journal file does not yet exist
- pass
+ util.rename(src, dest)
return a
def undoname(fn):