# HG changeset patch # User Matt Mackall # Date 1185054278 18000 # Node ID 4106dde15aed344dc2730e90ca153acf4c3f9365 # Parent ee983d0dbea8af9b0a538406a52b69ba998151be# Parent a28661788f2fd211d7caf5ab63c77ca32c0e9bb0 Merge with crew diff --git a/contrib/churn.py b/contrib/churn.py --- a/contrib/churn.py +++ b/contrib/churn.py @@ -11,9 +11,34 @@ # # -import sys from mercurial.i18n import gettext as _ from mercurial import hg, mdiff, cmdutil, ui, util, templater, node +import os, sys + +def get_tty_width(): + if 'COLUMNS' in os.environ: + try: + return int(os.environ['COLUMNS']) + except ValueError: + pass + try: + import termios, fcntl, struct + buf = 'abcd' + for dev in (sys.stdout, sys.stdin): + try: + if buf != 'abcd': + break + fd = dev.fileno() + if not os.isatty(fd): + continue + buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, buf) + except ValueError: + pass + if buf != 'abcd': + return struct.unpack('hh', buf)[1] + except ImportError: + pass + return 80 def __gather(ui, repo, node1, node2): def dirtywork(f, mmap1, mmap2): @@ -159,8 +184,9 @@ def churn(ui, repo, **opts): maximum = ordered[0][1] - ui.note("Assuming 80 character terminal\n") - width = 80 - 1 + width = get_tty_width() + ui.note(_("assuming %i character terminal\n") % width) + width -= 1 for i in ordered: person = i[0] diff --git a/hgext/alias.py b/hgext/alias.py --- a/hgext/alias.py +++ b/hgext/alias.py @@ -72,6 +72,5 @@ def uisetup(ui): args = target.split(' ') tcmd = args.pop(0) if args: - pui = ui.parentui or ui - pui.setconfig('defaults', cmd, ' '.join(args)) + ui.setconfig('defaults', cmd, ' '.join(args)) cmdtable[cmd] = lazycommand(ui, cmd, tcmd) diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py --- a/hgext/convert/__init__.py +++ b/hgext/convert/__init__.py @@ -192,7 +192,7 @@ class convert(object): def copy(self, rev): c = self.commitcache[rev] files = self.source.getchanges(rev) - + do_copies = (hasattr(c, 'copies') and hasattr(self.dest, 'copyfile')) for f, v in files: @@ -260,7 +260,7 @@ class convert(object): self.mapfilefd.close() def _convert(ui, src, dest=None, mapfile=None, **opts): - '''Convert a foreign SCM repository to a Mercurial one. + """Convert a foreign SCM repository to a Mercurial one. Accepted source formats: - GIT @@ -293,7 +293,7 @@ def _convert(ui, src, dest=None, mapfile that use unix logins to identify authors (eg: CVS). One line per author mapping and the line format is: srcauthor=whatever string you want - ''' + """ util._encoding = 'UTF-8' diff --git a/hgext/convert/common.py b/hgext/convert/common.py --- a/hgext/convert/common.py +++ b/hgext/convert/common.py @@ -60,7 +60,7 @@ class converter_source(object): def recode(self, s, encoding=None): if not encoding: encoding = self.encoding or 'utf-8' - + try: return s.decode(encoding).encode("utf-8") except: diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py --- a/hgext/convert/subversion.py +++ b/hgext/convert/subversion.py @@ -1,10 +1,21 @@ # Subversion 1.4/1.5 Python API backend # # Copyright(C) 2007 Daniel Holth et al +# +# Configuration options: +# +# convert.svn.trunk +# Relative path to the trunk (default: "trunk") +# convert.svn.branches +# Relative path to tree of branches (default: "branches") +# +# Set these in a hgrc, or on the command line as follows: +# +# hg convert --config convert.svn.trunk=wackoname [...] -import pprint import locale - +import os +import cPickle as pickle from mercurial import util # Subversion stuff. Works best with very recent Python SVN bindings @@ -27,6 +38,12 @@ except ImportError: class CompatibilityException(Exception): pass +class changedpath(object): + def __init__(self, p): + self.copyfrom_path = p.copyfrom_path + self.copyfrom_rev = p.copyfrom_rev + self.action = p.action + # SVN conversion code stolen from bzr-svn and tailor class convert_svn(converter_source): def __init__(self, ui, url, rev=None): @@ -51,16 +68,18 @@ class convert_svn(converter_source): try: # Support file://path@rev syntax. Useful e.g. to convert # deleted branches. - url, latest = url.rsplit("@", 1) - latest = int(latest) + at = url.rfind('@') + if at >= 0: + latest = int(url[at+1:]) + url = url[:at] except ValueError, e: pass self.url = url self.encoding = 'UTF-8' # Subversion is always nominal UTF-8 try: - self.transport = transport.SvnRaTransport(url = url) + self.transport = transport.SvnRaTransport(url=url) self.ra = self.transport.ra - self.ctx = svn.client.create_context() + self.ctx = self.transport.client self.base = svn.ra.get_repos_root(self.ra) self.module = self.url[len(self.base):] self.modulemap = {} # revision, module @@ -88,26 +107,47 @@ class convert_svn(converter_source): lastrevs[module] = revnum self.lastrevs = lastrevs + def exists(self, path, optrev): + try: + return svn.client.ls(self.url.rstrip('/') + '/' + path, + optrev, False, self.ctx) + except SubversionException, err: + return [] + def getheads(self): # detect standard /branches, /tags, /trunk layout optrev = svn.core.svn_opt_revision_t() optrev.kind = svn.core.svn_opt_revision_number optrev.value.number = self.last_changed rpath = self.url.strip('/') - paths = svn.client.ls(rpath, optrev, False, self.ctx) - if 'branches' in paths and 'trunk' in paths: - self.module += '/trunk' + cfgtrunk = self.ui.config('convert', 'svn.trunk') + cfgbranches = self.ui.config('convert', 'svn.branches') + trunk = (cfgtrunk or 'trunk').strip('/') + branches = (cfgbranches or 'branches').strip('/') + if self.exists(trunk, optrev) and self.exists(branches, optrev): + self.ui.note('found trunk at %r and branches at %r\n' % + (trunk, branches)) + oldmodule = self.module + self.module += '/' + trunk lt = self.latest(self.module, self.last_changed) self.head = self.revid(lt) self.heads = [self.head] - branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx) - for branch in branches.keys(): - module = '/branches/' + branch + branchnames = svn.client.ls(rpath + '/' + branches, optrev, False, + self.ctx) + for branch in branchnames.keys(): + if oldmodule: + module = '/' + oldmodule + '/' + branches + '/' + branch + else: + module = '/' + branches + '/' + branch brevnum = self.latest(module, self.last_changed) brev = self.revid(brevnum, module) self.ui.note('found branch %s at %d\n' % (branch, brevnum)) self.heads.append(brev) + elif cfgtrunk or cfgbranches: + raise util.Abort(_('trunk/branch layout expected, ' + 'but not found')) else: + self.ui.note('working with one branch\n') self.heads = [self.head] return self.heads @@ -116,7 +156,7 @@ class convert_svn(converter_source): self.modecache[(file, rev)] = mode return data - def getmode(self, file, rev): + def getmode(self, file, rev): return self.modecache[(file, rev)] def getchanges(self, rev): @@ -140,27 +180,79 @@ class convert_svn(converter_source): del self.commits[rev] return commit + def get_log(self, paths, start, end, limit=0, discover_changed_paths=True, + strict_node_history=False): + '''wrapper for svn.ra.get_log. + on a large repository, svn.ra.get_log pins huge amounts of + memory that cannot be recovered. work around it by forking + and writing results over a pipe.''' + + def child(fp): + protocol = -1 + def receiver(orig_paths, revnum, author, date, message, pool): + if orig_paths is not None: + for k, v in orig_paths.iteritems(): + orig_paths[k] = changedpath(v) + pickle.dump((orig_paths, revnum, author, date, message), + fp, protocol) + + try: + # Use an ra of our own so that our parent can consume + # our results without confusing the server. + t = transport.SvnRaTransport(url=self.url) + svn.ra.get_log(t.ra, paths, start, end, limit, + discover_changed_paths, + strict_node_history, + receiver) + except SubversionException, (_, num): + self.ui.print_exc() + pickle.dump(num, fp, protocol) + else: + pickle.dump(None, fp, protocol) + fp.close() + + def parent(fp): + while True: + entry = pickle.load(fp) + try: + orig_paths, revnum, author, date, message = entry + except: + if entry is None: + break + raise SubversionException("child raised exception", entry) + yield entry + + rfd, wfd = os.pipe() + pid = os.fork() + if pid: + os.close(wfd) + for p in parent(os.fdopen(rfd, 'rb')): + yield p + ret = os.waitpid(pid, 0)[1] + if ret: + raise util.Abort(_('get_log %s') % util.explain_exit(ret)) + else: + os.close(rfd) + child(os.fdopen(wfd, 'wb')) + os._exit(0) + def gettags(self): tags = {} - def parselogentry(*arg, **args): - orig_paths, revnum, author, date, message, pool = arg - for path in orig_paths: - if not path.startswith('/tags/'): - continue - ent = orig_paths[path] - source = ent.copyfrom_path - rev = ent.copyfrom_rev - tag = path.split('/', 2)[2] - tags[tag] = self.revid(rev, module=source) - start = self.revnum(self.head) try: - svn.ra.get_log(self.ra, ['/tags'], 0, start, 0, True, False, - parselogentry) - return tags - except SubversionException: + for entry in self.get_log(['/tags'], 0, start): + orig_paths, revnum, author, date, message = entry + for path in orig_paths: + if not path.startswith('/tags/'): + continue + ent = orig_paths[path] + source = ent.copyfrom_path + rev = ent.copyfrom_rev + tag = path.split('/', 2)[2] + tags[tag] = self.revid(rev, module=source) + except SubversionException, (_, num): self.ui.note('no tags found at revision %d\n' % start) - return {} + return tags # -- helper functions -- @@ -193,8 +285,8 @@ class convert_svn(converter_source): except SubversionException: dirent = None if not dirent: - raise util.Abort('%s not found up to revision %d' \ - % (path, stop)) + print self.base, path + raise util.Abort('%s not found up to revision %d' % (path, stop)) return dirent.created_rev @@ -242,25 +334,10 @@ class convert_svn(converter_source): self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module)) return None - received = [] - # svn.ra.get_log requires no other calls to the ra until it completes, - # so we just collect the log entries and parse them afterwards - def receivelog(*arg, **args): - received.append(arg) - self.child_cset = None - def parselogentry(*arg, **args): - orig_paths, revnum, author, date, message, pool = arg - - if self.is_blacklisted(revnum): - self.ui.note('skipping blacklisted revision %d\n' % revnum) - return - - self.ui.debug("parsing revision %d\n" % revnum) - - if orig_paths is None: - self.ui.debug('revision %d has no entries\n' % revnum) - return + def parselogentry(orig_paths, revnum, author, date, message): + self.ui.debug("parsing revision %d (%d changes)\n" % + (revnum, len(orig_paths))) if revnum in self.modulemap: new_module = self.modulemap[revnum] @@ -286,12 +363,11 @@ class convert_svn(converter_source): except IndexError: branch = None - paths = orig_paths.keys() - paths.sort() - for path in paths: + orig_paths = orig_paths.items() + orig_paths.sort() + for path, ent in orig_paths: # self.ui.write("path %s\n" % path) if path == self.module: # Follow branching back in history - ent = orig_paths[path] if ent: if ent.copyfrom_path: # ent.copyfrom_rev may not be the actual last revision @@ -310,7 +386,6 @@ class convert_svn(converter_source): self.ui.debug("boring@%s: %s\n" % (revnum, path)) continue entry = entrypath.decode(self.encoding) - ent = orig_paths[path] kind = svn.ra.check_path(self.ra, entrypath, revnum) if kind == svn.core.svn_node_file: @@ -373,7 +448,7 @@ class convert_svn(converter_source): # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action) # Sometimes this is tricky. For example: in # The Subversion Repository revision 6940 a dir - # was copied and one of its files was deleted + # was copied and one of its files was deleted # from the new location in the same commit. This # code can't deal with that yet. if ent.action == 'C': @@ -387,7 +462,7 @@ class convert_svn(converter_source): for child in children: # Can we move a child directory and its # parent in the same commit? (probably can). Could - # cause problems if instead of revnum -1, + # cause problems if instead of revnum -1, # we have to look in (copyfrom_path, revnum - 1) entrypath = get_entry_from_path("/" + child, module=old_module) if entrypath: @@ -417,7 +492,7 @@ class convert_svn(converter_source): for child in children: # Can we move a child directory and its # parent in the same commit? (probably can). Could - # cause problems if instead of revnum -1, + # cause problems if instead of revnum -1, # we have to look in (copyfrom_path, revnum - 1) entrypath = get_entry_from_path("/" + child, module=self.module) # print child, self.module, entrypath @@ -466,7 +541,7 @@ class convert_svn(converter_source): self.modulemap[revnum] = self.module # track backwards in time # a list of (filename, id) where id lets us retrieve the file. - # eg in git, id is the object hash. for svn it'll be the + # eg in git, id is the object hash. for svn it'll be the self.files[rev] = zip(entries, [rev] * len(entries)) if not entries: return @@ -480,8 +555,8 @@ class convert_svn(converter_source): author = author and self.recode(author) or '' cset = commit(author=author, - date=util.datestr(date), - desc=log, + date=util.datestr(date), + desc=log, parents=parents, copies=copies, branch=branch, @@ -492,20 +567,24 @@ class convert_svn(converter_source): self.child_cset.parents = [rev] self.child_cset = cset - self.ui.note('fetching revision log for "%s" from %d to %d\n' % \ + self.ui.note('fetching revision log for "%s" from %d to %d\n' % (self.module, from_revnum, to_revnum)) try: discover_changed_paths = True strict_node_history = False - svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0, - discover_changed_paths, strict_node_history, - receivelog) - for entry in received: - parselogentry(*entry) + for entry in self.get_log([self.module], from_revnum, to_revnum): + orig_paths, revnum, author, date, message = entry + if self.is_blacklisted(revnum): + self.ui.note('skipping blacklisted revision %d\n' % revnum) + continue + if orig_paths is None: + self.ui.debug('revision %d has no entries\n' % revnum) + continue + parselogentry(orig_paths, revnum, author, date, message) except SubversionException, (_, num): if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION: - raise NoSuchRevision(branch=self, + raise NoSuchRevision(branch=self, revision="Revision number %d" % to_revnum) raise @@ -567,7 +646,6 @@ class convert_svn(converter_source): dirents = getdir[0] if type(dirents) == int: # got here once due to infinite recursion bug - # pprint.pprint(getdir) return c = dirents.keys() c.sort() diff --git a/hgext/convert/transport.py b/hgext/convert/transport.py --- a/hgext/convert/transport.py +++ b/hgext/convert/transport.py @@ -24,9 +24,10 @@ from tempfile import mktemp from svn.core import SubversionException, Pool import svn.ra +import svn.client import svn.core -# Some older versions of the Python bindings need to be +# Some older versions of the Python bindings need to be # explicitly initialized. But what we want to do probably # won't work worth a darn against those libraries anyway! svn.ra.initialize() @@ -48,21 +49,6 @@ def _create_auth_baton(pool): ] return svn.core.svn_auth_open(providers, pool) - -# # The SVN libraries don't like trailing slashes... -# return url.rstrip('/') - - -class SvnRaCallbacks(svn.ra.callbacks2_t): - """Remote access callbacks implementation for bzr-svn.""" - def __init__(self, pool): - svn.ra.callbacks2_t.__init__(self) - self.auth_baton = _create_auth_baton(pool) - self.pool = pool - - def open_tmp_file(self, pool): - return mktemp(prefix='tailor-svn') - class NotBranchError(SubversionException): pass @@ -73,25 +59,30 @@ class SvnRaTransport(object): def __init__(self, url="", ra=None): self.pool = Pool() self.svn_url = url + self.username = '' + self.password = '' # Only Subversion 1.4 has reparent() if ra is None or not hasattr(svn.ra, 'reparent'): - self.callbacks = SvnRaCallbacks(self.pool) + self.client = svn.client.create_context(self.pool) + ab = _create_auth_baton(self.pool) + if False: + svn.core.svn_auth_set_parameter( + ab, svn.core.SVN_AUTH_PARAM_DEFAULT_USERNAME, self.username) + svn.core.svn_auth_set_parameter( + ab, svn.core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, self.password) + self.client.auth_baton = ab + self.client.config = svn_config try: - ver = svn.ra.version() - try: # Older SVN bindings - self.ra = svn.ra.open2(self.svn_url.encode('utf8'), self.callbacks, None, svn_config, None) - except TypeError, e: - self.ra = svn.ra.open2(self.svn_url.encode('utf8'), self.callbacks, svn_config, None) + self.ra = svn.client.open_ra_session( + self.svn_url.encode('utf8'), + self.client, self.pool) except SubversionException, (_, num): - if num == svn.core.SVN_ERR_RA_ILLEGAL_URL: - raise NotBranchError(url) - if num == svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED: - raise NotBranchError(url) - if num == svn.core.SVN_ERR_BAD_URL: + if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL, + svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, + svn.core.SVN_ERR_BAD_URL): raise NotBranchError(url) raise - else: self.ra = ra svn.ra.reparent(self.ra, self.svn_url.encode('utf8')) diff --git a/hgext/purge.py b/hgext/purge.py --- a/hgext/purge.py +++ b/hgext/purge.py @@ -31,7 +31,7 @@ from mercurial import hg, util from mercurial.i18n import _ import os -def dopurge(ui, repo, dirs=None, act=True, ignored=False, +def dopurge(ui, repo, dirs=None, act=True, ignored=False, abort_on_err=False, eol='\n', force=False, include=None, exclude=None): def error(msg): diff --git a/mercurial/archival.py b/mercurial/archival.py --- a/mercurial/archival.py +++ b/mercurial/archival.py @@ -200,8 +200,9 @@ def archive(repo, dest, node, kind, deco prefix is name of path to put before every archive member.''' - def write(name, mode, islink, data): + def write(name, mode, islink, getdata): if matchfn and not matchfn(name): return + data = getdata() if decode: data = repo.wwritedata(name, data) archiver.addfile(name, mode, islink, data) @@ -212,8 +213,8 @@ def archive(repo, dest, node, kind, deco items = m.items() items.sort() write('.hg_archival.txt', 0644, False, - 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node))) + lambda: 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node))) for filename, filenode in items: write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename), - repo.file(filename).read(filenode)) + lambda: repo.file(filename).read(filenode)) archiver.done() diff --git a/mercurial/changelog.py b/mercurial/changelog.py --- a/mercurial/changelog.py +++ b/mercurial/changelog.py @@ -42,7 +42,7 @@ class appender: def flush(self): pass def close(self): - close(self.fp) + self.fp.close() def seek(self, offset, whence=0): '''virtual file offset spans real file and data''' @@ -58,7 +58,6 @@ class appender: def read(self, count=-1): '''only trick here is reads that span real file and data''' ret = "" - old_offset = self.offset if self.offset < self.size: s = self.fp.read(count) ret = s diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -316,7 +316,7 @@ def dispatch(ui, args, argv0=None): util._fallbackencoding = fallback fullargs = args - cmd, func, args, options, cmdoptions = parse(ui, args) + cmd, func, args, options, cmdoptions = parse(lui, args) if options["config"]: raise util.Abort(_("Option --config may not be abbreviated!")) diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -8,7 +8,7 @@ import demandimport; demandimport.enable() from node import * from i18n import _ -import bisect, os, re, sys, urllib, shlex, stat +import bisect, os, re, sys, urllib, stat import ui, hg, util, revlog, bundlerepo, extensions import difflib, patch, time, help, mdiff, tempfile import errno, version, socket @@ -1362,7 +1362,7 @@ def help_(ui, name=None, with_version=Fa addglobalopts(False) - def helplist(select=None): + def helplist(header, select=None): h = {} cmds = {} for c, e in table.items(): @@ -1380,6 +1380,11 @@ def help_(ui, name=None, with_version=Fa h[f] = doc.splitlines(0)[0].rstrip() cmds[f] = c.lstrip("^") + if not h: + ui.status(_('no commands defined\n')) + return + + ui.status(header) fns = h.keys() fns.sort() m = max(map(len, fns)) @@ -1429,14 +1434,10 @@ def help_(ui, name=None, with_version=Fa try: ct = mod.cmdtable except AttributeError: - ct = None - if not ct: - ui.status(_('no commands defined\n')) - return - - ui.status(_('list of commands:\n\n')) + ct = {} + modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct]) - helplist(modcmds.has_key) + helplist(_('list of commands:\n\n'), modcmds.has_key) if name and name != 'shortlist': i = None @@ -1460,11 +1461,11 @@ def help_(ui, name=None, with_version=Fa # list of commands if name == "shortlist": - ui.status(_('basic commands:\n\n')) + header = _('basic commands:\n\n') else: - ui.status(_('list of commands:\n\n')) - - helplist() + header = _('list of commands:\n\n') + + helplist(header) # list all option lists opt_output = [] @@ -2040,14 +2041,12 @@ def paths(ui, repo, search=None): for name, path in ui.configitems("paths"): ui.write("%s = %s\n" % (name, path)) -def postincoming(ui, repo, modheads, optupdate, wasempty): +def postincoming(ui, repo, modheads, optupdate): if modheads == 0: return if optupdate: - if wasempty: - return hg.update(repo, repo.lookup('default')) - elif modheads == 1: - return hg.update(repo, repo.changelog.tip()) # update + if modheads == 1: + return hg.update(repo, None) else: ui.status(_("not updating, since new heads added\n")) if modheads > 1: @@ -2108,9 +2107,8 @@ def pull(ui, repo, source="default", **o error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.") raise util.Abort(error) - wasempty = repo.changelog.count() == 0 modheads = repo.pull(other, heads=revs, force=opts['force']) - return postincoming(ui, repo, modheads, opts['update'], wasempty) + return postincoming(ui, repo, modheads, opts['update']) def push(ui, repo, dest=None, **opts): """push changes to the specified destination @@ -2211,7 +2209,6 @@ def remove(ui, repo, *pats, **opts): Modified files and added files are not removed by default. To remove them, use the -f/--force option. """ - names = [] if not opts['after'] and not pats: raise util.Abort(_('no files specified')) files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) @@ -2681,8 +2678,6 @@ def unbundle(ui, repo, fname1, *fnames, bundle command. """ fnames = (fname1,) + fnames - result = None - wasempty = repo.changelog.count() == 0 for fname in fnames: if os.path.exists(fname): f = open(fname, "rb") @@ -2691,7 +2686,7 @@ def unbundle(ui, repo, fname1, *fnames, gen = changegroup.readbundle(f, fname) modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname) - return postincoming(ui, repo, modheads, opts['update'], wasempty) + return postincoming(ui, repo, modheads, opts['update']) def update(ui, repo, node=None, rev=None, clean=False, date=None): """update working directory diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -21,6 +21,7 @@ class dirstate(object): self._opener = opener self._root = root self._dirty = False + self._dirtypl = False self._ui = ui def __getattr__(self, name): @@ -113,7 +114,7 @@ class dirstate(object): return self._branch def setparents(self, p1, p2=nullid): - self._dirty = True + self._dirty = self._dirtypl = True self._pl = p1, p2 def setbranch(self, branch): @@ -123,7 +124,8 @@ class dirstate(object): def _read(self): self._map = {} self._copymap = {} - self._pl = [nullid, nullid] + if not self._dirtypl: + self._pl = [nullid, nullid] try: st = self._opener("dirstate").read() except IOError, err: @@ -132,7 +134,8 @@ class dirstate(object): if not st: return - self._pl = [st[:20], st[20: 40]] + if not self._dirtypl: + self._pl = [st[:20], st[20: 40]] # deref fields so they will be local in loop dmap = self._map @@ -157,8 +160,8 @@ class dirstate(object): def invalidate(self): for a in "_map _copymap _branch _pl _dirs _ignore".split(): - if hasattr(self, a): - self.__delattr__(a) + if a in self.__dict__: + delattr(self, a) self._dirty = False def copy(self, source, dest): @@ -271,7 +274,7 @@ class dirstate(object): st = self._opener("dirstate", "w", atomictemp=True) st.write(cs.getvalue()) st.rename() - self._dirty = False + self._dirty = self._dirtypl = False def _filter(self, files): ret = {} diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -244,7 +244,7 @@ class hgwebdir(object): if up < 0: break virtual = virtual[:up] - + req.write(tmpl("notfound", repo=virtual)) else: if req.form.has_key('static'): diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py +++ b/mercurial/hgweb/server.py @@ -39,7 +39,7 @@ class _error_logger(object): class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler): url_scheme = 'http' - + def __init__(self, *args, **kargs): self.protocol_version = 'HTTP/1.1' BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs) @@ -173,7 +173,7 @@ class _hgwebhandler(object, BaseHTTPServ class _shgwebhandler(_hgwebhandler): url_scheme = 'https' - + def setup(self): self.connection = self.request self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) diff --git a/mercurial/lock.py b/mercurial/lock.py --- a/mercurial/lock.py +++ b/mercurial/lock.py @@ -29,14 +29,13 @@ class lock(object): # old-style lock: symlink to pid # new-style lock: symlink to hostname:pid + _host = None + def __init__(self, file, timeout=-1, releasefn=None, desc=None): self.f = file self.held = 0 self.timeout = timeout self.releasefn = releasefn - self.id = None - self.host = None - self.pid = None self.desc = desc self.lock() @@ -59,13 +58,12 @@ class lock(object): inst.locker) def trylock(self): - if self.id is None: - self.host = socket.gethostname() - self.pid = os.getpid() - self.id = '%s:%s' % (self.host, self.pid) + if lock._host is None: + lock._host = socket.gethostname() + lockname = '%s:%s' % (lock._host, os.getpid()) while not self.held: try: - util.makelock(self.id, self.f) + util.makelock(lockname, self.f) self.held = 1 except (OSError, IOError), why: if why.errno == errno.EEXIST: @@ -93,7 +91,7 @@ class lock(object): host, pid = locker.split(":", 1) except ValueError: return locker - if host != self.host: + if host != lock._host: return locker try: pid = int(pid) diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -281,7 +281,7 @@ def externalpatch(patcher, args, patchna def internalpatch(patchname, ui, strip, cwd, files): """use builtin patch to apply to the working directory. returns whether patch was applied with fuzz factor.""" - fp = file(patchname) + fp = file(patchname, 'rb') if cwd: curdir = os.getcwd() os.chdir(cwd) @@ -303,7 +303,7 @@ class patchfile: self.fname = fname self.ui = ui try: - fp = file(fname, 'r') + fp = file(fname, 'rb') self.lines = fp.readlines() self.exists = True except IOError: @@ -383,7 +383,7 @@ class patchfile: try: os.unlink(fname) except: pass - fp = file(fname, 'w') + fp = file(fname, 'wb') base = os.path.basename(self.fname) fp.write("--- %s\n+++ %s\n" % (base, base)) for x in self.rej: @@ -402,7 +402,7 @@ class patchfile: if st.st_nlink > 1: os.unlink(dest) except: pass - fp = file(dest, 'w') + fp = file(dest, 'wb') if st: os.chmod(dest, st.st_mode) fp.writelines(self.lines) @@ -777,13 +777,13 @@ def selectfile(afile_orig, bfile_orig, h if count == 0: return path.rstrip() while count > 0: - i = path.find(os.sep, i) + i = path.find('/', i) if i == -1: raise PatchError(_("unable to strip away %d dirs from %s") % (count, path)) i += 1 # consume '//' in the path - while i < pathlen - 1 and path[i] == os.sep: + while i < pathlen - 1 and path[i] == '/': i += 1 count -= 1 return path[i:].rstrip() diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -616,7 +616,7 @@ def rename(src, dst): """forcibly rename a file""" try: os.rename(src, dst) - except OSError, err: + except OSError, err: # FIXME: check err (EEXIST ?) # on windows, rename to existing file is not allowed, so we # must delete destination first. but if file is open, unlink # schedules it for delete but does not delete it. rename @@ -1303,7 +1303,11 @@ class opener(object): os.makedirs(dirname) if self._can_symlink: - os.symlink(src, linkname) + try: + os.symlink(src, linkname) + except OSError, err: + raise OSError(err.errno, _('could not symlink to %r: %s') % + (src, err.strerror), linkname) else: f = self(self, dst, "w") f.write(src) diff --git a/mercurial/util_win32.py b/mercurial/util_win32.py --- a/mercurial/util_win32.py +++ b/mercurial/util_win32.py @@ -209,9 +209,9 @@ class posixfile_nt(object): def __init__(self, name, mode='rb'): access = 0 - if 'r' in mode or '+' in mode: + if 'r' in mode: access |= win32file.GENERIC_READ - if 'w' in mode or 'a' in mode: + if 'w' in mode or 'a' in mode or '+' in mode: access |= win32file.GENERIC_WRITE if 'r' in mode: creation = win32file.OPEN_EXISTING diff --git a/tests/test-extension b/tests/test-extension --- a/tests/test-extension +++ b/tests/test-extension @@ -64,3 +64,18 @@ emptypath=`pwd`/empty.py echo '[extensions]' > $HGRCPATH echo "empty = $emptypath" >> $HGRCPATH hg help empty + +cat > debugextension.py < $HGRCPATH +echo "debugextension = $debugpath" >> $HGRCPATH +hg help debugextension +hg --debug help debugextension diff --git a/tests/test-extension.out b/tests/test-extension.out --- a/tests/test-extension.out +++ b/tests/test-extension.out @@ -22,3 +22,30 @@ Foo empty extension - empty cmdtable no commands defined +debugextension extension - only debugcommands + +no commands defined +debugextension extension - only debugcommands + +list of commands: + + debugfoobar: + yet another debug command + +global options: + -R --repository repository root directory or symbolic path name + --cwd change working directory + -y --noninteractive do not prompt, assume 'yes' for any required answers + -q --quiet suppress output + -v --verbose enable additional output + --config set/override config option + --debug enable debugging output + --debugger start debugger + --encoding set the charset encoding (default: ascii) + --encodingmode set the charset encoding mode (default: strict) + --lsprof print improved command execution profile + --traceback print traceback on exception + --time time how long the command takes + --profile print command execution profile + --version output version information and exit + -h --help display help and exit diff --git a/tests/test-tag b/tests/test-tag --- a/tests/test-tag +++ b/tests/test-tag @@ -29,14 +29,18 @@ newline' hg tag -l 'xx:xx' echo % issue 601 -mv .hg/localtags .hg/ltags -head -1 .hg/ltags | tr -d '\n' > .hg/localtags +python << EOF +f = file('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close() +f = file('.hg/localtags', 'w'); f.write(last); f.close() +EOF cat .hg/localtags hg tag -l localnewline cat .hg/localtags -mv .hgtags hgtags -head -1 hgtags | tr -d '\n' > .hgtags +python << EOF +f = file('.hgtags'); last = f.readlines()[-1][:-1]; f.close() +f = file('.hgtags', 'w'); f.write(last); f.close() +EOF hg ci -d '1000000 0' -m'broken manual edit of .hgtags' cat .hgtags hg tag -d '1000000 0' newline