2005-08-28 01:21:25 +04:00
|
|
|
# localrepo.py - read/write repository class for mercurial
|
|
|
|
#
|
|
|
|
# Copyright 2005 Matt Mackall <mpm@selenic.com>
|
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms
|
|
|
|
# of the GNU General Public License, incorporated herein by reference.
|
|
|
|
|
2006-03-21 13:47:21 +03:00
|
|
|
import os, util
|
2005-08-28 02:35:37 +04:00
|
|
|
import filelog, manifest, changelog, dirstate, repo
|
|
|
|
from node import *
|
2005-10-19 05:37:48 +04:00
|
|
|
from i18n import gettext as _
|
2005-08-28 02:09:46 +04:00
|
|
|
from demandload import *
|
fix race in localrepo.addchangegroup.
localrepo.addchangegroup writes to changelog, then manifest, then normal
files. this breaks access ordering. if reader reads changelog while
manifest is being written, can find pointers into places in manifest
that are not yet written. same can happen for manifest and normal files.
fix is to make almost no change to localrepo.addchangegroup. it must
to write changelog and manifest data early because it has to read them
while writing other files. instead, write changelog and manifest data
to temp file that reader cannot see, then append temp data to manifest
after all normal files written, finally append temp data to changelog.
temp file code is in new appendfile module. can be used in other places
with small changes.
much smaller race still left. we write all new data in one write call,
but reader can maybe see partial update because python or os or filesystem
cannot always make write really atomic. file locking no help: slow, not
portable, not reliable over nfs. only real safe other plan is write to
temp file every time and rename, but performance bad when manifest or
changelog is big.
2006-03-24 20:08:12 +03:00
|
|
|
demandload(globals(), "appendfile changegroup")
|
2006-04-29 02:50:22 +04:00
|
|
|
demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
|
2006-05-01 03:30:39 +04:00
|
|
|
demandload(globals(), "revlog traceback")
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-11-19 09:48:47 +03:00
|
|
|
class localrepository(object):
|
2006-02-28 21:24:54 +03:00
|
|
|
def __del__(self):
|
|
|
|
self.transhandle = None
|
2006-03-06 19:47:41 +03:00
|
|
|
def __init__(self, parentui, path=None, create=0):
|
2005-08-28 03:28:53 +04:00
|
|
|
if not path:
|
|
|
|
p = os.getcwd()
|
|
|
|
while not os.path.isdir(os.path.join(p, ".hg")):
|
|
|
|
oldp = p
|
|
|
|
p = os.path.dirname(p)
|
2006-01-12 09:57:58 +03:00
|
|
|
if p == oldp:
|
|
|
|
raise repo.RepoError(_("no repo found"))
|
2005-08-28 03:28:53 +04:00
|
|
|
path = p
|
|
|
|
self.path = os.path.join(path, ".hg")
|
|
|
|
|
|
|
|
if not create and not os.path.isdir(self.path):
|
2005-12-15 18:19:23 +03:00
|
|
|
raise repo.RepoError(_("repository %s not found") % path)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
self.root = os.path.abspath(path)
|
2006-03-28 21:01:07 +04:00
|
|
|
self.origroot = path
|
2006-03-06 19:47:41 +03:00
|
|
|
self.ui = ui.ui(parentui=parentui)
|
2005-08-28 03:34:54 +04:00
|
|
|
self.opener = util.opener(self.path)
|
|
|
|
self.wopener = util.opener(self.root)
|
2006-04-05 00:38:43 +04:00
|
|
|
|
|
|
|
try:
|
|
|
|
self.ui.readconfig(self.join("hgrc"), self.root)
|
|
|
|
except IOError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
v = self.ui.revlogopts
|
2006-05-08 23:26:18 +04:00
|
|
|
self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
|
2006-04-28 23:52:08 +04:00
|
|
|
self.revlogv1 = self.revlogversion != revlog.REVLOGV0
|
2006-05-08 23:26:18 +04:00
|
|
|
fl = v.get('flags', None)
|
2006-04-05 00:38:43 +04:00
|
|
|
flags = 0
|
2006-05-08 23:26:18 +04:00
|
|
|
if fl != None:
|
|
|
|
for x in fl.split():
|
|
|
|
flags |= revlog.flagstr(x)
|
|
|
|
elif self.revlogv1:
|
|
|
|
flags = revlog.REVLOG_DEFAULT_FLAGS
|
2006-04-05 00:38:43 +04:00
|
|
|
|
|
|
|
v = self.revlogversion | flags
|
|
|
|
self.manifest = manifest.manifest(self.opener, v)
|
|
|
|
self.changelog = changelog.changelog(self.opener, v)
|
|
|
|
|
|
|
|
# the changelog might not have the inline index flag
|
|
|
|
# on. If the format of the changelog is the same as found in
|
|
|
|
# .hgrc, apply any flags found in the .hgrc as well.
|
|
|
|
# Otherwise, just version from the changelog
|
|
|
|
v = self.changelog.version
|
|
|
|
if v == self.revlogversion:
|
|
|
|
v |= flags
|
|
|
|
self.revlogversion = v
|
2006-04-05 00:38:43 +04:00
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
self.tagscache = None
|
|
|
|
self.nodetagscache = None
|
2005-09-15 11:59:16 +04:00
|
|
|
self.encodepats = None
|
|
|
|
self.decodepats = None
|
2006-02-28 21:24:54 +03:00
|
|
|
self.transhandle = None
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-08-28 20:27:24 +04:00
|
|
|
if create:
|
|
|
|
os.mkdir(self.path)
|
|
|
|
os.mkdir(self.join("data"))
|
|
|
|
|
2006-03-06 19:47:41 +03:00
|
|
|
self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
|
2006-04-29 02:50:22 +04:00
|
|
|
|
2006-02-15 02:28:06 +03:00
|
|
|
def hook(self, name, throw=False, **args):
|
2006-04-29 02:50:22 +04:00
|
|
|
def callhook(hname, funcname):
|
|
|
|
'''call python hook. hook is callable object, looked up as
|
|
|
|
name in python module. if callable returns "true", hook
|
2006-05-08 21:59:58 +04:00
|
|
|
fails, else passes. if hook raises exception, treated as
|
|
|
|
hook failure. exception propagates if throw is "true".
|
|
|
|
|
|
|
|
reason for "true" meaning "hook failed" is so that
|
|
|
|
unmodified commands (e.g. mercurial.commands.update) can
|
|
|
|
be run as hooks without wrappers to convert return values.'''
|
2006-04-29 02:50:22 +04:00
|
|
|
|
|
|
|
self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
|
|
|
|
d = funcname.rfind('.')
|
|
|
|
if d == -1:
|
|
|
|
raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
|
|
|
|
% (hname, funcname))
|
|
|
|
modname = funcname[:d]
|
|
|
|
try:
|
|
|
|
obj = __import__(modname)
|
|
|
|
except ImportError:
|
|
|
|
raise util.Abort(_('%s hook is invalid '
|
|
|
|
'(import of "%s" failed)') %
|
|
|
|
(hname, modname))
|
|
|
|
try:
|
|
|
|
for p in funcname.split('.')[1:]:
|
|
|
|
obj = getattr(obj, p)
|
|
|
|
except AttributeError, err:
|
|
|
|
raise util.Abort(_('%s hook is invalid '
|
|
|
|
'("%s" is not defined)') %
|
|
|
|
(hname, funcname))
|
|
|
|
if not callable(obj):
|
|
|
|
raise util.Abort(_('%s hook is invalid '
|
|
|
|
'("%s" is not callable)') %
|
|
|
|
(hname, funcname))
|
|
|
|
try:
|
2006-05-03 22:00:24 +04:00
|
|
|
r = obj(ui=self.ui, repo=self, hooktype=name, **args)
|
2006-04-29 02:50:22 +04:00
|
|
|
except (KeyboardInterrupt, util.SignalInterrupt):
|
|
|
|
raise
|
|
|
|
except Exception, exc:
|
|
|
|
if isinstance(exc, util.Abort):
|
|
|
|
self.ui.warn(_('error: %s hook failed: %s\n') %
|
|
|
|
(hname, exc.args[0] % exc.args[1:]))
|
|
|
|
else:
|
|
|
|
self.ui.warn(_('error: %s hook raised an exception: '
|
|
|
|
'%s\n') % (hname, exc))
|
|
|
|
if throw:
|
|
|
|
raise
|
2006-05-01 03:30:39 +04:00
|
|
|
if self.ui.traceback:
|
2006-04-29 02:50:22 +04:00
|
|
|
traceback.print_exc()
|
2006-05-08 21:59:58 +04:00
|
|
|
return True
|
|
|
|
if r:
|
2006-04-29 02:50:22 +04:00
|
|
|
if throw:
|
|
|
|
raise util.Abort(_('%s hook failed') % hname)
|
2006-05-08 21:59:58 +04:00
|
|
|
self.ui.warn(_('warning: %s hook failed\n') % hname)
|
2006-04-29 02:50:22 +04:00
|
|
|
return r
|
|
|
|
|
2005-10-30 00:44:05 +04:00
|
|
|
def runhook(name, cmd):
|
|
|
|
self.ui.note(_("running hook %s: %s\n") % (name, cmd))
|
2006-05-15 20:13:00 +04:00
|
|
|
env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
|
2006-03-11 09:42:59 +03:00
|
|
|
r = util.system(cmd, environ=env, cwd=self.root)
|
2005-08-28 01:21:25 +04:00
|
|
|
if r:
|
2006-02-15 02:28:06 +03:00
|
|
|
desc, r = util.explain_exit(r)
|
|
|
|
if throw:
|
|
|
|
raise util.Abort(_('%s hook %s') % (name, desc))
|
2006-05-08 21:59:58 +04:00
|
|
|
self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
|
|
|
|
return r
|
2005-10-30 00:44:05 +04:00
|
|
|
|
2006-05-08 21:59:58 +04:00
|
|
|
r = False
|
2006-03-06 19:34:49 +03:00
|
|
|
hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
|
|
|
|
if hname.split(".", 1)[0] == name and cmd]
|
|
|
|
hooks.sort()
|
|
|
|
for hname, cmd in hooks:
|
2006-04-29 02:50:22 +04:00
|
|
|
if cmd.startswith('python:'):
|
2006-05-08 21:59:58 +04:00
|
|
|
r = callhook(hname, cmd[7:].strip()) or r
|
2006-04-29 02:50:22 +04:00
|
|
|
else:
|
2006-05-08 21:59:58 +04:00
|
|
|
r = runhook(hname, cmd) or r
|
2005-10-30 00:44:05 +04:00
|
|
|
return r
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def tags(self):
|
|
|
|
'''return a mapping of tag to node'''
|
|
|
|
if not self.tagscache:
|
|
|
|
self.tagscache = {}
|
2006-03-22 07:30:47 +03:00
|
|
|
|
|
|
|
def parsetag(line, context):
|
|
|
|
if not line:
|
|
|
|
return
|
|
|
|
s = l.split(" ", 1)
|
|
|
|
if len(s) != 2:
|
2006-05-19 10:31:12 +04:00
|
|
|
self.ui.warn(_("%s: cannot parse entry\n") % context)
|
2006-03-22 07:30:47 +03:00
|
|
|
return
|
|
|
|
node, key = s
|
2006-05-19 10:31:12 +04:00
|
|
|
key = key.strip()
|
2005-08-28 01:21:25 +04:00
|
|
|
try:
|
2006-03-22 07:30:47 +03:00
|
|
|
bin_n = bin(node)
|
2005-08-28 01:21:25 +04:00
|
|
|
except TypeError:
|
2006-05-19 10:31:12 +04:00
|
|
|
self.ui.warn(_("%s: node '%s' is not well formed\n") %
|
|
|
|
(context, node))
|
2006-03-22 07:30:47 +03:00
|
|
|
return
|
|
|
|
if bin_n not in self.changelog.nodemap:
|
2006-05-19 10:31:12 +04:00
|
|
|
self.ui.warn(_("%s: tag '%s' refers to unknown node\n") %
|
|
|
|
(context, key))
|
2006-03-22 07:30:47 +03:00
|
|
|
return
|
2006-05-19 10:31:12 +04:00
|
|
|
self.tagscache[key] = bin_n
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-05-19 10:31:12 +04:00
|
|
|
# read the tags file from each head, ending with the tip,
|
2006-03-22 07:30:47 +03:00
|
|
|
# and add each tag found to the map, with "newer" ones
|
|
|
|
# taking precedence
|
2006-05-19 10:31:12 +04:00
|
|
|
heads = self.heads()
|
|
|
|
heads.reverse()
|
2006-03-22 07:30:47 +03:00
|
|
|
fl = self.file(".hgtags")
|
2006-05-19 10:31:12 +04:00
|
|
|
for node in heads:
|
|
|
|
change = self.changelog.read(node)
|
|
|
|
rev = self.changelog.rev(node)
|
|
|
|
fn, ff = self.manifest.find(change[0], '.hgtags')
|
|
|
|
if fn is None: continue
|
2006-03-22 07:30:47 +03:00
|
|
|
count = 0
|
2006-05-19 10:31:12 +04:00
|
|
|
for l in fl.read(fn).splitlines():
|
2006-03-22 07:30:47 +03:00
|
|
|
count += 1
|
2006-05-19 10:31:12 +04:00
|
|
|
parsetag(l, _(".hgtags (rev %d:%s), line %d") %
|
|
|
|
(rev, short(node), count))
|
2005-08-28 01:21:25 +04:00
|
|
|
try:
|
|
|
|
f = self.opener("localtags")
|
2006-03-22 07:30:47 +03:00
|
|
|
count = 0
|
2005-08-28 01:21:25 +04:00
|
|
|
for l in f:
|
2006-03-22 07:30:47 +03:00
|
|
|
count += 1
|
2006-05-19 10:31:12 +04:00
|
|
|
parsetag(l, _("localtags, line %d") % count)
|
2005-08-28 01:21:25 +04:00
|
|
|
except IOError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
self.tagscache['tip'] = self.changelog.tip()
|
|
|
|
|
|
|
|
return self.tagscache
|
|
|
|
|
|
|
|
def tagslist(self):
|
|
|
|
'''return a list of tags ordered by revision'''
|
|
|
|
l = []
|
|
|
|
for t, n in self.tags().items():
|
|
|
|
try:
|
|
|
|
r = self.changelog.rev(n)
|
|
|
|
except:
|
|
|
|
r = -2 # sort to the beginning of the list if unknown
|
2006-01-12 09:57:58 +03:00
|
|
|
l.append((r, t, n))
|
2005-08-28 01:21:25 +04:00
|
|
|
l.sort()
|
2006-01-12 09:57:58 +03:00
|
|
|
return [(t, n) for r, t, n in l]
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def nodetags(self, node):
|
|
|
|
'''return the tags associated with a node'''
|
|
|
|
if not self.nodetagscache:
|
|
|
|
self.nodetagscache = {}
|
2006-01-12 09:57:58 +03:00
|
|
|
for t, n in self.tags().items():
|
|
|
|
self.nodetagscache.setdefault(n, []).append(t)
|
2005-08-28 01:21:25 +04:00
|
|
|
return self.nodetagscache.get(node, [])
|
|
|
|
|
|
|
|
def lookup(self, key):
|
|
|
|
try:
|
|
|
|
return self.tags()[key]
|
|
|
|
except KeyError:
|
|
|
|
try:
|
|
|
|
return self.changelog.lookup(key)
|
|
|
|
except:
|
2005-10-19 05:38:39 +04:00
|
|
|
raise repo.RepoError(_("unknown revision '%s'") % key)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def dev(self):
|
|
|
|
return os.stat(self.path).st_dev
|
|
|
|
|
|
|
|
def local(self):
|
2005-08-28 03:28:53 +04:00
|
|
|
return True
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def join(self, f):
|
|
|
|
return os.path.join(self.path, f)
|
|
|
|
|
|
|
|
def wjoin(self, f):
|
|
|
|
return os.path.join(self.root, f)
|
|
|
|
|
|
|
|
def file(self, f):
|
2006-01-12 09:57:58 +03:00
|
|
|
if f[0] == '/':
|
|
|
|
f = f[1:]
|
2006-04-05 00:38:43 +04:00
|
|
|
return filelog.filelog(self.opener, f, self.revlogversion)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def getcwd(self):
|
|
|
|
return self.dirstate.getcwd()
|
|
|
|
|
|
|
|
def wfile(self, f, mode='r'):
|
|
|
|
return self.wopener(f, mode)
|
|
|
|
|
|
|
|
def wread(self, filename):
|
2005-09-15 11:59:16 +04:00
|
|
|
if self.encodepats == None:
|
|
|
|
l = []
|
|
|
|
for pat, cmd in self.ui.configitems("encode"):
|
2006-03-14 01:32:57 +03:00
|
|
|
mf = util.matcher(self.root, "", [pat], [], [])[1]
|
2005-09-15 11:59:16 +04:00
|
|
|
l.append((mf, cmd))
|
|
|
|
self.encodepats = l
|
|
|
|
|
|
|
|
data = self.wopener(filename, 'r').read()
|
|
|
|
|
|
|
|
for mf, cmd in self.encodepats:
|
|
|
|
if mf(filename):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
|
2005-09-15 11:59:16 +04:00
|
|
|
data = util.filter(data, cmd)
|
|
|
|
break
|
|
|
|
|
|
|
|
return data
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def wwrite(self, filename, data, fd=None):
|
2005-09-15 11:59:16 +04:00
|
|
|
if self.decodepats == None:
|
|
|
|
l = []
|
|
|
|
for pat, cmd in self.ui.configitems("decode"):
|
2006-03-14 01:32:57 +03:00
|
|
|
mf = util.matcher(self.root, "", [pat], [], [])[1]
|
2005-09-15 11:59:16 +04:00
|
|
|
l.append((mf, cmd))
|
|
|
|
self.decodepats = l
|
|
|
|
|
|
|
|
for mf, cmd in self.decodepats:
|
|
|
|
if mf(filename):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
|
2005-09-15 11:59:16 +04:00
|
|
|
data = util.filter(data, cmd)
|
|
|
|
break
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
if fd:
|
|
|
|
return fd.write(data)
|
|
|
|
return self.wopener(filename, 'w').write(data)
|
|
|
|
|
|
|
|
def transaction(self):
|
2006-02-28 21:24:54 +03:00
|
|
|
tr = self.transhandle
|
|
|
|
if tr != None and tr.running():
|
|
|
|
return tr.nest()
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
# save dirstate for undo
|
|
|
|
try:
|
|
|
|
ds = self.opener("dirstate").read()
|
|
|
|
except IOError:
|
|
|
|
ds = ""
|
|
|
|
self.opener("journal.dirstate", "w").write(ds)
|
|
|
|
|
2006-02-28 21:24:54 +03:00
|
|
|
tr = transaction.transaction(self.ui.warn, self.opener,
|
2006-03-11 09:24:19 +03:00
|
|
|
self.join("journal"),
|
2006-02-28 21:24:54 +03:00
|
|
|
aftertrans(self.path))
|
|
|
|
self.transhandle = tr
|
|
|
|
return tr
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def recover(self):
|
2006-02-19 21:43:03 +03:00
|
|
|
l = self.lock()
|
2005-08-28 01:21:25 +04:00
|
|
|
if os.path.exists(self.join("journal")):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("rolling back interrupted transaction\n"))
|
2005-11-09 01:22:03 +03:00
|
|
|
transaction.rollback(self.opener, self.join("journal"))
|
2006-02-22 09:26:29 +03:00
|
|
|
self.reload()
|
2005-11-09 01:22:03 +03:00
|
|
|
return True
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("no interrupted transaction available\n"))
|
2005-11-09 01:22:03 +03:00
|
|
|
return False
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def undo(self, wlock=None):
|
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2006-02-19 21:43:03 +03:00
|
|
|
l = self.lock()
|
2005-08-28 01:21:25 +04:00
|
|
|
if os.path.exists(self.join("undo")):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("rolling back last transaction\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
transaction.rollback(self.opener, self.join("undo"))
|
|
|
|
util.rename(self.join("undo.dirstate"), self.join("dirstate"))
|
2006-02-22 09:26:29 +03:00
|
|
|
self.reload()
|
|
|
|
self.wreload()
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("no undo information available\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-22 09:26:29 +03:00
|
|
|
def wreload(self):
|
|
|
|
self.dirstate.read()
|
|
|
|
|
|
|
|
def reload(self):
|
|
|
|
self.changelog.load()
|
|
|
|
self.manifest.load()
|
|
|
|
self.tagscache = None
|
|
|
|
self.nodetagscache = None
|
|
|
|
|
2006-03-28 21:01:07 +04:00
|
|
|
def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
|
|
|
|
desc=None):
|
2005-08-28 01:21:25 +04:00
|
|
|
try:
|
2006-03-28 21:01:07 +04:00
|
|
|
l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
|
2005-11-12 02:34:13 +03:00
|
|
|
except lock.LockHeld, inst:
|
|
|
|
if not wait:
|
2006-03-28 21:01:07 +04:00
|
|
|
raise
|
|
|
|
self.ui.warn(_("waiting for lock on %s held by %s\n") %
|
|
|
|
(desc, inst.args[0]))
|
|
|
|
# default to 600 seconds timeout
|
|
|
|
l = lock.lock(self.join(lockname),
|
|
|
|
int(self.ui.config("ui", "timeout") or 600),
|
|
|
|
releasefn, desc=desc)
|
2006-02-20 00:39:09 +03:00
|
|
|
if acquirefn:
|
|
|
|
acquirefn()
|
|
|
|
return l
|
|
|
|
|
|
|
|
def lock(self, wait=1):
|
2006-03-28 21:01:07 +04:00
|
|
|
return self.do_lock("lock", wait, acquirefn=self.reload,
|
|
|
|
desc=_('repository %s') % self.origroot)
|
2006-02-20 00:39:09 +03:00
|
|
|
|
|
|
|
def wlock(self, wait=1):
|
2006-03-28 21:01:07 +04:00
|
|
|
return self.do_lock("wlock", wait, self.dirstate.write,
|
|
|
|
self.wreload,
|
|
|
|
desc=_('working directory of %s') % self.origroot)
|
2005-11-12 02:34:13 +03:00
|
|
|
|
2006-02-18 02:23:53 +03:00
|
|
|
def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
|
|
|
|
"determine whether a new filenode is needed"
|
|
|
|
fp1 = manifest1.get(filename, nullid)
|
|
|
|
fp2 = manifest2.get(filename, nullid)
|
|
|
|
|
|
|
|
if fp2 != nullid:
|
|
|
|
# is one parent an ancestor of the other?
|
|
|
|
fpa = filelog.ancestor(fp1, fp2)
|
|
|
|
if fpa == fp1:
|
|
|
|
fp1, fp2 = fp2, nullid
|
|
|
|
elif fpa == fp2:
|
|
|
|
fp2 = nullid
|
|
|
|
|
|
|
|
# is the file unmodified from the parent? report existing entry
|
|
|
|
if fp2 == nullid and text == filelog.read(fp1):
|
|
|
|
return (fp1, None, None)
|
|
|
|
|
|
|
|
return (None, fp1, fp2)
|
2005-11-12 02:34:13 +03:00
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
|
2005-08-28 01:21:25 +04:00
|
|
|
orig_parent = self.dirstate.parents()[0] or nullid
|
|
|
|
p1 = p1 or self.dirstate.parents()[0] or nullid
|
|
|
|
p2 = p2 or self.dirstate.parents()[1] or nullid
|
|
|
|
c1 = self.changelog.read(p1)
|
|
|
|
c2 = self.changelog.read(p2)
|
|
|
|
m1 = self.manifest.read(c1[0])
|
|
|
|
mf1 = self.manifest.readflags(c1[0])
|
|
|
|
m2 = self.manifest.read(c2[0])
|
|
|
|
changed = []
|
|
|
|
|
|
|
|
if orig_parent == p1:
|
|
|
|
update_dirstate = 1
|
|
|
|
else:
|
|
|
|
update_dirstate = 0
|
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2006-02-19 21:43:03 +03:00
|
|
|
l = self.lock()
|
2005-08-28 01:21:25 +04:00
|
|
|
tr = self.transaction()
|
|
|
|
mm = m1.copy()
|
|
|
|
mfm = mf1.copy()
|
|
|
|
linkrev = self.changelog.count()
|
|
|
|
for f in files:
|
|
|
|
try:
|
|
|
|
t = self.wread(f)
|
|
|
|
tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
|
|
|
|
r = self.file(f)
|
|
|
|
mfm[f] = tm
|
|
|
|
|
2006-02-18 02:23:53 +03:00
|
|
|
(entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
|
|
|
|
if entry:
|
|
|
|
mm[f] = entry
|
|
|
|
continue
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
|
|
|
|
changed.append(f)
|
|
|
|
if update_dirstate:
|
|
|
|
self.dirstate.update([f], "n")
|
|
|
|
except IOError:
|
|
|
|
try:
|
|
|
|
del mm[f]
|
|
|
|
del mfm[f]
|
|
|
|
if update_dirstate:
|
|
|
|
self.dirstate.forget([f])
|
|
|
|
except:
|
|
|
|
# deleted from p2?
|
|
|
|
pass
|
|
|
|
|
|
|
|
mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
|
|
|
|
user = user or self.ui.username()
|
|
|
|
n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
|
|
|
|
tr.close()
|
|
|
|
if update_dirstate:
|
|
|
|
self.dirstate.setparents(n, nullid)
|
|
|
|
|
2006-01-12 09:57:58 +03:00
|
|
|
def commit(self, files=None, text="", user=None, date=None,
|
2006-05-12 01:32:09 +04:00
|
|
|
match=util.always, force=False, lock=None, wlock=None,
|
|
|
|
force_editor=False):
|
2005-08-28 01:21:25 +04:00
|
|
|
commit = []
|
|
|
|
remove = []
|
|
|
|
changed = []
|
|
|
|
|
|
|
|
if files:
|
|
|
|
for f in files:
|
|
|
|
s = self.dirstate.state(f)
|
|
|
|
if s in 'nmai':
|
|
|
|
commit.append(f)
|
|
|
|
elif s == 'r':
|
|
|
|
remove.append(f)
|
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%s not tracked!\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
2006-01-12 15:58:36 +03:00
|
|
|
modified, added, removed, deleted, unknown = self.changes(match=match)
|
2006-01-12 15:35:09 +03:00
|
|
|
commit = modified + added
|
|
|
|
remove = removed
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
p1, p2 = self.dirstate.parents()
|
|
|
|
c1 = self.changelog.read(p1)
|
|
|
|
c2 = self.changelog.read(p2)
|
|
|
|
m1 = self.manifest.read(c1[0])
|
|
|
|
mf1 = self.manifest.readflags(c1[0])
|
|
|
|
m2 = self.manifest.read(c2[0])
|
|
|
|
|
|
|
|
if not commit and not remove and not force and p2 == nullid:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("nothing changed\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
return None
|
|
|
|
|
2006-02-15 04:13:18 +03:00
|
|
|
xp1 = hex(p1)
|
|
|
|
if p2 == nullid: xp2 = ''
|
|
|
|
else: xp2 = hex(p2)
|
|
|
|
|
2006-02-16 19:48:31 +03:00
|
|
|
self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2006-02-28 21:25:10 +03:00
|
|
|
if not lock:
|
|
|
|
lock = self.lock()
|
2005-08-28 01:21:25 +04:00
|
|
|
tr = self.transaction()
|
|
|
|
|
|
|
|
# check in files
|
|
|
|
new = {}
|
|
|
|
linkrev = self.changelog.count()
|
|
|
|
commit.sort()
|
|
|
|
for f in commit:
|
|
|
|
self.ui.note(f + "\n")
|
|
|
|
try:
|
|
|
|
mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
|
|
|
|
t = self.wread(f)
|
|
|
|
except IOError:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("trouble committing %s!\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
raise
|
|
|
|
|
2005-08-28 09:04:17 +04:00
|
|
|
r = self.file(f)
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
meta = {}
|
|
|
|
cp = self.dirstate.copied(f)
|
|
|
|
if cp:
|
|
|
|
meta["copy"] = cp
|
|
|
|
meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
|
2005-08-28 09:04:17 +04:00
|
|
|
fp1, fp2 = nullid, nullid
|
|
|
|
else:
|
2006-02-18 02:23:53 +03:00
|
|
|
entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
|
|
|
|
if entry:
|
|
|
|
new[f] = entry
|
2005-08-28 01:21:25 +04:00
|
|
|
continue
|
|
|
|
|
|
|
|
new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
|
|
|
|
# remember what we've added so that we can later calculate
|
|
|
|
# the files to pull from a set of changesets
|
|
|
|
changed.append(f)
|
|
|
|
|
|
|
|
# update manifest
|
2006-01-22 20:54:25 +03:00
|
|
|
m1 = m1.copy()
|
2005-08-28 01:21:25 +04:00
|
|
|
m1.update(new)
|
|
|
|
for f in remove:
|
|
|
|
if f in m1:
|
|
|
|
del m1[f]
|
|
|
|
mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
|
|
|
|
(new, remove))
|
|
|
|
|
|
|
|
# add changeset
|
|
|
|
new = new.keys()
|
|
|
|
new.sort()
|
|
|
|
|
2006-03-21 14:45:27 +03:00
|
|
|
user = user or self.ui.username()
|
2006-05-12 01:32:09 +04:00
|
|
|
if not text or force_editor:
|
|
|
|
edittext = []
|
|
|
|
if text:
|
|
|
|
edittext.append(text)
|
|
|
|
edittext.append("")
|
2005-08-28 01:21:25 +04:00
|
|
|
if p2 != nullid:
|
2006-02-09 09:24:34 +03:00
|
|
|
edittext.append("HG: branch merge")
|
|
|
|
edittext.extend(["HG: changed %s" % f for f in changed])
|
|
|
|
edittext.extend(["HG: removed %s" % f for f in remove])
|
2005-08-28 01:21:25 +04:00
|
|
|
if not changed and not remove:
|
2006-02-09 09:24:34 +03:00
|
|
|
edittext.append("HG: no files changed")
|
|
|
|
edittext.append("")
|
2006-02-09 09:01:23 +03:00
|
|
|
# run editor in the repository root
|
|
|
|
olddir = os.getcwd()
|
|
|
|
os.chdir(self.root)
|
2006-05-17 21:38:41 +04:00
|
|
|
text = self.ui.edit("\n".join(edittext), user)
|
2006-02-09 09:01:23 +03:00
|
|
|
os.chdir(olddir)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-05-17 21:38:41 +04:00
|
|
|
lines = [line.rstrip() for line in text.rstrip().splitlines()]
|
|
|
|
while lines and not lines[0]:
|
|
|
|
del lines[0]
|
|
|
|
if not lines:
|
|
|
|
return None
|
|
|
|
text = '\n'.join(lines)
|
2006-01-28 22:38:31 +03:00
|
|
|
n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
|
2006-02-16 19:48:31 +03:00
|
|
|
self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
|
|
|
|
parent2=xp2)
|
2005-08-28 01:21:25 +04:00
|
|
|
tr.close()
|
|
|
|
|
|
|
|
self.dirstate.setparents(n)
|
|
|
|
self.dirstate.update(new, "n")
|
|
|
|
self.dirstate.forget(remove)
|
|
|
|
|
2006-02-16 19:48:31 +03:00
|
|
|
self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
|
2005-08-28 01:21:25 +04:00
|
|
|
return n
|
|
|
|
|
2006-03-31 22:37:25 +04:00
|
|
|
def walk(self, node=None, files=[], match=util.always, badmatch=None):
|
2005-08-28 01:21:25 +04:00
|
|
|
if node:
|
2005-12-06 16:10:38 +03:00
|
|
|
fdict = dict.fromkeys(files)
|
2005-08-28 01:21:25 +04:00
|
|
|
for fn in self.manifest.read(self.changelog.read(node)[0]):
|
2005-12-06 16:10:38 +03:00
|
|
|
fdict.pop(fn, None)
|
|
|
|
if match(fn):
|
|
|
|
yield 'm', fn
|
|
|
|
for fn in fdict:
|
2006-03-31 22:37:25 +04:00
|
|
|
if badmatch and badmatch(fn):
|
|
|
|
if match(fn):
|
|
|
|
yield 'b', fn
|
|
|
|
else:
|
|
|
|
self.ui.warn(_('%s: No such file in rev %s\n') % (
|
|
|
|
util.pathto(self.getcwd(), fn), short(node)))
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
2006-04-03 21:02:09 +04:00
|
|
|
for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
|
2005-08-28 01:21:25 +04:00
|
|
|
yield src, fn
|
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def changes(self, node1=None, node2=None, files=[], match=util.always,
|
2006-03-30 00:58:34 +04:00
|
|
|
wlock=None, show_ignored=None):
|
2006-01-12 13:32:07 +03:00
|
|
|
"""return changes 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.
|
|
|
|
"""
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def fcmp(fn, mf):
|
|
|
|
t1 = self.wread(fn)
|
|
|
|
t2 = self.file(fn).read(mf.get(fn, nullid))
|
|
|
|
return cmp(t1, t2)
|
|
|
|
|
|
|
|
def mfmatches(node):
|
2006-01-12 13:32:07 +03:00
|
|
|
change = self.changelog.read(node)
|
|
|
|
mf = dict(self.manifest.read(change[0]))
|
2005-08-28 01:21:25 +04:00
|
|
|
for fn in mf.keys():
|
|
|
|
if not match(fn):
|
|
|
|
del mf[fn]
|
|
|
|
return mf
|
|
|
|
|
2006-02-25 15:44:40 +03:00
|
|
|
if node1:
|
|
|
|
# read the manifest from node1 before the manifest from node2,
|
|
|
|
# so that we'll hit the manifest cache if we're going through
|
|
|
|
# all the revisions in parent->child order.
|
|
|
|
mf1 = mfmatches(node1)
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
# are we comparing the working directory?
|
|
|
|
if not node2:
|
2006-02-10 02:18:43 +03:00
|
|
|
if not wlock:
|
|
|
|
try:
|
|
|
|
wlock = self.wlock(wait=0)
|
2006-02-20 03:12:03 +03:00
|
|
|
except lock.LockException:
|
2006-02-10 02:18:43 +03:00
|
|
|
wlock = None
|
2006-03-30 00:58:34 +04:00
|
|
|
lookup, modified, added, removed, deleted, unknown, ignored = (
|
|
|
|
self.dirstate.changes(files, match, show_ignored))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
# are we comparing working dir against its parent?
|
|
|
|
if not node1:
|
2006-01-12 13:32:07 +03:00
|
|
|
if lookup:
|
2005-08-28 01:21:25 +04:00
|
|
|
# do a full compare of any files that might have changed
|
2006-01-12 13:32:07 +03:00
|
|
|
mf2 = mfmatches(self.dirstate.parents()[0])
|
|
|
|
for f in lookup:
|
2005-08-28 01:21:25 +04:00
|
|
|
if fcmp(f, mf2):
|
2006-01-12 13:32:07 +03:00
|
|
|
modified.append(f)
|
2005-11-12 02:34:17 +03:00
|
|
|
elif wlock is not None:
|
|
|
|
self.dirstate.update([f], "n")
|
2006-01-12 13:32:07 +03:00
|
|
|
else:
|
|
|
|
# we are comparing working dir against non-parent
|
|
|
|
# generate a pseudo-manifest for the working dir
|
|
|
|
mf2 = mfmatches(self.dirstate.parents()[0])
|
|
|
|
for f in lookup + modified + added:
|
|
|
|
mf2[f] = ""
|
2006-01-12 14:22:28 +03:00
|
|
|
for f in removed:
|
2006-01-12 13:32:07 +03:00
|
|
|
if f in mf2:
|
|
|
|
del mf2[f]
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
2006-01-12 13:32:07 +03:00
|
|
|
# we are comparing two revisions
|
2006-03-30 00:58:34 +04:00
|
|
|
deleted, unknown, ignored = [], [], []
|
2006-01-12 13:32:07 +03:00
|
|
|
mf2 = mfmatches(node2)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-01-12 13:32:07 +03:00
|
|
|
if node1:
|
|
|
|
# flush lists from dirstate before comparing manifests
|
|
|
|
modified, added = [], []
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-01-12 13:32:07 +03:00
|
|
|
for fn in mf2:
|
|
|
|
if mf1.has_key(fn):
|
|
|
|
if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
|
|
|
|
modified.append(fn)
|
|
|
|
del mf1[fn]
|
|
|
|
else:
|
|
|
|
added.append(fn)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-01-12 14:22:28 +03:00
|
|
|
removed = mf1.keys()
|
|
|
|
|
2006-01-12 13:32:07 +03:00
|
|
|
# sort and return results:
|
2006-03-30 00:58:34 +04:00
|
|
|
for l in modified, added, removed, deleted, unknown, ignored:
|
2005-08-28 01:21:25 +04:00
|
|
|
l.sort()
|
2006-03-30 00:58:34 +04:00
|
|
|
if show_ignored is None:
|
|
|
|
return (modified, added, removed, deleted, unknown)
|
|
|
|
else:
|
|
|
|
return (modified, added, removed, deleted, unknown, ignored)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def add(self, list, wlock=None):
|
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2005-08-28 01:21:25 +04:00
|
|
|
for f in list:
|
|
|
|
p = self.wjoin(f)
|
|
|
|
if not os.path.exists(p):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%s does not exist!\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
elif not os.path.isfile(p):
|
2006-01-12 09:57:58 +03:00
|
|
|
self.ui.warn(_("%s not added: only files supported currently\n")
|
|
|
|
% f)
|
2005-08-28 01:21:25 +04:00
|
|
|
elif self.dirstate.state(f) in 'an':
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%s already tracked!\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
|
|
|
self.dirstate.update([f], "a")
|
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def forget(self, list, wlock=None):
|
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2005-08-28 01:21:25 +04:00
|
|
|
for f in list:
|
|
|
|
if self.dirstate.state(f) not in 'ai':
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%s not added!\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
|
|
|
self.dirstate.forget([f])
|
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def remove(self, list, unlink=False, wlock=None):
|
2005-10-19 11:10:52 +04:00
|
|
|
if unlink:
|
|
|
|
for f in list:
|
|
|
|
try:
|
|
|
|
util.unlink(self.wjoin(f))
|
|
|
|
except OSError, inst:
|
2006-01-12 09:57:58 +03:00
|
|
|
if inst.errno != errno.ENOENT:
|
|
|
|
raise
|
2006-02-10 02:18:43 +03:00
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2005-08-28 01:21:25 +04:00
|
|
|
for f in list:
|
|
|
|
p = self.wjoin(f)
|
|
|
|
if os.path.exists(p):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%s still exists!\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
elif self.dirstate.state(f) == 'a':
|
|
|
|
self.dirstate.forget([f])
|
|
|
|
elif f not in self.dirstate:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%s not tracked!\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
|
|
|
self.dirstate.update([f], "r")
|
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def undelete(self, list, wlock=None):
|
2005-10-26 02:52:27 +04:00
|
|
|
p = self.dirstate.parents()[0]
|
2005-10-26 02:51:28 +04:00
|
|
|
mn = self.changelog.read(p)[0]
|
|
|
|
mf = self.manifest.readflags(mn)
|
|
|
|
m = self.manifest.read(mn)
|
2006-02-10 02:18:43 +03:00
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2005-10-26 02:51:28 +04:00
|
|
|
for f in list:
|
|
|
|
if self.dirstate.state(f) not in "r":
|
|
|
|
self.ui.warn("%s not removed!\n" % f)
|
|
|
|
else:
|
|
|
|
t = self.file(f).read(m[f])
|
2005-10-28 22:01:25 +04:00
|
|
|
self.wwrite(f, t)
|
2005-10-26 02:51:28 +04:00
|
|
|
util.set_exec(self.wjoin(f), mf[f])
|
|
|
|
self.dirstate.update([f], "n")
|
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
def copy(self, source, dest, wlock=None):
|
2005-08-28 01:21:25 +04:00
|
|
|
p = self.wjoin(dest)
|
|
|
|
if not os.path.exists(p):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%s does not exist!\n") % dest)
|
2005-08-28 01:21:25 +04:00
|
|
|
elif not os.path.isfile(p):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("copy failed: %s is not a file\n") % dest)
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
2006-02-10 02:18:43 +03:00
|
|
|
if not wlock:
|
|
|
|
wlock = self.wlock()
|
2005-08-28 01:21:25 +04:00
|
|
|
if self.dirstate.state(dest) == '?':
|
|
|
|
self.dirstate.update([dest], "a")
|
|
|
|
self.dirstate.copy(source, dest)
|
|
|
|
|
2005-11-16 14:56:19 +03:00
|
|
|
def heads(self, start=None):
|
2005-11-16 14:08:25 +03:00
|
|
|
heads = self.changelog.heads(start)
|
|
|
|
# sort the output in rev descending order
|
|
|
|
heads = [(-self.changelog.rev(h), h) for h in heads]
|
|
|
|
heads.sort()
|
|
|
|
return [n for (r, n) in heads]
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
# branchlookup returns a dict giving a list of branches for
|
|
|
|
# each head. A branch is defined as the tag of a node or
|
|
|
|
# the branch of the node's parents. If a node has multiple
|
|
|
|
# branch tags, tags are eliminated if they are visible from other
|
|
|
|
# branch tags.
|
|
|
|
#
|
|
|
|
# So, for this graph: a->b->c->d->e
|
|
|
|
# \ /
|
|
|
|
# aa -----/
|
|
|
|
# a has tag 2.6.12
|
|
|
|
# d has tag 2.6.13
|
|
|
|
# e would have branch tags for 2.6.12 and 2.6.13. Because the node
|
|
|
|
# for 2.6.12 can be reached from the node 2.6.13, that is eliminated
|
|
|
|
# from the list.
|
|
|
|
#
|
|
|
|
# It is possible that more than one head will have the same branch tag.
|
|
|
|
# callers need to check the result for multiple heads under the same
|
|
|
|
# branch tag if that is a problem for them (ie checkout of a specific
|
|
|
|
# branch).
|
|
|
|
#
|
|
|
|
# passing in a specific branch will limit the depth of the search
|
|
|
|
# through the parents. It won't limit the branches returned in the
|
|
|
|
# result though.
|
|
|
|
def branchlookup(self, heads=None, branch=None):
|
|
|
|
if not heads:
|
|
|
|
heads = self.heads()
|
|
|
|
headt = [ h for h in heads ]
|
|
|
|
chlog = self.changelog
|
|
|
|
branches = {}
|
|
|
|
merges = []
|
|
|
|
seenmerge = {}
|
|
|
|
|
|
|
|
# traverse the tree once for each head, recording in the branches
|
|
|
|
# dict which tags are visible from this head. The branches
|
|
|
|
# dict also records which tags are visible from each tag
|
|
|
|
# while we traverse.
|
|
|
|
while headt or merges:
|
|
|
|
if merges:
|
|
|
|
n, found = merges.pop()
|
|
|
|
visit = [n]
|
|
|
|
else:
|
|
|
|
h = headt.pop()
|
|
|
|
visit = [h]
|
|
|
|
found = [h]
|
|
|
|
seen = {}
|
|
|
|
while visit:
|
|
|
|
n = visit.pop()
|
|
|
|
if n in seen:
|
|
|
|
continue
|
|
|
|
pp = chlog.parents(n)
|
|
|
|
tags = self.nodetags(n)
|
|
|
|
if tags:
|
|
|
|
for x in tags:
|
|
|
|
if x == 'tip':
|
|
|
|
continue
|
|
|
|
for f in found:
|
|
|
|
branches.setdefault(f, {})[n] = 1
|
|
|
|
branches.setdefault(n, {})[n] = 1
|
|
|
|
break
|
|
|
|
if n not in found:
|
|
|
|
found.append(n)
|
|
|
|
if branch in tags:
|
|
|
|
continue
|
|
|
|
seen[n] = 1
|
|
|
|
if pp[1] != nullid and n not in seenmerge:
|
|
|
|
merges.append((pp[1], [x for x in found]))
|
|
|
|
seenmerge[n] = 1
|
|
|
|
if pp[0] != nullid:
|
|
|
|
visit.append(pp[0])
|
|
|
|
# traverse the branches dict, eliminating branch tags from each
|
|
|
|
# head that are visible from another branch tag for that head.
|
|
|
|
out = {}
|
|
|
|
viscache = {}
|
|
|
|
for h in heads:
|
|
|
|
def visible(node):
|
|
|
|
if node in viscache:
|
|
|
|
return viscache[node]
|
|
|
|
ret = {}
|
|
|
|
visit = [node]
|
|
|
|
while visit:
|
|
|
|
x = visit.pop()
|
|
|
|
if x in viscache:
|
|
|
|
ret.update(viscache[x])
|
|
|
|
elif x not in ret:
|
|
|
|
ret[x] = 1
|
|
|
|
if x in branches:
|
|
|
|
visit[len(visit):] = branches[x].keys()
|
|
|
|
viscache[node] = ret
|
|
|
|
return ret
|
|
|
|
if h not in branches:
|
|
|
|
continue
|
|
|
|
# O(n^2), but somewhat limited. This only searches the
|
|
|
|
# tags visible from a specific head, not all the tags in the
|
|
|
|
# whole repo.
|
|
|
|
for b in branches[h]:
|
|
|
|
vis = False
|
|
|
|
for bb in branches[h].keys():
|
|
|
|
if b != bb:
|
|
|
|
if b in visible(bb):
|
|
|
|
vis = True
|
|
|
|
break
|
|
|
|
if not vis:
|
|
|
|
l = out.setdefault(h, [])
|
|
|
|
l[len(l):] = self.nodetags(b)
|
|
|
|
return out
|
|
|
|
|
|
|
|
def branches(self, nodes):
|
2006-01-12 09:57:58 +03:00
|
|
|
if not nodes:
|
|
|
|
nodes = [self.changelog.tip()]
|
2005-08-28 01:21:25 +04:00
|
|
|
b = []
|
|
|
|
for n in nodes:
|
|
|
|
t = n
|
|
|
|
while n:
|
|
|
|
p = self.changelog.parents(n)
|
|
|
|
if p[1] != nullid or p[0] == nullid:
|
|
|
|
b.append((t, n, p[0], p[1]))
|
|
|
|
break
|
|
|
|
n = p[0]
|
|
|
|
return b
|
|
|
|
|
|
|
|
def between(self, pairs):
|
|
|
|
r = []
|
|
|
|
|
|
|
|
for top, bottom in pairs:
|
|
|
|
n, l, i = top, [], 0
|
|
|
|
f = 1
|
|
|
|
|
|
|
|
while n != bottom:
|
|
|
|
p = self.changelog.parents(n)[0]
|
|
|
|
if i == f:
|
|
|
|
l.append(n)
|
|
|
|
f = f * 2
|
|
|
|
n = p
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
r.append(l)
|
|
|
|
|
|
|
|
return r
|
|
|
|
|
2006-03-15 09:58:14 +03:00
|
|
|
def findincoming(self, remote, base=None, heads=None, force=False):
|
2005-08-28 01:21:25 +04:00
|
|
|
m = self.changelog.nodemap
|
|
|
|
search = []
|
|
|
|
fetch = {}
|
|
|
|
seen = {}
|
|
|
|
seenbranch = {}
|
|
|
|
if base == None:
|
|
|
|
base = {}
|
|
|
|
|
2006-04-22 00:33:51 +04:00
|
|
|
if not heads:
|
|
|
|
heads = remote.heads()
|
|
|
|
|
|
|
|
if self.changelog.tip() == nullid:
|
|
|
|
if heads != [nullid]:
|
|
|
|
return [nullid]
|
|
|
|
return []
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
# assume we're closer to the tip than the root
|
|
|
|
# and start by examining the heads
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("searching for changes\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
unknown = []
|
|
|
|
for h in heads:
|
|
|
|
if h not in m:
|
|
|
|
unknown.append(h)
|
|
|
|
else:
|
|
|
|
base[h] = 1
|
|
|
|
|
|
|
|
if not unknown:
|
2006-03-13 02:02:33 +03:00
|
|
|
return []
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
rep = {}
|
|
|
|
reqcnt = 0
|
|
|
|
|
|
|
|
# search through remote branches
|
|
|
|
# a 'branch' here is a linear segment of history, with four parts:
|
|
|
|
# head, root, first parent, second parent
|
|
|
|
# (a branch always has two parents (or none) by definition)
|
|
|
|
unknown = remote.branches(unknown)
|
|
|
|
while unknown:
|
|
|
|
r = []
|
|
|
|
while unknown:
|
|
|
|
n = unknown.pop(0)
|
|
|
|
if n[0] in seen:
|
|
|
|
continue
|
|
|
|
|
2006-01-12 09:57:58 +03:00
|
|
|
self.ui.debug(_("examining %s:%s\n")
|
|
|
|
% (short(n[0]), short(n[1])))
|
2005-08-28 01:21:25 +04:00
|
|
|
if n[0] == nullid:
|
|
|
|
break
|
|
|
|
if n in seenbranch:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("branch already found\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
continue
|
|
|
|
if n[1] and n[1] in m: # do we know the base?
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("found incomplete branch %s:%s\n")
|
2005-08-28 01:21:25 +04:00
|
|
|
% (short(n[0]), short(n[1])))
|
|
|
|
search.append(n) # schedule branch range for scanning
|
|
|
|
seenbranch[n] = 1
|
|
|
|
else:
|
|
|
|
if n[1] not in seen and n[1] not in fetch:
|
|
|
|
if n[2] in m and n[3] in m:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("found new changeset %s\n") %
|
2005-08-28 01:21:25 +04:00
|
|
|
short(n[1]))
|
|
|
|
fetch[n[1]] = 1 # earliest unknown
|
|
|
|
base[n[2]] = 1 # latest known
|
|
|
|
continue
|
|
|
|
|
|
|
|
for a in n[2:4]:
|
|
|
|
if a not in rep:
|
|
|
|
r.append(a)
|
|
|
|
rep[a] = 1
|
|
|
|
|
|
|
|
seen[n[0]] = 1
|
|
|
|
|
|
|
|
if r:
|
|
|
|
reqcnt += 1
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("request %d: %s\n") %
|
2005-08-28 01:21:25 +04:00
|
|
|
(reqcnt, " ".join(map(short, r))))
|
|
|
|
for p in range(0, len(r), 10):
|
|
|
|
for b in remote.branches(r[p:p+10]):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("received %s:%s\n") %
|
2005-08-28 01:21:25 +04:00
|
|
|
(short(b[0]), short(b[1])))
|
|
|
|
if b[0] in m:
|
2006-01-12 09:57:58 +03:00
|
|
|
self.ui.debug(_("found base node %s\n")
|
|
|
|
% short(b[0]))
|
2005-08-28 01:21:25 +04:00
|
|
|
base[b[0]] = 1
|
|
|
|
elif b[0] not in seen:
|
|
|
|
unknown.append(b)
|
|
|
|
|
|
|
|
# do binary search on the branches we found
|
|
|
|
while search:
|
|
|
|
n = search.pop(0)
|
|
|
|
reqcnt += 1
|
|
|
|
l = remote.between([(n[0], n[1])])[0]
|
|
|
|
l.append(n[1])
|
|
|
|
p = n[0]
|
|
|
|
f = 1
|
|
|
|
for i in l:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
|
2005-08-28 01:21:25 +04:00
|
|
|
if i in m:
|
|
|
|
if f <= 2:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("found new branch changeset %s\n") %
|
2005-08-28 01:21:25 +04:00
|
|
|
short(p))
|
|
|
|
fetch[p] = 1
|
|
|
|
base[i] = 1
|
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("narrowed branch search to %s:%s\n")
|
2005-08-28 01:21:25 +04:00
|
|
|
% (short(p), short(i)))
|
|
|
|
search.append((p, i))
|
|
|
|
break
|
|
|
|
p, f = i, f * 2
|
|
|
|
|
|
|
|
# sanity check our fetch list
|
|
|
|
for f in fetch.keys():
|
|
|
|
if f in m:
|
2005-10-19 05:38:39 +04:00
|
|
|
raise repo.RepoError(_("already have changeset ") + short(f[:4]))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
if base.keys() == [nullid]:
|
2006-03-15 09:58:14 +03:00
|
|
|
if force:
|
|
|
|
self.ui.warn(_("warning: repository is unrelated\n"))
|
|
|
|
else:
|
|
|
|
raise util.Abort(_("repository is unrelated"))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.note(_("found new changesets starting at ") +
|
2005-08-28 01:21:25 +04:00
|
|
|
" ".join([short(f) for f in fetch]) + "\n")
|
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("%d total queries\n") % reqcnt)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
return fetch.keys()
|
|
|
|
|
2006-03-15 09:58:14 +03:00
|
|
|
def findoutgoing(self, remote, base=None, heads=None, force=False):
|
2006-03-30 00:35:21 +04:00
|
|
|
"""Return list of nodes that are roots of subsets not in remote
|
|
|
|
|
|
|
|
If base dict is specified, assume that these nodes and their parents
|
|
|
|
exist on the remote side.
|
|
|
|
If a list of heads is specified, return only nodes which are heads
|
|
|
|
or ancestors of these heads, and return a second element which
|
|
|
|
contains all remote heads which get new children.
|
|
|
|
"""
|
2005-08-28 01:21:25 +04:00
|
|
|
if base == None:
|
|
|
|
base = {}
|
2006-03-15 09:58:14 +03:00
|
|
|
self.findincoming(remote, base, heads, force=force)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("common changesets up to ")
|
2005-08-28 01:21:25 +04:00
|
|
|
+ " ".join(map(short, base.keys())) + "\n")
|
|
|
|
|
|
|
|
remain = dict.fromkeys(self.changelog.nodemap)
|
|
|
|
|
|
|
|
# prune everything remote has from the tree
|
|
|
|
del remain[nullid]
|
|
|
|
remove = base.keys()
|
|
|
|
while remove:
|
|
|
|
n = remove.pop(0)
|
|
|
|
if n in remain:
|
|
|
|
del remain[n]
|
|
|
|
for p in self.changelog.parents(n):
|
|
|
|
remove.append(p)
|
|
|
|
|
|
|
|
# find every node whose parents have been pruned
|
|
|
|
subset = []
|
2006-03-30 00:35:21 +04:00
|
|
|
# find every remote head that will get new children
|
|
|
|
updated_heads = {}
|
2005-08-28 01:21:25 +04:00
|
|
|
for n in remain:
|
|
|
|
p1, p2 = self.changelog.parents(n)
|
|
|
|
if p1 not in remain and p2 not in remain:
|
|
|
|
subset.append(n)
|
2006-03-30 00:35:21 +04:00
|
|
|
if heads:
|
|
|
|
if p1 in heads:
|
|
|
|
updated_heads[p1] = True
|
|
|
|
if p2 in heads:
|
|
|
|
updated_heads[p2] = True
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
# this is the set of all roots we have to push
|
2006-03-30 00:35:21 +04:00
|
|
|
if heads:
|
|
|
|
return subset, updated_heads.keys()
|
|
|
|
else:
|
|
|
|
return subset
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-03-15 09:58:14 +03:00
|
|
|
def pull(self, remote, heads=None, force=False):
|
2006-02-19 21:43:03 +03:00
|
|
|
l = self.lock()
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-04-22 00:33:51 +04:00
|
|
|
fetch = self.findincoming(remote, force=force)
|
|
|
|
if fetch == [nullid]:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("requesting all changes\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
if not fetch:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("no changes found\n"))
|
2006-03-29 22:27:16 +04:00
|
|
|
return 0
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-08 06:51:09 +04:00
|
|
|
if heads is None:
|
2006-02-17 19:26:21 +03:00
|
|
|
cg = remote.changegroup(fetch, 'pull')
|
2005-10-08 06:51:09 +04:00
|
|
|
else:
|
2006-02-17 19:26:21 +03:00
|
|
|
cg = remote.changegroupsubset(fetch, heads, 'pull')
|
2006-05-09 03:50:27 +04:00
|
|
|
return self.addchangegroup(cg, 'pull')
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-14 23:11:57 +03:00
|
|
|
def push(self, remote, force=False, revs=None):
|
2005-08-28 01:21:25 +04:00
|
|
|
lock = remote.lock()
|
|
|
|
|
|
|
|
base = {}
|
2006-03-30 00:35:21 +04:00
|
|
|
remote_heads = remote.heads()
|
|
|
|
inc = self.findincoming(remote, base, remote_heads, force=force)
|
2005-08-28 01:21:25 +04:00
|
|
|
if not force and inc:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("abort: unsynced remote changes!\n"))
|
2006-03-30 00:35:21 +04:00
|
|
|
self.ui.status(_("(did you forget to sync?"
|
|
|
|
" use push -f to force)\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
return 1
|
|
|
|
|
2006-03-30 00:35:21 +04:00
|
|
|
update, updated_heads = self.findoutgoing(remote, base, remote_heads)
|
2006-02-14 23:11:57 +03:00
|
|
|
if revs is not None:
|
|
|
|
msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
|
|
|
|
else:
|
|
|
|
bases, heads = update, self.changelog.heads()
|
|
|
|
|
|
|
|
if not bases:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("no changes found\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
return 1
|
|
|
|
elif not force:
|
2006-04-22 01:30:23 +04:00
|
|
|
# FIXME we don't properly detect creation of new heads
|
|
|
|
# in the push -r case, assume the user knows what he's doing
|
|
|
|
if not revs and len(remote_heads) < len(heads) \
|
|
|
|
and remote_heads != [nullid]:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("abort: push creates new remote branches!\n"))
|
|
|
|
self.ui.status(_("(did you forget to merge?"
|
|
|
|
" use push -f to force)\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
return 1
|
|
|
|
|
2006-02-14 23:11:57 +03:00
|
|
|
if revs is None:
|
2006-02-21 18:46:38 +03:00
|
|
|
cg = self.changegroup(update, 'push')
|
2006-02-14 23:11:57 +03:00
|
|
|
else:
|
2006-02-21 18:46:38 +03:00
|
|
|
cg = self.changegroupsubset(update, revs, 'push')
|
2006-05-09 03:50:27 +04:00
|
|
|
return remote.addchangegroup(cg, 'push')
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-17 19:26:21 +03:00
|
|
|
def changegroupsubset(self, bases, heads, source):
|
2005-10-12 05:56:47 +04:00
|
|
|
"""This function generates a changegroup consisting of all the nodes
|
|
|
|
that are descendents of any of the bases, and ancestors of any of
|
|
|
|
the heads.
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
It is fairly complex as determining which filenodes and which
|
|
|
|
manifest nodes need to be included for the changeset to be complete
|
|
|
|
is non-trivial.
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
Another wrinkle is doing the reverse, figuring out which changeset in
|
|
|
|
the changegroup a particular filenode or manifestnode belongs to."""
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-17 19:26:21 +03:00
|
|
|
self.hook('preoutgoing', throw=True, source=source)
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# Set up some initial variables
|
|
|
|
# Make it easy to refer to self.changelog
|
2005-10-07 21:57:11 +04:00
|
|
|
cl = self.changelog
|
2005-10-12 05:56:47 +04:00
|
|
|
# msng is short for missing - compute the list of changesets in this
|
|
|
|
# changegroup.
|
2005-10-08 06:49:25 +04:00
|
|
|
msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
|
2005-10-12 05:56:47 +04:00
|
|
|
# Some bases may turn out to be superfluous, and some heads may be
|
|
|
|
# too. nodesbetween will return the minimal set of bases and heads
|
|
|
|
# necessary to re-create the changegroup.
|
|
|
|
|
|
|
|
# Known heads are the list of heads that it is assumed the recipient
|
|
|
|
# of this changegroup will know about.
|
2005-10-07 21:57:11 +04:00
|
|
|
knownheads = {}
|
2005-10-12 05:56:47 +04:00
|
|
|
# We assume that all parents of bases are known heads.
|
2005-10-08 06:49:25 +04:00
|
|
|
for n in bases:
|
2005-10-07 21:57:11 +04:00
|
|
|
for p in cl.parents(n):
|
|
|
|
if p != nullid:
|
|
|
|
knownheads[p] = 1
|
|
|
|
knownheads = knownheads.keys()
|
2005-10-08 06:49:25 +04:00
|
|
|
if knownheads:
|
2005-10-12 05:56:47 +04:00
|
|
|
# Now that we know what heads are known, we can compute which
|
|
|
|
# changesets are known. The recipient must know about all
|
|
|
|
# changesets required to reach the known heads from the null
|
|
|
|
# changeset.
|
2005-10-08 06:49:25 +04:00
|
|
|
has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
|
2005-10-12 05:56:47 +04:00
|
|
|
junk = None
|
|
|
|
# Transform the list into an ersatz set.
|
2005-10-11 19:06:52 +04:00
|
|
|
has_cl_set = dict.fromkeys(has_cl_set)
|
2005-10-08 06:49:25 +04:00
|
|
|
else:
|
2005-10-12 05:56:47 +04:00
|
|
|
# If there were no known heads, the recipient cannot be assumed to
|
|
|
|
# know about any changesets.
|
2005-10-08 06:49:25 +04:00
|
|
|
has_cl_set = {}
|
2005-10-07 21:57:11 +04:00
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# Make it easy to refer to self.manifest
|
2005-10-07 21:57:11 +04:00
|
|
|
mnfst = self.manifest
|
2005-10-12 05:56:47 +04:00
|
|
|
# We don't know which manifests are missing yet
|
2005-10-07 21:57:11 +04:00
|
|
|
msng_mnfst_set = {}
|
2005-10-12 05:56:47 +04:00
|
|
|
# Nor do we know which filenodes are missing.
|
2005-10-07 21:57:11 +04:00
|
|
|
msng_filenode_set = {}
|
|
|
|
|
2005-10-08 06:49:25 +04:00
|
|
|
junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
|
|
|
|
junk = None
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# A changeset always belongs to itself, so the changenode lookup
|
|
|
|
# function for a changenode is identity.
|
2005-10-07 21:57:11 +04:00
|
|
|
def identity(x):
|
|
|
|
return x
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# A function generating function. Sets up an environment for the
|
|
|
|
# inner function.
|
2005-10-07 21:57:11 +04:00
|
|
|
def cmp_by_rev_func(revlog):
|
2005-10-12 05:56:47 +04:00
|
|
|
# Compare two nodes by their revision number in the environment's
|
|
|
|
# revision history. Since the revision number both represents the
|
|
|
|
# most efficient order to read the nodes in, and represents a
|
|
|
|
# topological sorting of the nodes, this function is often useful.
|
|
|
|
def cmp_by_rev(a, b):
|
2005-10-07 21:57:11 +04:00
|
|
|
return cmp(revlog.rev(a), revlog.rev(b))
|
2005-10-12 05:56:47 +04:00
|
|
|
return cmp_by_rev
|
2005-10-07 21:57:11 +04:00
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# If we determine that a particular file or manifest node must be a
|
|
|
|
# node that the recipient of the changegroup will already have, we can
|
|
|
|
# also assume the recipient will have all the parents. This function
|
|
|
|
# prunes them from the set of missing nodes.
|
2005-10-07 21:57:11 +04:00
|
|
|
def prune_parents(revlog, hasset, msngset):
|
|
|
|
haslst = hasset.keys()
|
|
|
|
haslst.sort(cmp_by_rev_func(revlog))
|
|
|
|
for node in haslst:
|
|
|
|
parentlst = [p for p in revlog.parents(node) if p != nullid]
|
|
|
|
while parentlst:
|
|
|
|
n = parentlst.pop()
|
|
|
|
if n not in hasset:
|
|
|
|
hasset[n] = 1
|
|
|
|
p = [p for p in revlog.parents(n) if p != nullid]
|
|
|
|
parentlst.extend(p)
|
|
|
|
for n in hasset:
|
|
|
|
msngset.pop(n, None)
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# This is a function generating function used to set up an environment
|
|
|
|
# for the inner function to execute in.
|
2005-10-07 21:57:11 +04:00
|
|
|
def manifest_and_file_collector(changedfileset):
|
2005-10-12 05:56:47 +04:00
|
|
|
# This is an information gathering function that gathers
|
|
|
|
# information from each changeset node that goes out as part of
|
|
|
|
# the changegroup. The information gathered is a list of which
|
|
|
|
# manifest nodes are potentially required (the recipient may
|
|
|
|
# already have them) and total list of all files which were
|
|
|
|
# changed in any changeset in the changegroup.
|
|
|
|
#
|
|
|
|
# We also remember the first changenode we saw any manifest
|
|
|
|
# referenced by so we can later determine which changenode 'owns'
|
|
|
|
# the manifest.
|
2005-10-07 21:57:11 +04:00
|
|
|
def collect_manifests_and_files(clnode):
|
|
|
|
c = cl.read(clnode)
|
2005-08-28 01:21:25 +04:00
|
|
|
for f in c[3]:
|
2005-10-07 21:57:11 +04:00
|
|
|
# This is to make sure we only have one instance of each
|
|
|
|
# filename string for each filename.
|
2005-10-08 06:49:25 +04:00
|
|
|
changedfileset.setdefault(f, f)
|
|
|
|
msng_mnfst_set.setdefault(c[0], clnode)
|
2005-10-07 21:57:11 +04:00
|
|
|
return collect_manifests_and_files
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# Figure out which manifest nodes (of the ones we think might be part
|
|
|
|
# of the changegroup) the recipient must know about and remove them
|
|
|
|
# from the changegroup.
|
2005-10-07 21:57:11 +04:00
|
|
|
def prune_manifests():
|
|
|
|
has_mnfst_set = {}
|
|
|
|
for n in msng_mnfst_set:
|
2005-10-12 05:56:47 +04:00
|
|
|
# If a 'missing' manifest thinks it belongs to a changenode
|
|
|
|
# the recipient is assumed to have, obviously the recipient
|
|
|
|
# must have that manifest.
|
2005-10-07 21:57:11 +04:00
|
|
|
linknode = cl.node(mnfst.linkrev(n))
|
|
|
|
if linknode in has_cl_set:
|
|
|
|
has_mnfst_set[n] = 1
|
|
|
|
prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# Use the information collected in collect_manifests_and_files to say
|
|
|
|
# which changenode any manifestnode belongs to.
|
2005-10-07 21:57:11 +04:00
|
|
|
def lookup_manifest_link(mnfstnode):
|
|
|
|
return msng_mnfst_set[mnfstnode]
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# A function generating function that sets up the initial environment
|
|
|
|
# the inner function.
|
2005-10-07 21:57:11 +04:00
|
|
|
def filenode_collector(changedfiles):
|
2005-10-10 19:36:29 +04:00
|
|
|
next_rev = [0]
|
2005-10-12 05:56:47 +04:00
|
|
|
# This gathers information from each manifestnode included in the
|
|
|
|
# changegroup about which filenodes the manifest node references
|
|
|
|
# so we can include those in the changegroup too.
|
|
|
|
#
|
|
|
|
# It also remembers which changenode each filenode belongs to. It
|
|
|
|
# does this by assuming the a filenode belongs to the changenode
|
|
|
|
# the first manifest that references it belongs to.
|
2005-10-07 21:57:11 +04:00
|
|
|
def collect_msng_filenodes(mnfstnode):
|
2005-10-10 19:36:29 +04:00
|
|
|
r = mnfst.rev(mnfstnode)
|
|
|
|
if r == next_rev[0]:
|
|
|
|
# If the last rev we looked at was the one just previous,
|
|
|
|
# we only need to see a diff.
|
|
|
|
delta = mdiff.patchtext(mnfst.delta(mnfstnode))
|
2005-10-12 05:56:47 +04:00
|
|
|
# For each line in the delta
|
2005-10-10 19:36:29 +04:00
|
|
|
for dline in delta.splitlines():
|
2005-10-12 05:56:47 +04:00
|
|
|
# get the filename and filenode for that line
|
2005-10-10 19:36:29 +04:00
|
|
|
f, fnode = dline.split('\0')
|
|
|
|
fnode = bin(fnode[:40])
|
|
|
|
f = changedfiles.get(f, None)
|
2005-10-12 05:56:47 +04:00
|
|
|
# And if the file is in the list of files we care
|
|
|
|
# about.
|
2005-10-10 19:36:29 +04:00
|
|
|
if f is not None:
|
2005-10-12 05:56:47 +04:00
|
|
|
# Get the changenode this manifest belongs to
|
2005-10-10 19:36:29 +04:00
|
|
|
clnode = msng_mnfst_set[mnfstnode]
|
2005-10-12 05:56:47 +04:00
|
|
|
# Create the set of filenodes for the file if
|
|
|
|
# there isn't one already.
|
2005-10-10 19:36:29 +04:00
|
|
|
ndset = msng_filenode_set.setdefault(f, {})
|
2005-10-12 05:56:47 +04:00
|
|
|
# And set the filenode's changelog node to the
|
|
|
|
# manifest's if it hasn't been set already.
|
2005-10-10 19:36:29 +04:00
|
|
|
ndset.setdefault(fnode, clnode)
|
|
|
|
else:
|
2005-10-12 05:56:47 +04:00
|
|
|
# Otherwise we need a full manifest.
|
2005-10-10 19:36:29 +04:00
|
|
|
m = mnfst.read(mnfstnode)
|
2005-10-12 05:56:47 +04:00
|
|
|
# For every file in we care about.
|
2005-10-10 19:36:29 +04:00
|
|
|
for f in changedfiles:
|
|
|
|
fnode = m.get(f, None)
|
2005-10-12 05:56:47 +04:00
|
|
|
# If it's in the manifest
|
2005-10-10 19:36:29 +04:00
|
|
|
if fnode is not None:
|
2005-10-12 05:56:47 +04:00
|
|
|
# See comments above.
|
2005-10-10 19:36:29 +04:00
|
|
|
clnode = msng_mnfst_set[mnfstnode]
|
|
|
|
ndset = msng_filenode_set.setdefault(f, {})
|
|
|
|
ndset.setdefault(fnode, clnode)
|
2005-10-12 05:56:47 +04:00
|
|
|
# Remember the revision we hope to see next.
|
2005-10-10 19:36:29 +04:00
|
|
|
next_rev[0] = r + 1
|
2005-10-08 06:49:25 +04:00
|
|
|
return collect_msng_filenodes
|
2005-10-07 21:57:11 +04:00
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# We have a list of filenodes we think we need for a file, lets remove
|
|
|
|
# all those we now the recipient must have.
|
2005-10-07 21:57:11 +04:00
|
|
|
def prune_filenodes(f, filerevlog):
|
|
|
|
msngset = msng_filenode_set[f]
|
|
|
|
hasset = {}
|
2005-10-12 05:56:47 +04:00
|
|
|
# If a 'missing' filenode thinks it belongs to a changenode we
|
|
|
|
# assume the recipient must have, then the recipient must have
|
|
|
|
# that filenode.
|
2005-10-07 21:57:11 +04:00
|
|
|
for n in msngset:
|
|
|
|
clnode = cl.node(filerevlog.linkrev(n))
|
|
|
|
if clnode in has_cl_set:
|
|
|
|
hasset[n] = 1
|
|
|
|
prune_parents(filerevlog, hasset, msngset)
|
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# A function generator function that sets up the a context for the
|
|
|
|
# inner function.
|
2005-10-07 21:57:11 +04:00
|
|
|
def lookup_filenode_link_func(fname):
|
|
|
|
msngset = msng_filenode_set[fname]
|
2005-10-12 05:56:47 +04:00
|
|
|
# Lookup the changenode the filenode belongs to.
|
2005-10-07 21:57:11 +04:00
|
|
|
def lookup_filenode_link(fnode):
|
|
|
|
return msngset[fnode]
|
|
|
|
return lookup_filenode_link
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-12 05:56:47 +04:00
|
|
|
# Now that we have all theses utility functions to help out and
|
|
|
|
# logically divide up the task, generate the group.
|
2005-08-28 01:21:25 +04:00
|
|
|
def gengroup():
|
2005-10-12 05:56:47 +04:00
|
|
|
# The set of changed files starts empty.
|
2005-10-07 21:57:11 +04:00
|
|
|
changedfiles = {}
|
2005-10-12 05:56:47 +04:00
|
|
|
# Create a changenode group generator that will call our functions
|
|
|
|
# back to lookup the owning changenode and collect information.
|
2005-10-07 21:57:11 +04:00
|
|
|
group = cl.group(msng_cl_lst, identity,
|
|
|
|
manifest_and_file_collector(changedfiles))
|
|
|
|
for chnk in group:
|
|
|
|
yield chnk
|
2005-10-12 05:56:47 +04:00
|
|
|
|
|
|
|
# The list of manifests has been collected by the generator
|
|
|
|
# calling our functions back.
|
2005-10-07 21:57:11 +04:00
|
|
|
prune_manifests()
|
|
|
|
msng_mnfst_lst = msng_mnfst_set.keys()
|
2005-10-12 05:56:47 +04:00
|
|
|
# Sort the manifestnodes by revision number.
|
2005-10-07 21:57:11 +04:00
|
|
|
msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
|
2005-10-12 05:56:47 +04:00
|
|
|
# Create a generator for the manifestnodes that calls our lookup
|
|
|
|
# and data collection functions back.
|
2005-10-08 06:49:25 +04:00
|
|
|
group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
|
2005-10-07 21:57:11 +04:00
|
|
|
filenode_collector(changedfiles))
|
|
|
|
for chnk in group:
|
|
|
|
yield chnk
|
2005-10-12 05:56:47 +04:00
|
|
|
|
|
|
|
# These are no longer needed, dereference and toss the memory for
|
|
|
|
# them.
|
2005-10-07 21:57:11 +04:00
|
|
|
msng_mnfst_lst = None
|
|
|
|
msng_mnfst_set.clear()
|
2005-10-12 05:56:47 +04:00
|
|
|
|
2005-10-10 19:36:29 +04:00
|
|
|
changedfiles = changedfiles.keys()
|
|
|
|
changedfiles.sort()
|
2005-10-12 05:56:47 +04:00
|
|
|
# Go through all our files in order sorted by name.
|
2005-10-07 21:57:11 +04:00
|
|
|
for fname in changedfiles:
|
|
|
|
filerevlog = self.file(fname)
|
2005-10-12 05:56:47 +04:00
|
|
|
# Toss out the filenodes that the recipient isn't really
|
|
|
|
# missing.
|
2006-01-20 20:35:43 +03:00
|
|
|
if msng_filenode_set.has_key(fname):
|
|
|
|
prune_filenodes(fname, filerevlog)
|
|
|
|
msng_filenode_lst = msng_filenode_set[fname].keys()
|
|
|
|
else:
|
|
|
|
msng_filenode_lst = []
|
2005-10-12 05:56:47 +04:00
|
|
|
# If any filenodes are left, generate the group for them,
|
|
|
|
# otherwise don't bother.
|
2005-10-07 21:57:11 +04:00
|
|
|
if len(msng_filenode_lst) > 0:
|
2006-03-21 13:47:21 +03:00
|
|
|
yield changegroup.genchunk(fname)
|
2005-10-12 05:56:47 +04:00
|
|
|
# Sort the filenodes by their revision #
|
2005-10-07 21:57:11 +04:00
|
|
|
msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
|
2005-10-12 05:56:47 +04:00
|
|
|
# Create a group generator and only pass in a changenode
|
|
|
|
# lookup function as we need to collect no information
|
|
|
|
# from filenodes.
|
2005-10-07 21:57:11 +04:00
|
|
|
group = filerevlog.group(msng_filenode_lst,
|
2005-10-08 06:49:25 +04:00
|
|
|
lookup_filenode_link_func(fname))
|
2005-10-07 21:57:11 +04:00
|
|
|
for chnk in group:
|
|
|
|
yield chnk
|
2006-01-20 20:35:43 +03:00
|
|
|
if msng_filenode_set.has_key(fname):
|
|
|
|
# Don't need this anymore, toss it to free memory.
|
|
|
|
del msng_filenode_set[fname]
|
2005-10-12 05:56:47 +04:00
|
|
|
# Signal that no more groups are left.
|
2006-03-21 13:47:21 +03:00
|
|
|
yield changegroup.closechunk()
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-04-28 13:36:33 +04:00
|
|
|
if msng_cl_lst:
|
2006-04-28 09:29:02 +04:00
|
|
|
self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
|
2006-02-17 19:26:21 +03:00
|
|
|
|
2005-10-07 21:57:11 +04:00
|
|
|
return util.chunkbuffer(gengroup())
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-17 19:26:21 +03:00
|
|
|
def changegroup(self, basenodes, source):
|
2005-10-12 05:56:47 +04:00
|
|
|
"""Generate a changegroup of all nodes that we have that a recipient
|
|
|
|
doesn't.
|
|
|
|
|
|
|
|
This is much easier than the previous function as we can assume that
|
|
|
|
the recipient has any changenode we aren't sending them."""
|
2006-02-17 19:26:21 +03:00
|
|
|
|
|
|
|
self.hook('preoutgoing', throw=True, source=source)
|
|
|
|
|
2005-10-07 21:57:11 +04:00
|
|
|
cl = self.changelog
|
|
|
|
nodes = cl.nodesbetween(basenodes, None)[0]
|
|
|
|
revset = dict.fromkeys([cl.rev(n) for n in nodes])
|
|
|
|
|
|
|
|
def identity(x):
|
|
|
|
return x
|
|
|
|
|
|
|
|
def gennodelst(revlog):
|
|
|
|
for r in xrange(0, revlog.count()):
|
|
|
|
n = revlog.node(r)
|
|
|
|
if revlog.linkrev(n) in revset:
|
|
|
|
yield n
|
|
|
|
|
|
|
|
def changed_file_collector(changedfileset):
|
|
|
|
def collect_changed_files(clnode):
|
|
|
|
c = cl.read(clnode)
|
|
|
|
for fname in c[3]:
|
|
|
|
changedfileset[fname] = 1
|
|
|
|
return collect_changed_files
|
|
|
|
|
|
|
|
def lookuprevlink_func(revlog):
|
|
|
|
def lookuprevlink(n):
|
|
|
|
return cl.node(revlog.linkrev(n))
|
|
|
|
return lookuprevlink
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-07 21:57:11 +04:00
|
|
|
def gengroup():
|
|
|
|
# construct a list of all changed files
|
|
|
|
changedfiles = {}
|
|
|
|
|
|
|
|
for chnk in cl.group(nodes, identity,
|
|
|
|
changed_file_collector(changedfiles)):
|
|
|
|
yield chnk
|
|
|
|
changedfiles = changedfiles.keys()
|
|
|
|
changedfiles.sort()
|
|
|
|
|
|
|
|
mnfst = self.manifest
|
|
|
|
nodeiter = gennodelst(mnfst)
|
|
|
|
for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
|
|
|
|
yield chnk
|
|
|
|
|
|
|
|
for fname in changedfiles:
|
|
|
|
filerevlog = self.file(fname)
|
|
|
|
nodeiter = gennodelst(filerevlog)
|
|
|
|
nodeiter = list(nodeiter)
|
|
|
|
if nodeiter:
|
2006-03-21 13:47:21 +03:00
|
|
|
yield changegroup.genchunk(fname)
|
2005-10-07 21:57:11 +04:00
|
|
|
lookup = lookuprevlink_func(filerevlog)
|
|
|
|
for chnk in filerevlog.group(nodeiter, lookup):
|
|
|
|
yield chnk
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-03-21 13:47:21 +03:00
|
|
|
yield changegroup.closechunk()
|
2006-04-22 00:14:27 +04:00
|
|
|
|
|
|
|
if nodes:
|
|
|
|
self.hook('outgoing', node=hex(nodes[0]), source=source)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-07 21:57:11 +04:00
|
|
|
return util.chunkbuffer(gengroup())
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-05-09 03:50:27 +04:00
|
|
|
def addchangegroup(self, source, srctype):
|
2006-03-29 22:27:16 +04:00
|
|
|
"""add changegroup to repo.
|
|
|
|
returns number of heads modified or added + 1."""
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def csmap(x):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("add changeset %s\n") % short(x))
|
fix race in localrepo.addchangegroup.
localrepo.addchangegroup writes to changelog, then manifest, then normal
files. this breaks access ordering. if reader reads changelog while
manifest is being written, can find pointers into places in manifest
that are not yet written. same can happen for manifest and normal files.
fix is to make almost no change to localrepo.addchangegroup. it must
to write changelog and manifest data early because it has to read them
while writing other files. instead, write changelog and manifest data
to temp file that reader cannot see, then append temp data to manifest
after all normal files written, finally append temp data to changelog.
temp file code is in new appendfile module. can be used in other places
with small changes.
much smaller race still left. we write all new data in one write call,
but reader can maybe see partial update because python or os or filesystem
cannot always make write really atomic. file locking no help: slow, not
portable, not reliable over nfs. only real safe other plan is write to
temp file every time and rename, but performance bad when manifest or
changelog is big.
2006-03-24 20:08:12 +03:00
|
|
|
return cl.count()
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def revmap(x):
|
fix race in localrepo.addchangegroup.
localrepo.addchangegroup writes to changelog, then manifest, then normal
files. this breaks access ordering. if reader reads changelog while
manifest is being written, can find pointers into places in manifest
that are not yet written. same can happen for manifest and normal files.
fix is to make almost no change to localrepo.addchangegroup. it must
to write changelog and manifest data early because it has to read them
while writing other files. instead, write changelog and manifest data
to temp file that reader cannot see, then append temp data to manifest
after all normal files written, finally append temp data to changelog.
temp file code is in new appendfile module. can be used in other places
with small changes.
much smaller race still left. we write all new data in one write call,
but reader can maybe see partial update because python or os or filesystem
cannot always make write really atomic. file locking no help: slow, not
portable, not reliable over nfs. only real safe other plan is write to
temp file every time and rename, but performance bad when manifest or
changelog is big.
2006-03-24 20:08:12 +03:00
|
|
|
return cl.rev(x)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-01-12 09:57:58 +03:00
|
|
|
if not source:
|
2006-03-29 22:27:16 +04:00
|
|
|
return 0
|
2006-02-15 21:49:30 +03:00
|
|
|
|
2006-05-09 03:50:27 +04:00
|
|
|
self.hook('prechangegroup', throw=True, source=srctype)
|
2006-02-15 21:49:30 +03:00
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
changesets = files = revisions = 0
|
|
|
|
|
|
|
|
tr = self.transaction()
|
|
|
|
|
fix race in localrepo.addchangegroup.
localrepo.addchangegroup writes to changelog, then manifest, then normal
files. this breaks access ordering. if reader reads changelog while
manifest is being written, can find pointers into places in manifest
that are not yet written. same can happen for manifest and normal files.
fix is to make almost no change to localrepo.addchangegroup. it must
to write changelog and manifest data early because it has to read them
while writing other files. instead, write changelog and manifest data
to temp file that reader cannot see, then append temp data to manifest
after all normal files written, finally append temp data to changelog.
temp file code is in new appendfile module. can be used in other places
with small changes.
much smaller race still left. we write all new data in one write call,
but reader can maybe see partial update because python or os or filesystem
cannot always make write really atomic. file locking no help: slow, not
portable, not reliable over nfs. only real safe other plan is write to
temp file every time and rename, but performance bad when manifest or
changelog is big.
2006-03-24 20:08:12 +03:00
|
|
|
# write changelog and manifest data to temp files so
|
|
|
|
# concurrent readers will not see inconsistent view
|
2006-05-09 19:03:00 +04:00
|
|
|
cl = None
|
|
|
|
try:
|
|
|
|
cl = appendfile.appendchangelog(self.opener, self.changelog.version)
|
|
|
|
|
|
|
|
oldheads = len(cl.heads())
|
|
|
|
|
|
|
|
# pull off the changeset group
|
|
|
|
self.ui.status(_("adding changesets\n"))
|
|
|
|
co = cl.tip()
|
2006-03-21 13:47:21 +03:00
|
|
|
chunkiter = changegroup.chunkiter(source)
|
2006-05-09 19:03:00 +04:00
|
|
|
cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
|
|
|
|
cnr, cor = map(cl.rev, (cn, co))
|
|
|
|
if cn == nullid:
|
|
|
|
cnr = cor
|
|
|
|
changesets = cnr - cor
|
|
|
|
|
|
|
|
mf = None
|
|
|
|
try:
|
|
|
|
mf = appendfile.appendmanifest(self.opener,
|
|
|
|
self.manifest.version)
|
|
|
|
|
|
|
|
# pull off the manifest group
|
|
|
|
self.ui.status(_("adding manifests\n"))
|
|
|
|
mm = mf.tip()
|
|
|
|
chunkiter = changegroup.chunkiter(source)
|
|
|
|
mo = mf.addgroup(chunkiter, revmap, tr)
|
|
|
|
|
|
|
|
# process the files
|
|
|
|
self.ui.status(_("adding file changes\n"))
|
|
|
|
while 1:
|
|
|
|
f = changegroup.getchunk(source)
|
|
|
|
if not f:
|
|
|
|
break
|
|
|
|
self.ui.debug(_("adding %s revisions\n") % f)
|
|
|
|
fl = self.file(f)
|
|
|
|
o = fl.count()
|
|
|
|
chunkiter = changegroup.chunkiter(source)
|
|
|
|
n = fl.addgroup(chunkiter, revmap, tr)
|
|
|
|
revisions += fl.count() - o
|
|
|
|
files += 1
|
|
|
|
|
|
|
|
# write order here is important so concurrent readers will see
|
|
|
|
# consistent view of repo
|
|
|
|
mf.writedata()
|
|
|
|
finally:
|
|
|
|
if mf:
|
|
|
|
mf.cleanup()
|
|
|
|
cl.writedata()
|
|
|
|
finally:
|
|
|
|
if cl:
|
|
|
|
cl.cleanup()
|
fix race in localrepo.addchangegroup.
localrepo.addchangegroup writes to changelog, then manifest, then normal
files. this breaks access ordering. if reader reads changelog while
manifest is being written, can find pointers into places in manifest
that are not yet written. same can happen for manifest and normal files.
fix is to make almost no change to localrepo.addchangegroup. it must
to write changelog and manifest data early because it has to read them
while writing other files. instead, write changelog and manifest data
to temp file that reader cannot see, then append temp data to manifest
after all normal files written, finally append temp data to changelog.
temp file code is in new appendfile module. can be used in other places
with small changes.
much smaller race still left. we write all new data in one write call,
but reader can maybe see partial update because python or os or filesystem
cannot always make write really atomic. file locking no help: slow, not
portable, not reliable over nfs. only real safe other plan is write to
temp file every time and rename, but performance bad when manifest or
changelog is big.
2006-03-24 20:08:12 +03:00
|
|
|
|
|
|
|
# make changelog and manifest see real files again
|
2006-04-09 04:08:06 +04:00
|
|
|
self.changelog = changelog.changelog(self.opener, self.changelog.version)
|
|
|
|
self.manifest = manifest.manifest(self.opener, self.manifest.version)
|
2006-04-05 00:38:43 +04:00
|
|
|
self.changelog.checkinlinesize(tr)
|
2006-04-09 04:08:06 +04:00
|
|
|
self.manifest.checkinlinesize(tr)
|
fix race in localrepo.addchangegroup.
localrepo.addchangegroup writes to changelog, then manifest, then normal
files. this breaks access ordering. if reader reads changelog while
manifest is being written, can find pointers into places in manifest
that are not yet written. same can happen for manifest and normal files.
fix is to make almost no change to localrepo.addchangegroup. it must
to write changelog and manifest data early because it has to read them
while writing other files. instead, write changelog and manifest data
to temp file that reader cannot see, then append temp data to manifest
after all normal files written, finally append temp data to changelog.
temp file code is in new appendfile module. can be used in other places
with small changes.
much smaller race still left. we write all new data in one write call,
but reader can maybe see partial update because python or os or filesystem
cannot always make write really atomic. file locking no help: slow, not
portable, not reliable over nfs. only real safe other plan is write to
temp file every time and rename, but performance bad when manifest or
changelog is big.
2006-03-24 20:08:12 +03:00
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
newheads = len(self.changelog.heads())
|
|
|
|
heads = ""
|
|
|
|
if oldheads and newheads > oldheads:
|
2005-10-19 05:38:39 +04:00
|
|
|
heads = _(" (+%d heads)") % (newheads - oldheads)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("added %d changesets"
|
|
|
|
" with %d changes to %d files%s\n")
|
|
|
|
% (changesets, revisions, files, heads))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-05-11 17:01:30 +04:00
|
|
|
if changesets > 0:
|
|
|
|
self.hook('pretxnchangegroup', throw=True,
|
|
|
|
node=hex(self.changelog.node(cor+1)), source=srctype)
|
2006-02-15 21:49:30 +03:00
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
tr.close()
|
|
|
|
|
2005-10-04 01:45:14 +04:00
|
|
|
if changesets > 0:
|
2006-05-09 03:07:56 +04:00
|
|
|
self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
|
2006-05-09 03:50:27 +04:00
|
|
|
source=srctype)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-04 01:45:14 +04:00
|
|
|
for i in range(cor + 1, cnr + 1):
|
2006-05-09 03:07:56 +04:00
|
|
|
self.hook("incoming", node=hex(self.changelog.node(i)),
|
2006-05-09 03:50:27 +04:00
|
|
|
source=srctype)
|
2005-09-22 21:12:42 +04:00
|
|
|
|
2006-03-29 22:27:16 +04:00
|
|
|
return newheads - oldheads + 1
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
def update(self, node, allow=False, force=False, choose=None,
|
2006-05-02 20:44:02 +04:00
|
|
|
moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
|
2005-08-28 01:21:25 +04:00
|
|
|
pl = self.dirstate.parents()
|
|
|
|
if not force and pl[1] != nullid:
|
2006-05-11 20:43:50 +04:00
|
|
|
raise util.Abort(_("outstanding uncommitted merges"))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-01-30 02:02:06 +03:00
|
|
|
err = False
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
p1, p2 = pl[0], node
|
|
|
|
pa = self.changelog.ancestor(p1, p2)
|
|
|
|
m1n = self.changelog.read(p1)[0]
|
|
|
|
m2n = self.changelog.read(p2)[0]
|
|
|
|
man = self.manifest.ancestor(m1n, m2n)
|
|
|
|
m1 = self.manifest.read(m1n)
|
|
|
|
mf1 = self.manifest.readflags(m1n)
|
2006-01-22 20:54:25 +03:00
|
|
|
m2 = self.manifest.read(m2n).copy()
|
2005-08-28 01:21:25 +04:00
|
|
|
mf2 = self.manifest.readflags(m2n)
|
|
|
|
ma = self.manifest.read(man)
|
|
|
|
mfa = self.manifest.readflags(man)
|
|
|
|
|
2006-01-12 15:58:36 +03:00
|
|
|
modified, added, removed, deleted, unknown = self.changes()
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-02-01 10:46:24 +03:00
|
|
|
# is this a jump, or a merge? i.e. is there a linear path
|
|
|
|
# from p1 to p2?
|
|
|
|
linear_path = (pa == p1 or pa == p2)
|
|
|
|
|
|
|
|
if allow and linear_path:
|
|
|
|
raise util.Abort(_("there is nothing to merge, "
|
|
|
|
"just use 'hg update'"))
|
2005-12-15 07:19:03 +03:00
|
|
|
if allow and not forcemerge:
|
2006-01-12 15:35:09 +03:00
|
|
|
if modified or added or removed:
|
2006-03-24 16:53:23 +03:00
|
|
|
raise util.Abort(_("outstanding uncommitted changes"))
|
2006-05-11 21:14:48 +04:00
|
|
|
|
2005-12-15 07:19:03 +03:00
|
|
|
if not forcemerge and not force:
|
2006-01-12 15:35:09 +03:00
|
|
|
for f in unknown:
|
2005-12-15 07:19:03 +03:00
|
|
|
if f in m2:
|
2006-01-12 09:57:58 +03:00
|
|
|
t1 = self.wread(f)
|
|
|
|
t2 = self.file(f).read(m2[f])
|
|
|
|
if cmp(t1, t2) != 0:
|
2005-12-15 07:19:03 +03:00
|
|
|
raise util.Abort(_("'%s' already exists in the working"
|
|
|
|
" dir and differs from remote") % f)
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
# resolve the manifest to determine which files
|
|
|
|
# we care about merging
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.note(_("resolving manifests\n"))
|
|
|
|
self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
|
2005-08-28 01:21:25 +04:00
|
|
|
(force, allow, moddirstate, linear_path))
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_(" ancestor %s local %s remote %s\n") %
|
2005-08-28 01:21:25 +04:00
|
|
|
(short(man), short(m1n), short(m2n)))
|
|
|
|
|
|
|
|
merge = {}
|
|
|
|
get = {}
|
|
|
|
remove = []
|
|
|
|
|
|
|
|
# construct a working dir manifest
|
|
|
|
mw = m1.copy()
|
|
|
|
mfw = mf1.copy()
|
2006-01-12 15:35:09 +03:00
|
|
|
umap = dict.fromkeys(unknown)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-01-12 15:35:09 +03:00
|
|
|
for f in added + modified + unknown:
|
2005-08-28 01:21:25 +04:00
|
|
|
mw[f] = ""
|
|
|
|
mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
|
|
|
|
|
2006-02-10 02:18:43 +03:00
|
|
|
if moddirstate and not wlock:
|
2005-11-12 02:34:13 +03:00
|
|
|
wlock = self.wlock()
|
|
|
|
|
2006-01-12 23:55:19 +03:00
|
|
|
for f in deleted + removed:
|
2006-01-12 09:57:58 +03:00
|
|
|
if f in mw:
|
|
|
|
del mw[f]
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
# If we're jumping between revisions (as opposed to merging),
|
|
|
|
# and if neither the working directory nor the target rev has
|
|
|
|
# the file, then we need to remove it from the dirstate, to
|
|
|
|
# prevent the dirstate from listing the file when it is no
|
|
|
|
# longer in the manifest.
|
|
|
|
if moddirstate and linear_path and f not in m2:
|
|
|
|
self.dirstate.forget((f,))
|
|
|
|
|
|
|
|
# Compare manifests
|
|
|
|
for f, n in mw.iteritems():
|
2006-01-12 09:57:58 +03:00
|
|
|
if choose and not choose(f):
|
|
|
|
continue
|
2005-08-28 01:21:25 +04:00
|
|
|
if f in m2:
|
|
|
|
s = 0
|
|
|
|
|
|
|
|
# is the wfile new since m1, and match m2?
|
|
|
|
if f not in m1:
|
|
|
|
t1 = self.wread(f)
|
|
|
|
t2 = self.file(f).read(m2[f])
|
|
|
|
if cmp(t1, t2) == 0:
|
|
|
|
n = m2[f]
|
|
|
|
del t1, t2
|
|
|
|
|
|
|
|
# are files different?
|
|
|
|
if n != m2[f]:
|
|
|
|
a = ma.get(f, nullid)
|
|
|
|
# are both different from the ancestor?
|
|
|
|
if n != a and m2[f] != a:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_(" %s versions differ, resolve\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
# merge executable bits
|
|
|
|
# "if we changed or they changed, change in merge"
|
|
|
|
a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
|
|
|
|
mode = ((a^b) | (a^c)) ^ a
|
|
|
|
merge[f] = (m1.get(f, nullid), m2[f], mode)
|
|
|
|
s = 1
|
|
|
|
# are we clobbering?
|
|
|
|
# is remote's version newer?
|
|
|
|
# or are we going back in time?
|
|
|
|
elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_(" remote %s is newer, get\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
get[f] = m2[f]
|
|
|
|
s = 1
|
2006-04-14 02:41:50 +04:00
|
|
|
elif f in umap or f in added:
|
2005-08-28 01:21:25 +04:00
|
|
|
# this unknown file is the same as the checkout
|
2006-04-14 02:41:50 +04:00
|
|
|
# we need to reset the dirstate if the file was added
|
2005-08-28 01:21:25 +04:00
|
|
|
get[f] = m2[f]
|
|
|
|
|
|
|
|
if not s and mfw[f] != mf2[f]:
|
|
|
|
if force:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_(" updating permissions for %s\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
util.set_exec(self.wjoin(f), mf2[f])
|
|
|
|
else:
|
|
|
|
a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
|
|
|
|
mode = ((a^b) | (a^c)) ^ a
|
|
|
|
if mode != b:
|
2006-01-12 09:57:58 +03:00
|
|
|
self.ui.debug(_(" updating permissions for %s\n")
|
|
|
|
% f)
|
2005-08-28 01:21:25 +04:00
|
|
|
util.set_exec(self.wjoin(f), mode)
|
|
|
|
del m2[f]
|
|
|
|
elif f in ma:
|
|
|
|
if n != ma[f]:
|
2005-10-19 05:38:39 +04:00
|
|
|
r = _("d")
|
2005-08-28 01:21:25 +04:00
|
|
|
if not force and (linear_path or allow):
|
|
|
|
r = self.ui.prompt(
|
2005-10-19 05:38:39 +04:00
|
|
|
(_(" local changed %s which remote deleted\n") % f) +
|
|
|
|
_("(k)eep or (d)elete?"), _("[kd]"), _("k"))
|
|
|
|
if r == _("d"):
|
2005-08-28 01:21:25 +04:00
|
|
|
remove.append(f)
|
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("other deleted %s\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
remove.append(f) # other deleted it
|
|
|
|
else:
|
2005-09-14 03:38:27 +04:00
|
|
|
# file is created on branch or in working directory
|
|
|
|
if force and f not in umap:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("remote deleted %s, clobbering\n") % f)
|
2005-09-14 03:38:27 +04:00
|
|
|
remove.append(f)
|
|
|
|
elif n == m1.get(f, nullid): # same as parent
|
2005-09-13 23:22:48 +04:00
|
|
|
if p2 == pa: # going backwards?
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("remote deleted %s\n") % f)
|
2005-09-13 23:22:48 +04:00
|
|
|
remove.append(f)
|
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("local modified %s, keeping\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("working dir created %s, keeping\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
for f, n in m2.iteritems():
|
2006-01-12 09:57:58 +03:00
|
|
|
if choose and not choose(f):
|
|
|
|
continue
|
|
|
|
if f[0] == "/":
|
|
|
|
continue
|
2005-08-28 01:21:25 +04:00
|
|
|
if f in ma and n != ma[f]:
|
2005-10-19 05:38:39 +04:00
|
|
|
r = _("k")
|
2005-08-28 01:21:25 +04:00
|
|
|
if not force and (linear_path or allow):
|
|
|
|
r = self.ui.prompt(
|
2005-10-19 05:38:39 +04:00
|
|
|
(_("remote changed %s which local deleted\n") % f) +
|
|
|
|
_("(k)eep or (d)elete?"), _("[kd]"), _("k"))
|
2006-01-12 09:57:58 +03:00
|
|
|
if r == _("k"):
|
|
|
|
get[f] = n
|
2005-08-28 01:21:25 +04:00
|
|
|
elif f not in ma:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("remote created %s\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
get[f] = n
|
|
|
|
else:
|
|
|
|
if force or p2 == pa: # going backwards?
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("local deleted %s, recreating\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
get[f] = n
|
|
|
|
else:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.debug(_("local deleted %s\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
del mw, m1, m2, ma
|
|
|
|
|
|
|
|
if force:
|
|
|
|
for f in merge:
|
|
|
|
get[f] = merge[f][1]
|
|
|
|
merge = {}
|
|
|
|
|
|
|
|
if linear_path or force:
|
|
|
|
# we don't need to do any magic, just jump to the new rev
|
|
|
|
branch_merge = False
|
|
|
|
p1, p2 = p2, nullid
|
|
|
|
else:
|
|
|
|
if not allow:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("this update spans a branch"
|
|
|
|
" affecting the following files:\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
fl = merge.keys() + get.keys()
|
|
|
|
fl.sort()
|
|
|
|
for f in fl:
|
|
|
|
cf = ""
|
2006-01-12 09:57:58 +03:00
|
|
|
if f in merge:
|
|
|
|
cf = _(" (resolve)")
|
2005-08-28 01:21:25 +04:00
|
|
|
self.ui.status(" %s%s\n" % (f, cf))
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("aborting update spanning branches!\n"))
|
2006-03-29 22:27:16 +04:00
|
|
|
self.ui.status(_("(use 'hg merge' to merge across branches"
|
2006-04-02 20:16:06 +04:00
|
|
|
" or 'hg update -C' to lose changes)\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
return 1
|
|
|
|
branch_merge = True
|
|
|
|
|
2006-05-11 21:14:48 +04:00
|
|
|
xp1 = hex(p1)
|
|
|
|
xp2 = hex(p2)
|
|
|
|
if p2 == nullid: xxp2 = ''
|
|
|
|
else: xxp2 = xp2
|
|
|
|
|
|
|
|
self.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
# get the files we don't need to change
|
|
|
|
files = get.keys()
|
|
|
|
files.sort()
|
|
|
|
for f in files:
|
2006-01-12 09:57:58 +03:00
|
|
|
if f[0] == "/":
|
|
|
|
continue
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.note(_("getting %s\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
t = self.file(f).read(get[f])
|
2005-10-28 22:01:25 +04:00
|
|
|
self.wwrite(f, t)
|
2005-08-28 01:21:25 +04:00
|
|
|
util.set_exec(self.wjoin(f), mf2[f])
|
|
|
|
if moddirstate:
|
|
|
|
if branch_merge:
|
|
|
|
self.dirstate.update([f], 'n', st_mtime=-1)
|
|
|
|
else:
|
|
|
|
self.dirstate.update([f], 'n')
|
|
|
|
|
|
|
|
# merge the tricky bits
|
2006-03-13 10:56:59 +03:00
|
|
|
failedmerge = []
|
2005-08-28 01:21:25 +04:00
|
|
|
files = merge.keys()
|
|
|
|
files.sort()
|
|
|
|
for f in files:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("merging %s\n") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
my, other, flag = merge[f]
|
2006-03-11 10:34:02 +03:00
|
|
|
ret = self.merge3(f, my, other, xp1, xp2)
|
2006-01-30 02:02:06 +03:00
|
|
|
if ret:
|
|
|
|
err = True
|
2006-03-13 10:56:59 +03:00
|
|
|
failedmerge.append(f)
|
2005-08-28 01:21:25 +04:00
|
|
|
util.set_exec(self.wjoin(f), flag)
|
|
|
|
if moddirstate:
|
|
|
|
if branch_merge:
|
|
|
|
# We've done a branch merge, mark this file as merged
|
|
|
|
# so that we properly record the merger later
|
|
|
|
self.dirstate.update([f], 'm')
|
|
|
|
else:
|
|
|
|
# We've update-merged a locally modified file, so
|
|
|
|
# we set the dirstate to emulate a normal checkout
|
|
|
|
# of that file some time in the past. Thus our
|
|
|
|
# merge will appear as a normal local file
|
|
|
|
# modification.
|
|
|
|
f_len = len(self.file(f).read(other))
|
|
|
|
self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
|
|
|
|
|
|
|
|
remove.sort()
|
|
|
|
for f in remove:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.note(_("removing %s\n") % f)
|
2006-03-04 21:01:45 +03:00
|
|
|
util.audit_path(f)
|
2005-08-28 01:21:25 +04:00
|
|
|
try:
|
2005-10-19 11:10:52 +04:00
|
|
|
util.unlink(self.wjoin(f))
|
2005-08-28 01:21:25 +04:00
|
|
|
except OSError, inst:
|
2005-10-19 04:56:50 +04:00
|
|
|
if inst.errno != errno.ENOENT:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("update failed to remove %s: %s!\n") %
|
2005-10-19 04:56:50 +04:00
|
|
|
(f, inst.strerror))
|
2005-08-28 01:21:25 +04:00
|
|
|
if moddirstate:
|
|
|
|
if branch_merge:
|
|
|
|
self.dirstate.update(remove, 'r')
|
|
|
|
else:
|
|
|
|
self.dirstate.forget(remove)
|
|
|
|
|
2005-11-03 07:22:29 +03:00
|
|
|
if moddirstate:
|
|
|
|
self.dirstate.setparents(p1, p2)
|
2006-03-13 10:56:59 +03:00
|
|
|
|
2006-05-02 20:44:02 +04:00
|
|
|
if show_stats:
|
|
|
|
stats = ((len(get), _("updated")),
|
|
|
|
(len(merge) - len(failedmerge), _("merged")),
|
|
|
|
(len(remove), _("removed")),
|
|
|
|
(len(failedmerge), _("unresolved")))
|
|
|
|
note = ", ".join([_("%d files %s") % s for s in stats])
|
|
|
|
self.ui.status("%s\n" % note)
|
|
|
|
if moddirstate:
|
|
|
|
if branch_merge:
|
|
|
|
if failedmerge:
|
|
|
|
self.ui.status(_("There are unresolved merges,"
|
|
|
|
" you can redo the full merge using:\n"
|
|
|
|
" hg update -C %s\n"
|
|
|
|
" hg merge %s\n"
|
|
|
|
% (self.changelog.rev(p1),
|
|
|
|
self.changelog.rev(p2))))
|
|
|
|
else:
|
|
|
|
self.ui.status(_("(branch merge, don't forget to commit)\n"))
|
|
|
|
elif failedmerge:
|
|
|
|
self.ui.status(_("There are unresolved merges with"
|
|
|
|
" locally modified files.\n"))
|
2006-03-13 10:56:59 +03:00
|
|
|
|
2006-05-11 21:14:48 +04:00
|
|
|
self.hook('update', parent1=xp1, parent2=xxp2, error=int(err))
|
2006-01-30 02:02:06 +03:00
|
|
|
return err
|
2005-11-03 07:22:29 +03:00
|
|
|
|
2006-03-11 10:34:02 +03:00
|
|
|
def merge3(self, fn, my, other, p1, p2):
|
2005-08-28 01:21:25 +04:00
|
|
|
"""perform a 3-way merge in the working directory"""
|
|
|
|
|
|
|
|
def temp(prefix, node):
|
|
|
|
pre = "%s~%s." % (os.path.basename(fn), prefix)
|
2006-04-30 23:11:22 +04:00
|
|
|
(fd, name) = tempfile.mkstemp(prefix=pre)
|
2005-08-28 01:21:25 +04:00
|
|
|
f = os.fdopen(fd, "wb")
|
|
|
|
self.wwrite(fn, fl.read(node), f)
|
|
|
|
f.close()
|
|
|
|
return name
|
|
|
|
|
|
|
|
fl = self.file(fn)
|
|
|
|
base = fl.ancestor(my, other)
|
|
|
|
a = self.wjoin(fn)
|
|
|
|
b = temp("base", base)
|
|
|
|
c = temp("other", other)
|
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.note(_("resolving %s\n") % fn)
|
|
|
|
self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
|
2005-09-27 01:01:05 +04:00
|
|
|
(fn, short(my), short(other), short(base)))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
|
|
|
|
or "hgmerge")
|
2006-03-12 08:33:19 +03:00
|
|
|
r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
|
|
|
|
environ={'HG_FILE': fn,
|
2006-03-11 10:34:02 +03:00
|
|
|
'HG_MY_NODE': p1,
|
|
|
|
'HG_OTHER_NODE': p2,
|
|
|
|
'HG_FILE_MY_NODE': hex(my),
|
|
|
|
'HG_FILE_OTHER_NODE': hex(other),
|
|
|
|
'HG_FILE_BASE_NODE': hex(base)})
|
2005-08-28 01:21:25 +04:00
|
|
|
if r:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("merging %s failed!\n") % fn)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
os.unlink(b)
|
|
|
|
os.unlink(c)
|
2006-01-30 02:02:06 +03:00
|
|
|
return r
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
def verify(self):
|
|
|
|
filelinkrevs = {}
|
|
|
|
filenodes = {}
|
|
|
|
changesets = revisions = files = 0
|
2005-10-05 21:51:02 +04:00
|
|
|
errors = [0]
|
2006-04-27 23:58:47 +04:00
|
|
|
warnings = [0]
|
2005-10-05 21:37:51 +04:00
|
|
|
neededmanifests = {}
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-05 21:51:02 +04:00
|
|
|
def err(msg):
|
|
|
|
self.ui.warn(msg + "\n")
|
|
|
|
errors[0] += 1
|
|
|
|
|
2006-04-27 23:58:47 +04:00
|
|
|
def warn(msg):
|
|
|
|
self.ui.warn(msg + "\n")
|
|
|
|
warnings[0] += 1
|
|
|
|
|
2006-01-30 09:34:35 +03:00
|
|
|
def checksize(obj, name):
|
|
|
|
d = obj.checksize()
|
|
|
|
if d[0]:
|
|
|
|
err(_("%s data length off by %d bytes") % (name, d[0]))
|
|
|
|
if d[1]:
|
|
|
|
err(_("%s index contains %d extra bytes") % (name, d[1]))
|
|
|
|
|
2006-04-27 23:58:47 +04:00
|
|
|
def checkversion(obj, name):
|
|
|
|
if obj.version != revlog.REVLOGV0:
|
|
|
|
if not revlogv1:
|
|
|
|
warn(_("warning: `%s' uses revlog format 1") % name)
|
|
|
|
elif revlogv1:
|
|
|
|
warn(_("warning: `%s' uses revlog format 0") % name)
|
|
|
|
|
|
|
|
revlogv1 = self.revlogversion != revlog.REVLOGV0
|
2006-04-28 23:52:08 +04:00
|
|
|
if self.ui.verbose or revlogv1 != self.revlogv1:
|
|
|
|
self.ui.status(_("repository uses revlog format %d\n") %
|
|
|
|
(revlogv1 and 1 or 0))
|
2006-04-27 23:58:47 +04:00
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
seen = {}
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("checking changesets\n"))
|
2006-01-30 09:34:35 +03:00
|
|
|
checksize(self.changelog, "changelog")
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
for i in range(self.changelog.count()):
|
|
|
|
changesets += 1
|
|
|
|
n = self.changelog.node(i)
|
2005-10-05 21:37:51 +04:00
|
|
|
l = self.changelog.linkrev(n)
|
|
|
|
if l != i:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("incorrect link (%d) for changeset revision %d") %(l, i))
|
2005-08-28 01:21:25 +04:00
|
|
|
if n in seen:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("duplicate changeset at revision %d") % i)
|
2005-08-28 01:21:25 +04:00
|
|
|
seen[n] = 1
|
|
|
|
|
|
|
|
for p in self.changelog.parents(n):
|
|
|
|
if p not in self.changelog.nodemap:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("changeset %s has unknown parent %s") %
|
2005-08-28 01:21:25 +04:00
|
|
|
(short(n), short(p)))
|
|
|
|
try:
|
|
|
|
changes = self.changelog.read(n)
|
2005-11-03 05:59:40 +03:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
self.ui.warn(_("interrupted"))
|
|
|
|
raise
|
2005-08-28 01:21:25 +04:00
|
|
|
except Exception, inst:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("unpacking changeset %s: %s") % (short(n), inst))
|
2006-03-08 02:27:23 +03:00
|
|
|
continue
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-05 21:37:51 +04:00
|
|
|
neededmanifests[changes[0]] = n
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
for f in changes[3]:
|
|
|
|
filelinkrevs.setdefault(f, []).append(i)
|
|
|
|
|
|
|
|
seen = {}
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("checking manifests\n"))
|
2006-04-27 23:58:47 +04:00
|
|
|
checkversion(self.manifest, "manifest")
|
2006-01-30 09:34:35 +03:00
|
|
|
checksize(self.manifest, "manifest")
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
for i in range(self.manifest.count()):
|
|
|
|
n = self.manifest.node(i)
|
2005-10-05 21:37:51 +04:00
|
|
|
l = self.manifest.linkrev(n)
|
|
|
|
|
|
|
|
if l < 0 or l >= self.changelog.count():
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("bad manifest link (%d) at revision %d") % (l, i))
|
2005-10-05 21:37:51 +04:00
|
|
|
|
|
|
|
if n in neededmanifests:
|
|
|
|
del neededmanifests[n]
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
if n in seen:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("duplicate manifest at revision %d") % i)
|
2005-10-05 21:51:02 +04:00
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
seen[n] = 1
|
|
|
|
|
|
|
|
for p in self.manifest.parents(n):
|
|
|
|
if p not in self.manifest.nodemap:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("manifest %s has unknown parent %s") %
|
2005-10-05 21:51:02 +04:00
|
|
|
(short(n), short(p)))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
try:
|
|
|
|
delta = mdiff.patchtext(self.manifest.delta(n))
|
|
|
|
except KeyboardInterrupt:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("interrupted"))
|
2005-08-28 02:09:46 +04:00
|
|
|
raise
|
2005-08-28 01:21:25 +04:00
|
|
|
except Exception, inst:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("unpacking manifest %s: %s") % (short(n), inst))
|
2006-03-08 02:27:23 +03:00
|
|
|
continue
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2006-03-08 02:27:23 +03:00
|
|
|
try:
|
|
|
|
ff = [ l.split('\0') for l in delta.splitlines() ]
|
|
|
|
for f, fn in ff:
|
|
|
|
filenodes.setdefault(f, {})[bin(fn[:40])] = 1
|
|
|
|
except (ValueError, TypeError), inst:
|
|
|
|
err(_("broken delta in manifest %s: %s") % (short(n), inst))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("crosschecking files in changesets and manifests\n"))
|
2005-10-05 21:37:51 +04:00
|
|
|
|
2006-01-12 09:57:58 +03:00
|
|
|
for m, c in neededmanifests.items():
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("Changeset %s refers to unknown manifest %s") %
|
2005-10-05 21:59:42 +04:00
|
|
|
(short(m), short(c)))
|
2005-10-05 21:37:51 +04:00
|
|
|
del neededmanifests
|
|
|
|
|
2005-08-28 01:21:25 +04:00
|
|
|
for f in filenodes:
|
|
|
|
if f not in filelinkrevs:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("file %s in manifest but not in changesets") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
for f in filelinkrevs:
|
|
|
|
if f not in filenodes:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("file %s in changeset but not in manifest") % f)
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("checking files\n"))
|
2005-08-28 01:21:25 +04:00
|
|
|
ff = filenodes.keys()
|
|
|
|
ff.sort()
|
|
|
|
for f in ff:
|
2006-01-12 09:57:58 +03:00
|
|
|
if f == "/dev/null":
|
|
|
|
continue
|
2005-08-28 01:21:25 +04:00
|
|
|
files += 1
|
2006-03-08 02:27:23 +03:00
|
|
|
if not f:
|
|
|
|
err(_("file without name in manifest %s") % short(n))
|
|
|
|
continue
|
2005-08-28 01:21:25 +04:00
|
|
|
fl = self.file(f)
|
2006-04-27 23:58:47 +04:00
|
|
|
checkversion(fl, f)
|
2006-01-30 09:34:35 +03:00
|
|
|
checksize(fl, f)
|
2005-11-03 06:26:23 +03:00
|
|
|
|
2006-01-12 09:57:58 +03:00
|
|
|
nodes = {nullid: 1}
|
2005-08-28 01:21:25 +04:00
|
|
|
seen = {}
|
|
|
|
for i in range(fl.count()):
|
|
|
|
revisions += 1
|
|
|
|
n = fl.node(i)
|
|
|
|
|
|
|
|
if n in seen:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("%s: duplicate revision %d") % (f, i))
|
2005-08-28 01:21:25 +04:00
|
|
|
if n not in filenodes[f]:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
|
|
|
del filenodes[f][n]
|
|
|
|
|
|
|
|
flr = fl.linkrev(n)
|
2006-03-08 02:27:23 +03:00
|
|
|
if flr not in filelinkrevs.get(f, []):
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("%s:%s points to unexpected changeset %d")
|
2005-10-05 21:51:02 +04:00
|
|
|
% (f, short(n), flr))
|
2005-08-28 01:21:25 +04:00
|
|
|
else:
|
|
|
|
filelinkrevs[f].remove(flr)
|
|
|
|
|
|
|
|
# verify contents
|
|
|
|
try:
|
|
|
|
t = fl.read(n)
|
2005-11-03 05:59:40 +03:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
self.ui.warn(_("interrupted"))
|
|
|
|
raise
|
2005-08-28 01:21:25 +04:00
|
|
|
except Exception, inst:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("unpacking file %s %s: %s") % (f, short(n), inst))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
|
|
|
# verify parents
|
|
|
|
(p1, p2) = fl.parents(n)
|
|
|
|
if p1 not in nodes:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("file %s:%s unknown parent 1 %s") %
|
2005-10-05 21:51:02 +04:00
|
|
|
(f, short(n), short(p1)))
|
2005-08-28 01:21:25 +04:00
|
|
|
if p2 not in nodes:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("file %s:%s unknown parent 2 %s") %
|
2005-08-28 01:21:25 +04:00
|
|
|
(f, short(n), short(p1)))
|
|
|
|
nodes[n] = 1
|
|
|
|
|
|
|
|
# cross-check
|
|
|
|
for node in filenodes[f]:
|
2005-10-19 05:38:39 +04:00
|
|
|
err(_("node %s in manifests not in %s") % (hex(node), f))
|
2005-08-28 01:21:25 +04:00
|
|
|
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
|
2005-08-28 01:21:25 +04:00
|
|
|
(files, changesets, revisions))
|
|
|
|
|
2006-04-27 23:58:47 +04:00
|
|
|
if warnings[0]:
|
|
|
|
self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
|
2005-10-05 21:51:02 +04:00
|
|
|
if errors[0]:
|
2005-10-19 05:38:39 +04:00
|
|
|
self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
|
2005-08-28 01:21:25 +04:00
|
|
|
return 1
|
2006-02-28 21:24:54 +03:00
|
|
|
|
|
|
|
# used to avoid circular references so destructors work
|
|
|
|
def aftertrans(base):
|
|
|
|
p = base
|
|
|
|
def a():
|
|
|
|
util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
|
|
|
|
util.rename(os.path.join(p, "journal.dirstate"),
|
|
|
|
os.path.join(p, "undo.dirstate"))
|
|
|
|
return a
|
|
|
|
|