summaryrefslogtreecommitdiff
path: root/sys/src/cmd/hg/mercurial
diff options
context:
space:
mode:
authorOri Bernstein <ori@eigenstate.org>2021-06-14 00:00:37 +0000
committerOri Bernstein <ori@eigenstate.org>2021-06-14 00:00:37 +0000
commita73a964e51247ed169d322c725a3a18859f109a3 (patch)
tree3f752d117274d444bda44e85609aeac1acf313f3 /sys/src/cmd/hg/mercurial
parente64efe273fcb921a61bf27d33b230c4e64fcd425 (diff)
python, hg: tow outside the environment.
they've served us well, and can ride off into the sunset.
Diffstat (limited to 'sys/src/cmd/hg/mercurial')
-rw-r--r--sys/src/cmd/hg/mercurial/__init__.py0
-rw-r--r--sys/src/cmd/hg/mercurial/ancestor.py85
-rw-r--r--sys/src/cmd/hg/mercurial/archival.py226
-rw-r--r--sys/src/cmd/hg/mercurial/base85.c155
-rw-r--r--sys/src/cmd/hg/mercurial/bdiff.c401
-rw-r--r--sys/src/cmd/hg/mercurial/bundlerepo.py303
-rw-r--r--sys/src/cmd/hg/mercurial/byterange.py468
-rw-r--r--sys/src/cmd/hg/mercurial/changegroup.py140
-rw-r--r--sys/src/cmd/hg/mercurial/changelog.py228
-rw-r--r--sys/src/cmd/hg/mercurial/cmdutil.py1254
-rw-r--r--sys/src/cmd/hg/mercurial/commands.py3565
-rw-r--r--sys/src/cmd/hg/mercurial/config.py137
-rw-r--r--sys/src/cmd/hg/mercurial/context.py818
-rw-r--r--sys/src/cmd/hg/mercurial/copies.py233
-rw-r--r--sys/src/cmd/hg/mercurial/demandimport.py139
-rw-r--r--sys/src/cmd/hg/mercurial/diffhelpers.c156
-rw-r--r--sys/src/cmd/hg/mercurial/dirstate.py601
-rw-r--r--sys/src/cmd/hg/mercurial/dispatch.py501
-rw-r--r--sys/src/cmd/hg/mercurial/encoding.py75
-rw-r--r--sys/src/cmd/hg/mercurial/error.py72
-rw-r--r--sys/src/cmd/hg/mercurial/extensions.py178
-rw-r--r--sys/src/cmd/hg/mercurial/fancyopts.py110
-rw-r--r--sys/src/cmd/hg/mercurial/filelog.py68
-rw-r--r--sys/src/cmd/hg/mercurial/filemerge.py231
-rw-r--r--sys/src/cmd/hg/mercurial/graphmod.py119
-rw-r--r--sys/src/cmd/hg/mercurial/hbisect.py145
-rw-r--r--sys/src/cmd/hg/mercurial/help.py527
-rw-r--r--sys/src/cmd/hg/mercurial/hg.py367
-rw-r--r--sys/src/cmd/hg/mercurial/hook.py135
-rw-r--r--sys/src/cmd/hg/mercurial/httprepo.py258
-rw-r--r--sys/src/cmd/hg/mercurial/i18n.py52
-rw-r--r--sys/src/cmd/hg/mercurial/ignore.py103
-rw-r--r--sys/src/cmd/hg/mercurial/keepalive.py671
-rw-r--r--sys/src/cmd/hg/mercurial/localrepo.py2156
-rw-r--r--sys/src/cmd/hg/mercurial/lock.py137
-rw-r--r--sys/src/cmd/hg/mercurial/lsprof.py113
-rw-r--r--sys/src/cmd/hg/mercurial/lsprofcalltree.py86
-rw-r--r--sys/src/cmd/hg/mercurial/mail.py190
-rw-r--r--sys/src/cmd/hg/mercurial/manifest.py201
-rw-r--r--sys/src/cmd/hg/mercurial/match.py249
-rw-r--r--sys/src/cmd/hg/mercurial/mdiff.py269
-rw-r--r--sys/src/cmd/hg/mercurial/merge.py481
-rw-r--r--sys/src/cmd/hg/mercurial/minirst.py343
-rw-r--r--sys/src/cmd/hg/mercurial/mpatch.c444
-rw-r--r--sys/src/cmd/hg/mercurial/node.py18
-rw-r--r--sys/src/cmd/hg/mercurial/osutil.c534
-rw-r--r--sys/src/cmd/hg/mercurial/parsers.c435
-rw-r--r--sys/src/cmd/hg/mercurial/patch.py1454
-rw-r--r--sys/src/cmd/hg/mercurial/posix.py252
-rw-r--r--sys/src/cmd/hg/mercurial/repair.py145
-rw-r--r--sys/src/cmd/hg/mercurial/repo.py43
-rw-r--r--sys/src/cmd/hg/mercurial/revlog.py1376
-rw-r--r--sys/src/cmd/hg/mercurial/simplemerge.py451
-rw-r--r--sys/src/cmd/hg/mercurial/sshrepo.py260
-rw-r--r--sys/src/cmd/hg/mercurial/sshserver.py225
-rw-r--r--sys/src/cmd/hg/mercurial/statichttprepo.py134
-rw-r--r--sys/src/cmd/hg/mercurial/store.py333
-rw-r--r--sys/src/cmd/hg/mercurial/streamclone.py67
-rw-r--r--sys/src/cmd/hg/mercurial/strutil.py34
-rw-r--r--sys/src/cmd/hg/mercurial/subrepo.py197
-rw-r--r--sys/src/cmd/hg/mercurial/tags.py338
-rw-r--r--sys/src/cmd/hg/mercurial/templatefilters.py211
-rw-r--r--sys/src/cmd/hg/mercurial/templater.py245
-rw-r--r--sys/src/cmd/hg/mercurial/transaction.py165
-rw-r--r--sys/src/cmd/hg/mercurial/ui.py381
-rw-r--r--sys/src/cmd/hg/mercurial/url.py533
-rw-r--r--sys/src/cmd/hg/mercurial/util.py1284
-rw-r--r--sys/src/cmd/hg/mercurial/verify.py258
-rw-r--r--sys/src/cmd/hg/mercurial/win32.py144
-rw-r--r--sys/src/cmd/hg/mercurial/windows.py292
70 files changed, 0 insertions, 26999 deletions
diff --git a/sys/src/cmd/hg/mercurial/__init__.py b/sys/src/cmd/hg/mercurial/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/sys/src/cmd/hg/mercurial/__init__.py
+++ /dev/null
diff --git a/sys/src/cmd/hg/mercurial/ancestor.py b/sys/src/cmd/hg/mercurial/ancestor.py
deleted file mode 100644
index 56464283b..000000000
--- a/sys/src/cmd/hg/mercurial/ancestor.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# ancestor.py - generic DAG ancestor algorithm for mercurial
-#
-# Copyright 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import heapq
-
-def ancestor(a, b, pfunc):
- """
- return the least common ancestor of nodes a and b or None if there
- is no such ancestor.
-
- pfunc must return a list of parent vertices
- """
-
- if a == b:
- return a
-
- # find depth from root of all ancestors
- parentcache = {}
- visit = [a, b]
- depth = {}
- while visit:
- vertex = visit[-1]
- pl = pfunc(vertex)
- parentcache[vertex] = pl
- if not pl:
- depth[vertex] = 0
- visit.pop()
- else:
- for p in pl:
- if p == a or p == b: # did we find a or b as a parent?
- return p # we're done
- if p not in depth:
- visit.append(p)
- if visit[-1] == vertex:
- depth[vertex] = min([depth[p] for p in pl]) - 1
- visit.pop()
-
- # traverse ancestors in order of decreasing distance from root
- def ancestors(vertex):
- h = [(depth[vertex], vertex)]
- seen = set()
- while h:
- d, n = heapq.heappop(h)
- if n not in seen:
- seen.add(n)
- yield (d, n)
- for p in parentcache[n]:
- heapq.heappush(h, (depth[p], p))
-
- def generations(vertex):
- sg, s = None, set()
- for g, v in ancestors(vertex):
- if g != sg:
- if sg:
- yield sg, s
- sg, s = g, set((v,))
- else:
- s.add(v)
- yield sg, s
-
- x = generations(a)
- y = generations(b)
- gx = x.next()
- gy = y.next()
-
- # increment each ancestor list until it is closer to root than
- # the other, or they match
- try:
- while 1:
- if gx[0] == gy[0]:
- for v in gx[1]:
- if v in gy[1]:
- return v
- gy = y.next()
- gx = x.next()
- elif gx[0] > gy[0]:
- gy = y.next()
- else:
- gx = x.next()
- except StopIteration:
- return None
diff --git a/sys/src/cmd/hg/mercurial/archival.py b/sys/src/cmd/hg/mercurial/archival.py
deleted file mode 100644
index 17093ce0f..000000000
--- a/sys/src/cmd/hg/mercurial/archival.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# archival.py - revision archival for mercurial
-#
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-from node import hex
-import util
-import cStringIO, os, stat, tarfile, time, zipfile
-import zlib, gzip
-
-def tidyprefix(dest, prefix, suffixes):
- '''choose prefix to use for names in archive. make sure prefix is
- safe for consumers.'''
-
- if prefix:
- prefix = util.normpath(prefix)
- else:
- if not isinstance(dest, str):
- raise ValueError('dest must be string if no prefix')
- prefix = os.path.basename(dest)
- lower = prefix.lower()
- for sfx in suffixes:
- if lower.endswith(sfx):
- prefix = prefix[:-len(sfx)]
- break
- lpfx = os.path.normpath(util.localpath(prefix))
- prefix = util.pconvert(lpfx)
- if not prefix.endswith('/'):
- prefix += '/'
- if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
- raise util.Abort(_('archive prefix contains illegal components'))
- return prefix
-
-class tarit(object):
- '''write archive to tar file or stream. can write uncompressed,
- or compress with gzip or bzip2.'''
-
- class GzipFileWithTime(gzip.GzipFile):
-
- def __init__(self, *args, **kw):
- timestamp = None
- if 'timestamp' in kw:
- timestamp = kw.pop('timestamp')
- if timestamp is None:
- self.timestamp = time.time()
- else:
- self.timestamp = timestamp
- gzip.GzipFile.__init__(self, *args, **kw)
-
- def _write_gzip_header(self):
- self.fileobj.write('\037\213') # magic header
- self.fileobj.write('\010') # compression method
- # Python 2.6 deprecates self.filename
- fname = getattr(self, 'name', None) or self.filename
- flags = 0
- if fname:
- flags = gzip.FNAME
- self.fileobj.write(chr(flags))
- gzip.write32u(self.fileobj, long(self.timestamp))
- self.fileobj.write('\002')
- self.fileobj.write('\377')
- if fname:
- self.fileobj.write(fname + '\000')
-
- def __init__(self, dest, prefix, mtime, kind=''):
- self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
- '.tgz', '.tbz2'])
- self.mtime = mtime
-
- def taropen(name, mode, fileobj=None):
- if kind == 'gz':
- mode = mode[0]
- if not fileobj:
- fileobj = open(name, mode + 'b')
- gzfileobj = self.GzipFileWithTime(name, mode + 'b',
- zlib.Z_BEST_COMPRESSION,
- fileobj, timestamp=mtime)
- return tarfile.TarFile.taropen(name, mode, gzfileobj)
- else:
- return tarfile.open(name, mode + kind, fileobj)
-
- if isinstance(dest, str):
- self.z = taropen(dest, mode='w:')
- else:
- # Python 2.5-2.5.1 have a regression that requires a name arg
- self.z = taropen(name='', mode='w|', fileobj=dest)
-
- def addfile(self, name, mode, islink, data):
- i = tarfile.TarInfo(self.prefix + name)
- i.mtime = self.mtime
- i.size = len(data)
- if islink:
- i.type = tarfile.SYMTYPE
- i.mode = 0777
- i.linkname = data
- data = None
- i.size = 0
- else:
- i.mode = mode
- data = cStringIO.StringIO(data)
- self.z.addfile(i, data)
-
- def done(self):
- self.z.close()
-
-class tellable(object):
- '''provide tell method for zipfile.ZipFile when writing to http
- response file object.'''
-
- def __init__(self, fp):
- self.fp = fp
- self.offset = 0
-
- def __getattr__(self, key):
- return getattr(self.fp, key)
-
- def write(self, s):
- self.fp.write(s)
- self.offset += len(s)
-
- def tell(self):
- return self.offset
-
-class zipit(object):
- '''write archive to zip file or stream. can write uncompressed,
- or compressed with deflate.'''
-
- def __init__(self, dest, prefix, mtime, compress=True):
- self.prefix = tidyprefix(dest, prefix, ('.zip',))
- if not isinstance(dest, str):
- try:
- dest.tell()
- except (AttributeError, IOError):
- dest = tellable(dest)
- self.z = zipfile.ZipFile(dest, 'w',
- compress and zipfile.ZIP_DEFLATED or
- zipfile.ZIP_STORED)
- self.date_time = time.gmtime(mtime)[:6]
-
- def addfile(self, name, mode, islink, data):
- i = zipfile.ZipInfo(self.prefix + name, self.date_time)
- i.compress_type = self.z.compression
- # unzip will not honor unix file modes unless file creator is
- # set to unix (id 3).
- i.create_system = 3
- ftype = stat.S_IFREG
- if islink:
- mode = 0777
- ftype = stat.S_IFLNK
- i.external_attr = (mode | ftype) << 16L
- self.z.writestr(i, data)
-
- def done(self):
- self.z.close()
-
-class fileit(object):
- '''write archive as files in directory.'''
-
- def __init__(self, name, prefix, mtime):
- if prefix:
- raise util.Abort(_('cannot give prefix when archiving to files'))
- self.basedir = name
- self.opener = util.opener(self.basedir)
-
- def addfile(self, name, mode, islink, data):
- if islink:
- self.opener.symlink(data, name)
- return
- f = self.opener(name, "w", atomictemp=True)
- f.write(data)
- f.rename()
- destfile = os.path.join(self.basedir, name)
- os.chmod(destfile, mode)
-
- def done(self):
- pass
-
-archivers = {
- 'files': fileit,
- 'tar': tarit,
- 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
- 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
- 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
- 'zip': zipit,
- }
-
-def archive(repo, dest, node, kind, decode=True, matchfn=None,
- prefix=None, mtime=None):
- '''create archive of repo as it was at node.
-
- dest can be name of directory, name of archive file, or file
- object to write archive to.
-
- kind is type of archive to create.
-
- decode tells whether to put files through decode filters from
- hgrc.
-
- matchfn is function to filter names of files to write to archive.
-
- prefix is name of path to put before every archive member.'''
-
- 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)
-
- if kind not in archivers:
- raise util.Abort(_("unknown archive type '%s'") % kind)
-
- ctx = repo[node]
- archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
-
- if repo.ui.configbool("ui", "archivemeta", True):
- write('.hg_archival.txt', 0644, False,
- lambda: 'repo: %s\nnode: %s\n' % (
- hex(repo.changelog.node(0)), hex(node)))
- for f in ctx:
- ff = ctx.flags(f)
- write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
- archiver.done()
diff --git a/sys/src/cmd/hg/mercurial/base85.c b/sys/src/cmd/hg/mercurial/base85.c
deleted file mode 100644
index 3e6c0614c..000000000
--- a/sys/src/cmd/hg/mercurial/base85.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- base85 codec
-
- Copyright 2006 Brendan Cully <brendan@kublai.com>
-
- This software may be used and distributed according to the terms of
- the GNU General Public License, incorporated herein by reference.
-
- Largely based on git's implementation
-*/
-
-#include <Python.h>
-
-static const char b85chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
-static char b85dec[256];
-
-static void
-b85prep(void)
-{
- int i;
-
- memset(b85dec, 0, sizeof(b85dec));
- for (i = 0; i < sizeof(b85chars); i++)
- b85dec[(int)(b85chars[i])] = i + 1;
-}
-
-static PyObject *
-b85encode(PyObject *self, PyObject *args)
-{
- const unsigned char *text;
- PyObject *out;
- char *dst;
- int len, olen, i;
- unsigned int acc, val, ch;
- int pad = 0;
-
- if (!PyArg_ParseTuple(args, "s#|i", &text, &len, &pad))
- return NULL;
-
- if (pad)
- olen = ((len + 3) / 4 * 5) - 3;
- else {
- olen = len % 4;
- if (olen)
- olen++;
- olen += len / 4 * 5;
- }
- if (!(out = PyString_FromStringAndSize(NULL, olen + 3)))
- return NULL;
-
- dst = PyString_AS_STRING(out);
-
- while (len) {
- acc = 0;
- for (i = 24; i >= 0; i -= 8) {
- ch = *text++;
- acc |= ch << i;
- if (--len == 0)
- break;
- }
- for (i = 4; i >= 0; i--) {
- val = acc % 85;
- acc /= 85;
- dst[i] = b85chars[val];
- }
- dst += 5;
- }
-
- if (!pad)
- _PyString_Resize(&out, olen);
-
- return out;
-}
-
-static PyObject *
-b85decode(PyObject *self, PyObject *args)
-{
- PyObject *out;
- const char *text;
- char *dst;
- int len, i, j, olen, c, cap;
- unsigned int acc;
-
- if (!PyArg_ParseTuple(args, "s#", &text, &len))
- return NULL;
-
- olen = len / 5 * 4;
- i = len % 5;
- if (i)
- olen += i - 1;
- if (!(out = PyString_FromStringAndSize(NULL, olen)))
- return NULL;
-
- dst = PyString_AS_STRING(out);
-
- i = 0;
- while (i < len)
- {
- acc = 0;
- cap = len - i - 1;
- if (cap > 4)
- cap = 4;
- for (j = 0; j < cap; i++, j++)
- {
- c = b85dec[(int)*text++] - 1;
- if (c < 0)
- return PyErr_Format(PyExc_ValueError, "Bad base85 character at position %d", i);
- acc = acc * 85 + c;
- }
- if (i++ < len)
- {
- c = b85dec[(int)*text++] - 1;
- if (c < 0)
- return PyErr_Format(PyExc_ValueError, "Bad base85 character at position %d", i);
- /* overflow detection: 0xffffffff == "|NsC0",
- * "|NsC" == 0x03030303 */
- if (acc > 0x03030303 || (acc *= 85) > 0xffffffff - c)
- return PyErr_Format(PyExc_ValueError, "Bad base85 sequence at position %d", i);
- acc += c;
- }
-
- cap = olen < 4 ? olen : 4;
- olen -= cap;
- for (j = 0; j < 4 - cap; j++)
- acc *= 85;
- if (cap && cap < 4)
- acc += 0xffffff >> (cap - 1) * 8;
- for (j = 0; j < cap; j++)
- {
- acc = (acc << 8) | (acc >> 24);
- *dst++ = acc;
- }
- }
-
- return out;
-}
-
-static char base85_doc[] = "Base85 Data Encoding";
-
-static PyMethodDef methods[] = {
- {"b85encode", b85encode, METH_VARARGS,
- "Encode text in base85.\n\n"
- "If the second parameter is true, pad the result to a multiple of "
- "five characters.\n"},
- {"b85decode", b85decode, METH_VARARGS, "Decode base85 text.\n"},
- {NULL, NULL}
-};
-
-PyMODINIT_FUNC initbase85(void)
-{
- Py_InitModule3("base85", methods, base85_doc);
-
- b85prep();
-}
diff --git a/sys/src/cmd/hg/mercurial/bdiff.c b/sys/src/cmd/hg/mercurial/bdiff.c
deleted file mode 100644
index 60d3c633b..000000000
--- a/sys/src/cmd/hg/mercurial/bdiff.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- bdiff.c - efficient binary diff extension for Mercurial
-
- Copyright 2005, 2006 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.
-
- Based roughly on Python difflib
-*/
-
-#include <Python.h>
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
-
-#if defined __hpux || defined __SUNPRO_C || defined _AIX
-# define inline
-#endif
-
-#ifdef __linux
-# define inline __inline
-#endif
-
-#ifdef _WIN32
-#ifdef _MSC_VER
-#define inline __inline
-typedef unsigned long uint32_t;
-#else
-#include <stdint.h>
-#endif
-static uint32_t htonl(uint32_t x)
-{
- return ((x & 0x000000ffUL) << 24) |
- ((x & 0x0000ff00UL) << 8) |
- ((x & 0x00ff0000UL) >> 8) |
- ((x & 0xff000000UL) >> 24);
-}
-#else
-#include <sys/types.h>
-#if defined __BEOS__ && !defined __HAIKU__
-#include <ByteOrder.h>
-#else
-#include <arpa/inet.h>
-#endif
-#include <inttypes.h>
-#endif
-
-struct line {
- int h, len, n, e;
- const char *l;
-};
-
-struct pos {
- int pos, len;
-};
-
-struct hunk {
- int a1, a2, b1, b2;
-};
-
-struct hunklist {
- struct hunk *base, *head;
-};
-
-int splitlines(const char *a, int len, struct line **lr)
-{
- int h, i;
- const char *p, *b = a;
- const char * const plast = a + len - 1;
- struct line *l;
-
- /* count the lines */
- i = 1; /* extra line for sentinel */
- for (p = a; p < a + len; p++)
- if (*p == '\n' || p == plast)
- i++;
-
- *lr = l = (struct line *)malloc(sizeof(struct line) * i);
- if (!l)
- return -1;
-
- /* build the line array and calculate hashes */
- h = 0;
- for (p = a; p < a + len; p++) {
- /* Leonid Yuriev's hash */
- h = (h * 1664525) + *p + 1013904223;
-
- if (*p == '\n' || p == plast) {
- l->h = h;
- h = 0;
- l->len = p - b + 1;
- l->l = b;
- l->n = INT_MAX;
- l++;
- b = p + 1;
- }
- }
-
- /* set up a sentinel */
- l->h = l->len = 0;
- l->l = a + len;
- return i - 1;
-}
-
-int inline cmp(struct line *a, struct line *b)
-{
- return a->h != b->h || a->len != b->len || memcmp(a->l, b->l, a->len);
-}
-
-static int equatelines(struct line *a, int an, struct line *b, int bn)
-{
- int i, j, buckets = 1, t, scale;
- struct pos *h = NULL;
-
- /* build a hash table of the next highest power of 2 */
- while (buckets < bn + 1)
- buckets *= 2;
-
- /* try to allocate a large hash table to avoid collisions */
- for (scale = 4; scale; scale /= 2) {
- h = (struct pos *)malloc(scale * buckets * sizeof(struct pos));
- if (h)
- break;
- }
-
- if (!h)
- return 0;
-
- buckets = buckets * scale - 1;
-
- /* clear the hash table */
- for (i = 0; i <= buckets; i++) {
- h[i].pos = INT_MAX;
- h[i].len = 0;
- }
-
- /* add lines to the hash table chains */
- for (i = bn - 1; i >= 0; i--) {
- /* find the equivalence class */
- for (j = b[i].h & buckets; h[j].pos != INT_MAX;
- j = (j + 1) & buckets)
- if (!cmp(b + i, b + h[j].pos))
- break;
-
- /* add to the head of the equivalence class */
- b[i].n = h[j].pos;
- b[i].e = j;
- h[j].pos = i;
- h[j].len++; /* keep track of popularity */
- }
-
- /* compute popularity threshold */
- t = (bn >= 4000) ? bn / 1000 : bn + 1;
-
- /* match items in a to their equivalence class in b */
- for (i = 0; i < an; i++) {
- /* find the equivalence class */
- for (j = a[i].h & buckets; h[j].pos != INT_MAX;
- j = (j + 1) & buckets)
- if (!cmp(a + i, b + h[j].pos))
- break;
-
- a[i].e = j; /* use equivalence class for quick compare */
- if (h[j].len <= t)
- a[i].n = h[j].pos; /* point to head of match list */
- else
- a[i].n = INT_MAX; /* too popular */
- }
-
- /* discard hash tables */
- free(h);
- return 1;
-}
-
-static int longest_match(struct line *a, struct line *b, struct pos *pos,
- int a1, int a2, int b1, int b2, int *omi, int *omj)
-{
- int mi = a1, mj = b1, mk = 0, mb = 0, i, j, k;
-
- for (i = a1; i < a2; i++) {
- /* skip things before the current block */
- for (j = a[i].n; j < b1; j = b[j].n)
- ;
-
- /* loop through all lines match a[i] in b */
- for (; j < b2; j = b[j].n) {
- /* does this extend an earlier match? */
- if (i > a1 && j > b1 && pos[j - 1].pos == i - 1)
- k = pos[j - 1].len + 1;
- else
- k = 1;
- pos[j].pos = i;
- pos[j].len = k;
-
- /* best match so far? */
- if (k > mk) {
- mi = i;
- mj = j;
- mk = k;
- }
- }
- }
-
- if (mk) {
- mi = mi - mk + 1;
- mj = mj - mk + 1;
- }
-
- /* expand match to include neighboring popular lines */
- while (mi - mb > a1 && mj - mb > b1 &&
- a[mi - mb - 1].e == b[mj - mb - 1].e)
- mb++;
- while (mi + mk < a2 && mj + mk < b2 &&
- a[mi + mk].e == b[mj + mk].e)
- mk++;
-
- *omi = mi - mb;
- *omj = mj - mb;
-
- return mk + mb;
-}
-
-static void recurse(struct line *a, struct line *b, struct pos *pos,
- int a1, int a2, int b1, int b2, struct hunklist *l)
-{
- int i, j, k;
-
- /* find the longest match in this chunk */
- k = longest_match(a, b, pos, a1, a2, b1, b2, &i, &j);
- if (!k)
- return;
-
- /* and recurse on the remaining chunks on either side */
- recurse(a, b, pos, a1, i, b1, j, l);
- l->head->a1 = i;
- l->head->a2 = i + k;
- l->head->b1 = j;
- l->head->b2 = j + k;
- l->head++;
- recurse(a, b, pos, i + k, a2, j + k, b2, l);
-}
-
-static struct hunklist diff(struct line *a, int an, struct line *b, int bn)
-{
- struct hunklist l;
- struct hunk *curr;
- struct pos *pos;
- int t;
-
- /* allocate and fill arrays */
- t = equatelines(a, an, b, bn);
- pos = (struct pos *)calloc(bn ? bn : 1, sizeof(struct pos));
- /* we can't have more matches than lines in the shorter file */
- l.head = l.base = (struct hunk *)malloc(sizeof(struct hunk) *
- ((an<bn ? an:bn) + 1));
-
- if (pos && l.base && t) {
- /* generate the matching block list */
- recurse(a, b, pos, 0, an, 0, bn, &l);
- l.head->a1 = l.head->a2 = an;
- l.head->b1 = l.head->b2 = bn;
- l.head++;
- }
-
- free(pos);
-
- /* normalize the hunk list, try to push each hunk towards the end */
- for (curr = l.base; curr != l.head; curr++) {
- struct hunk *next = curr+1;
- int shift = 0;
-
- if (next == l.head)
- break;
-
- if (curr->a2 == next->a1)
- while (curr->a2+shift < an && curr->b2+shift < bn
- && !cmp(a+curr->a2+shift, b+curr->b2+shift))
- shift++;
- else if (curr->b2 == next->b1)
- while (curr->b2+shift < bn && curr->a2+shift < an
- && !cmp(b+curr->b2+shift, a+curr->a2+shift))
- shift++;
- if (!shift)
- continue;
- curr->b2 += shift;
- next->b1 += shift;
- curr->a2 += shift;
- next->a1 += shift;
- }
-
- return l;
-}
-
-static PyObject *blocks(PyObject *self, PyObject *args)
-{
- PyObject *sa, *sb, *rl = NULL, *m;
- struct line *a, *b;
- struct hunklist l = {NULL, NULL};
- struct hunk *h;
- int an, bn, pos = 0;
-
- if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
- return NULL;
-
- an = splitlines(PyString_AsString(sa), PyString_Size(sa), &a);
- bn = splitlines(PyString_AsString(sb), PyString_Size(sb), &b);
- if (!a || !b)
- goto nomem;
-
- l = diff(a, an, b, bn);
- rl = PyList_New(l.head - l.base);
- if (!l.head || !rl)
- goto nomem;
-
- for (h = l.base; h != l.head; h++) {
- m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
- PyList_SetItem(rl, pos, m);
- pos++;
- }
-
-nomem:
- free(a);
- free(b);
- free(l.base);
- return rl ? rl : PyErr_NoMemory();
-}
-
-static PyObject *bdiff(PyObject *self, PyObject *args)
-{
- char *sa, *sb;
- PyObject *result = NULL;
- struct line *al, *bl;
- struct hunklist l = {NULL, NULL};
- struct hunk *h;
- char encode[12], *rb;
- int an, bn, len = 0, la, lb;
-
- if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb))
- return NULL;
-
- an = splitlines(sa, la, &al);
- bn = splitlines(sb, lb, &bl);
- if (!al || !bl)
- goto nomem;
-
- l = diff(al, an, bl, bn);
- if (!l.head)
- goto nomem;
-
- /* calculate length of output */
- la = lb = 0;
- for (h = l.base; h != l.head; h++) {
- if (h->a1 != la || h->b1 != lb)
- len += 12 + bl[h->b1].l - bl[lb].l;
- la = h->a2;
- lb = h->b2;
- }
-
- result = PyString_FromStringAndSize(NULL, len);
- if (!result)
- goto nomem;
-
- /* build binary patch */
- rb = PyString_AsString(result);
- la = lb = 0;
-
- for (h = l.base; h != l.head; h++) {
- if (h->a1 != la || h->b1 != lb) {
- len = bl[h->b1].l - bl[lb].l;
- *(uint32_t *)(encode) = htonl(al[la].l - al->l);
- *(uint32_t *)(encode + 4) = htonl(al[h->a1].l - al->l);
- *(uint32_t *)(encode + 8) = htonl(len);
- memcpy(rb, encode, 12);
- memcpy(rb + 12, bl[lb].l, len);
- rb += 12 + len;
- }
- la = h->a2;
- lb = h->b2;
- }
-
-nomem:
- free(al);
- free(bl);
- free(l.base);
- return result ? result : PyErr_NoMemory();
-}
-
-static char mdiff_doc[] = "Efficient binary diff.";
-
-static PyMethodDef methods[] = {
- {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"},
- {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"},
- {NULL, NULL}
-};
-
-PyMODINIT_FUNC initbdiff(void)
-{
- Py_InitModule3("bdiff", methods, mdiff_doc);
-}
-
diff --git a/sys/src/cmd/hg/mercurial/bundlerepo.py b/sys/src/cmd/hg/mercurial/bundlerepo.py
deleted file mode 100644
index 14d74e1e5..000000000
--- a/sys/src/cmd/hg/mercurial/bundlerepo.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# bundlerepo.py - repository class for viewing uncompressed bundles
-#
-# Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-"""Repository class for viewing uncompressed bundles.
-
-This provides a read-only repository interface to bundles as if they
-were part of the actual repository.
-"""
-
-from node import nullid
-from i18n import _
-import os, struct, bz2, zlib, tempfile, shutil
-import changegroup, util, mdiff
-import localrepo, changelog, manifest, filelog, revlog, error
-
-class bundlerevlog(revlog.revlog):
- def __init__(self, opener, indexfile, bundlefile,
- linkmapper=None):
- # How it works:
- # to retrieve a revision, we need to know the offset of
- # the revision in the bundlefile (an opened file).
- #
- # We store this offset in the index (start), to differentiate a
- # rev in the bundle and from a rev in the revlog, we check
- # len(index[r]). If the tuple is bigger than 7, it is a bundle
- # (it is bigger since we store the node to which the delta is)
- #
- revlog.revlog.__init__(self, opener, indexfile)
- self.bundlefile = bundlefile
- self.basemap = {}
- def chunkpositer():
- for chunk in changegroup.chunkiter(bundlefile):
- pos = bundlefile.tell()
- yield chunk, pos - len(chunk)
- n = len(self)
- prev = None
- for chunk, start in chunkpositer():
- size = len(chunk)
- if size < 80:
- raise util.Abort(_("invalid changegroup"))
- start += 80
- size -= 80
- node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
- if node in self.nodemap:
- prev = node
- continue
- for p in (p1, p2):
- if not p in self.nodemap:
- raise error.LookupError(p1, self.indexfile,
- _("unknown parent"))
- if linkmapper is None:
- link = n
- else:
- link = linkmapper(cs)
-
- if not prev:
- prev = p1
- # start, size, full unc. size, base (unused), link, p1, p2, node
- e = (revlog.offset_type(start, 0), size, -1, -1, link,
- self.rev(p1), self.rev(p2), node)
- self.basemap[n] = prev
- self.index.insert(-1, e)
- self.nodemap[node] = n
- prev = node
- n += 1
-
- def bundle(self, rev):
- """is rev from the bundle"""
- if rev < 0:
- return False
- return rev in self.basemap
- def bundlebase(self, rev): return self.basemap[rev]
- def chunk(self, rev, df=None, cachelen=4096):
- # Warning: in case of bundle, the diff is against bundlebase,
- # not against rev - 1
- # XXX: could use some caching
- if not self.bundle(rev):
- return revlog.revlog.chunk(self, rev, df)
- self.bundlefile.seek(self.start(rev))
- return self.bundlefile.read(self.length(rev))
-
- def revdiff(self, rev1, rev2):
- """return or calculate a delta between two revisions"""
- if self.bundle(rev1) and self.bundle(rev2):
- # hot path for bundle
- revb = self.rev(self.bundlebase(rev2))
- if revb == rev1:
- return self.chunk(rev2)
- elif not self.bundle(rev1) and not self.bundle(rev2):
- return revlog.revlog.revdiff(self, rev1, rev2)
-
- return mdiff.textdiff(self.revision(self.node(rev1)),
- self.revision(self.node(rev2)))
-
- def revision(self, node):
- """return an uncompressed revision of a given"""
- if node == nullid: return ""
-
- text = None
- chain = []
- iter_node = node
- rev = self.rev(iter_node)
- # reconstruct the revision if it is from a changegroup
- while self.bundle(rev):
- if self._cache and self._cache[0] == iter_node:
- text = self._cache[2]
- break
- chain.append(rev)
- iter_node = self.bundlebase(rev)
- rev = self.rev(iter_node)
- if text is None:
- text = revlog.revlog.revision(self, iter_node)
-
- while chain:
- delta = self.chunk(chain.pop())
- text = mdiff.patches(text, [delta])
-
- p1, p2 = self.parents(node)
- if node != revlog.hash(text, p1, p2):
- raise error.RevlogError(_("integrity check failed on %s:%d")
- % (self.datafile, self.rev(node)))
-
- self._cache = (node, self.rev(node), text)
- return text
-
- def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
- raise NotImplementedError
- def addgroup(self, revs, linkmapper, transaction):
- raise NotImplementedError
- def strip(self, rev, minlink):
- raise NotImplementedError
- def checksize(self):
- raise NotImplementedError
-
-class bundlechangelog(bundlerevlog, changelog.changelog):
- def __init__(self, opener, bundlefile):
- changelog.changelog.__init__(self, opener)
- bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
-
-class bundlemanifest(bundlerevlog, manifest.manifest):
- def __init__(self, opener, bundlefile, linkmapper):
- manifest.manifest.__init__(self, opener)
- bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
- linkmapper)
-
-class bundlefilelog(bundlerevlog, filelog.filelog):
- def __init__(self, opener, path, bundlefile, linkmapper):
- filelog.filelog.__init__(self, opener, path)
- bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
- linkmapper)
-
-class bundlerepository(localrepo.localrepository):
- def __init__(self, ui, path, bundlename):
- self._tempparent = None
- try:
- localrepo.localrepository.__init__(self, ui, path)
- except error.RepoError:
- self._tempparent = tempfile.mkdtemp()
- localrepo.instance(ui, self._tempparent, 1)
- localrepo.localrepository.__init__(self, ui, self._tempparent)
-
- if path:
- self._url = 'bundle:' + path + '+' + bundlename
- else:
- self._url = 'bundle:' + bundlename
-
- self.tempfile = None
- self.bundlefile = open(bundlename, "rb")
- header = self.bundlefile.read(6)
- if not header.startswith("HG"):
- raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
- elif not header.startswith("HG10"):
- raise util.Abort(_("%s: unknown bundle version") % bundlename)
- elif (header == "HG10BZ") or (header == "HG10GZ"):
- fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
- suffix=".hg10un", dir=self.path)
- self.tempfile = temp
- fptemp = os.fdopen(fdtemp, 'wb')
- def generator(f):
- if header == "HG10BZ":
- zd = bz2.BZ2Decompressor()
- zd.decompress("BZ")
- elif header == "HG10GZ":
- zd = zlib.decompressobj()
- for chunk in f:
- yield zd.decompress(chunk)
- gen = generator(util.filechunkiter(self.bundlefile, 4096))
-
- try:
- fptemp.write("HG10UN")
- for chunk in gen:
- fptemp.write(chunk)
- finally:
- fptemp.close()
- self.bundlefile.close()
-
- self.bundlefile = open(self.tempfile, "rb")
- # seek right after the header
- self.bundlefile.seek(6)
- elif header == "HG10UN":
- # nothing to do
- pass
- else:
- raise util.Abort(_("%s: unknown bundle compression type")
- % bundlename)
- # dict with the mapping 'filename' -> position in the bundle
- self.bundlefilespos = {}
-
- @util.propertycache
- def changelog(self):
- c = bundlechangelog(self.sopener, self.bundlefile)
- self.manstart = self.bundlefile.tell()
- return c
-
- @util.propertycache
- def manifest(self):
- self.bundlefile.seek(self.manstart)
- m = bundlemanifest(self.sopener, self.bundlefile, self.changelog.rev)
- self.filestart = self.bundlefile.tell()
- return m
-
- @util.propertycache
- def manstart(self):
- self.changelog
- return self.manstart
-
- @util.propertycache
- def filestart(self):
- self.manifest
- return self.filestart
-
- def url(self):
- return self._url
-
- def file(self, f):
- if not self.bundlefilespos:
- self.bundlefile.seek(self.filestart)
- while 1:
- chunk = changegroup.getchunk(self.bundlefile)
- if not chunk:
- break
- self.bundlefilespos[chunk] = self.bundlefile.tell()
- for c in changegroup.chunkiter(self.bundlefile):
- pass
-
- if f[0] == '/':
- f = f[1:]
- if f in self.bundlefilespos:
- self.bundlefile.seek(self.bundlefilespos[f])
- return bundlefilelog(self.sopener, f, self.bundlefile,
- self.changelog.rev)
- else:
- return filelog.filelog(self.sopener, f)
-
- def close(self):
- """Close assigned bundle file immediately."""
- self.bundlefile.close()
-
- def __del__(self):
- bundlefile = getattr(self, 'bundlefile', None)
- if bundlefile and not bundlefile.closed:
- bundlefile.close()
- tempfile = getattr(self, 'tempfile', None)
- if tempfile is not None:
- os.unlink(tempfile)
- if self._tempparent:
- shutil.rmtree(self._tempparent, True)
-
- def cancopy(self):
- return False
-
- def getcwd(self):
- return os.getcwd() # always outside the repo
-
-def instance(ui, path, create):
- if create:
- raise util.Abort(_('cannot create new bundle repository'))
- parentpath = ui.config("bundle", "mainreporoot", "")
- if parentpath:
- # Try to make the full path relative so we get a nice, short URL.
- # In particular, we don't want temp dir names in test outputs.
- cwd = os.getcwd()
- if parentpath == cwd:
- parentpath = ''
- else:
- cwd = os.path.join(cwd,'')
- if parentpath.startswith(cwd):
- parentpath = parentpath[len(cwd):]
- path = util.drop_scheme('file', path)
- if path.startswith('bundle:'):
- path = util.drop_scheme('bundle', path)
- s = path.split("+", 1)
- if len(s) == 1:
- repopath, bundlename = parentpath, s[0]
- else:
- repopath, bundlename = s
- else:
- repopath, bundlename = parentpath, path
- return bundlerepository(ui, repopath, bundlename)
diff --git a/sys/src/cmd/hg/mercurial/byterange.py b/sys/src/cmd/hg/mercurial/byterange.py
deleted file mode 100644
index f833e8270..000000000
--- a/sys/src/cmd/hg/mercurial/byterange.py
+++ /dev/null
@@ -1,468 +0,0 @@
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc.,
-# 59 Temple Place, Suite 330,
-# Boston, MA 02111-1307 USA
-
-# This file is part of urlgrabber, a high-level cross-protocol url-grabber
-# Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
-
-# $Id: byterange.py,v 1.9 2005/02/14 21:55:07 mstenner Exp $
-
-import os
-import stat
-import urllib
-import urllib2
-import email.Utils
-
-try:
- from cStringIO import StringIO
-except ImportError, msg:
- from StringIO import StringIO
-
-class RangeError(IOError):
- """Error raised when an unsatisfiable range is requested."""
- pass
-
-class HTTPRangeHandler(urllib2.BaseHandler):
- """Handler that enables HTTP Range headers.
-
- This was extremely simple. The Range header is a HTTP feature to
- begin with so all this class does is tell urllib2 that the
- "206 Partial Content" reponse from the HTTP server is what we
- expected.
-
- Example:
- import urllib2
- import byterange
-
- range_handler = range.HTTPRangeHandler()
- opener = urllib2.build_opener(range_handler)
-
- # install it
- urllib2.install_opener(opener)
-
- # create Request and set Range header
- req = urllib2.Request('http://www.python.org/')
- req.header['Range'] = 'bytes=30-50'
- f = urllib2.urlopen(req)
- """
-
- def http_error_206(self, req, fp, code, msg, hdrs):
- # 206 Partial Content Response
- r = urllib.addinfourl(fp, hdrs, req.get_full_url())
- r.code = code
- r.msg = msg
- return r
-
- def http_error_416(self, req, fp, code, msg, hdrs):
- # HTTP's Range Not Satisfiable error
- raise RangeError('Requested Range Not Satisfiable')
-
-class RangeableFileObject:
- """File object wrapper to enable raw range handling.
- This was implemented primarilary for handling range
- specifications for file:// urls. This object effectively makes
- a file object look like it consists only of a range of bytes in
- the stream.
-
- Examples:
- # expose 10 bytes, starting at byte position 20, from
- # /etc/aliases.
- >>> fo = RangeableFileObject(file('/etc/passwd', 'r'), (20,30))
- # seek seeks within the range (to position 23 in this case)
- >>> fo.seek(3)
- # tell tells where your at _within the range_ (position 3 in
- # this case)
- >>> fo.tell()
- # read EOFs if an attempt is made to read past the last
- # byte in the range. the following will return only 7 bytes.
- >>> fo.read(30)
- """
-
- def __init__(self, fo, rangetup):
- """Create a RangeableFileObject.
- fo -- a file like object. only the read() method need be
- supported but supporting an optimized seek() is
- preferable.
- rangetup -- a (firstbyte,lastbyte) tuple specifying the range
- to work over.
- The file object provided is assumed to be at byte offset 0.
- """
- self.fo = fo
- (self.firstbyte, self.lastbyte) = range_tuple_normalize(rangetup)
- self.realpos = 0
- self._do_seek(self.firstbyte)
-
- def __getattr__(self, name):
- """This effectively allows us to wrap at the instance level.
- Any attribute not found in _this_ object will be searched for
- in self.fo. This includes methods."""
- if hasattr(self.fo, name):
- return getattr(self.fo, name)
- raise AttributeError(name)
-
- def tell(self):
- """Return the position within the range.
- This is different from fo.seek in that position 0 is the
- first byte position of the range tuple. For example, if
- this object was created with a range tuple of (500,899),
- tell() will return 0 when at byte position 500 of the file.
- """
- return (self.realpos - self.firstbyte)
-
- def seek(self, offset, whence=0):
- """Seek within the byte range.
- Positioning is identical to that described under tell().
- """
- assert whence in (0, 1, 2)
- if whence == 0: # absolute seek
- realoffset = self.firstbyte + offset
- elif whence == 1: # relative seek
- realoffset = self.realpos + offset
- elif whence == 2: # absolute from end of file
- # XXX: are we raising the right Error here?
- raise IOError('seek from end of file not supported.')
-
- # do not allow seek past lastbyte in range
- if self.lastbyte and (realoffset >= self.lastbyte):
- realoffset = self.lastbyte
-
- self._do_seek(realoffset - self.realpos)
-
- def read(self, size=-1):
- """Read within the range.
- This method will limit the size read based on the range.
- """
- size = self._calc_read_size(size)
- rslt = self.fo.read(size)
- self.realpos += len(rslt)
- return rslt
-
- def readline(self, size=-1):
- """Read lines within the range.
- This method will limit the size read based on the range.
- """
- size = self._calc_read_size(size)
- rslt = self.fo.readline(size)
- self.realpos += len(rslt)
- return rslt
-
- def _calc_read_size(self, size):
- """Handles calculating the amount of data to read based on
- the range.
- """
- if self.lastbyte:
- if size > -1:
- if ((self.realpos + size) >= self.lastbyte):
- size = (self.lastbyte - self.realpos)
- else:
- size = (self.lastbyte - self.realpos)
- return size
-
- def _do_seek(self, offset):
- """Seek based on whether wrapped object supports seek().
- offset is relative to the current position (self.realpos).
- """
- assert offset >= 0
- if not hasattr(self.fo, 'seek'):
- self._poor_mans_seek(offset)
- else:
- self.fo.seek(self.realpos + offset)
- self.realpos += offset
-
- def _poor_mans_seek(self, offset):
- """Seek by calling the wrapped file objects read() method.
- This is used for file like objects that do not have native
- seek support. The wrapped objects read() method is called
- to manually seek to the desired position.
- offset -- read this number of bytes from the wrapped
- file object.
- raise RangeError if we encounter EOF before reaching the
- specified offset.
- """
- pos = 0
- bufsize = 1024
- while pos < offset:
- if (pos + bufsize) > offset:
- bufsize = offset - pos
- buf = self.fo.read(bufsize)
- if len(buf) != bufsize:
- raise RangeError('Requested Range Not Satisfiable')
- pos += bufsize
-
-class FileRangeHandler(urllib2.FileHandler):
- """FileHandler subclass that adds Range support.
- This class handles Range headers exactly like an HTTP
- server would.
- """
- def open_local_file(self, req):
- import mimetypes
- import email
- host = req.get_host()
- file = req.get_selector()
- localfile = urllib.url2pathname(file)
- stats = os.stat(localfile)
- size = stats[stat.ST_SIZE]
- modified = email.Utils.formatdate(stats[stat.ST_MTIME])
- mtype = mimetypes.guess_type(file)[0]
- if host:
- host, port = urllib.splitport(host)
- if port or socket.gethostbyname(host) not in self.get_names():
- raise urllib2.URLError('file not on local host')
- fo = open(localfile,'rb')
- brange = req.headers.get('Range', None)
- brange = range_header_to_tuple(brange)
- assert brange != ()
- if brange:
- (fb, lb) = brange
- if lb == '':
- lb = size
- if fb < 0 or fb > size or lb > size:
- raise RangeError('Requested Range Not Satisfiable')
- size = (lb - fb)
- fo = RangeableFileObject(fo, (fb, lb))
- headers = email.message_from_string(
- 'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' %
- (mtype or 'text/plain', size, modified))
- return urllib.addinfourl(fo, headers, 'file:'+file)
-
-
-# FTP Range Support
-# Unfortunately, a large amount of base FTP code had to be copied
-# from urllib and urllib2 in order to insert the FTP REST command.
-# Code modifications for range support have been commented as
-# follows:
-# -- range support modifications start/end here
-
-from urllib import splitport, splituser, splitpasswd, splitattr, \
- unquote, addclosehook, addinfourl
-import ftplib
-import socket
-import sys
-import mimetypes
-import email
-
-class FTPRangeHandler(urllib2.FTPHandler):
- def ftp_open(self, req):
- host = req.get_host()
- if not host:
- raise IOError('ftp error', 'no host given')
- host, port = splitport(host)
- if port is None:
- port = ftplib.FTP_PORT
-
- # username/password handling
- user, host = splituser(host)
- if user:
- user, passwd = splitpasswd(user)
- else:
- passwd = None
- host = unquote(host)
- user = unquote(user or '')
- passwd = unquote(passwd or '')
-
- try:
- host = socket.gethostbyname(host)
- except socket.error, msg:
- raise urllib2.URLError(msg)
- path, attrs = splitattr(req.get_selector())
- dirs = path.split('/')
- dirs = map(unquote, dirs)
- dirs, file = dirs[:-1], dirs[-1]
- if dirs and not dirs[0]:
- dirs = dirs[1:]
- try:
- fw = self.connect_ftp(user, passwd, host, port, dirs)
- type = file and 'I' or 'D'
- for attr in attrs:
- attr, value = splitattr(attr)
- if attr.lower() == 'type' and \
- value in ('a', 'A', 'i', 'I', 'd', 'D'):
- type = value.upper()
-
- # -- range support modifications start here
- rest = None
- range_tup = range_header_to_tuple(req.headers.get('Range', None))
- assert range_tup != ()
- if range_tup:
- (fb, lb) = range_tup
- if fb > 0:
- rest = fb
- # -- range support modifications end here
-
- fp, retrlen = fw.retrfile(file, type, rest)
-
- # -- range support modifications start here
- if range_tup:
- (fb, lb) = range_tup
- if lb == '':
- if retrlen is None or retrlen == 0:
- raise RangeError('Requested Range Not Satisfiable due to unobtainable file length.')
- lb = retrlen
- retrlen = lb - fb
- if retrlen < 0:
- # beginning of range is larger than file
- raise RangeError('Requested Range Not Satisfiable')
- else:
- retrlen = lb - fb
- fp = RangeableFileObject(fp, (0, retrlen))
- # -- range support modifications end here
-
- headers = ""
- mtype = mimetypes.guess_type(req.get_full_url())[0]
- if mtype:
- headers += "Content-Type: %s\n" % mtype
- if retrlen is not None and retrlen >= 0:
- headers += "Content-Length: %d\n" % retrlen
- headers = email.message_from_string(headers)
- return addinfourl(fp, headers, req.get_full_url())
- except ftplib.all_errors, msg:
- raise IOError('ftp error', msg), sys.exc_info()[2]
-
- def connect_ftp(self, user, passwd, host, port, dirs):
- fw = ftpwrapper(user, passwd, host, port, dirs)
- return fw
-
-class ftpwrapper(urllib.ftpwrapper):
- # range support note:
- # this ftpwrapper code is copied directly from
- # urllib. The only enhancement is to add the rest
- # argument and pass it on to ftp.ntransfercmd
- def retrfile(self, file, type, rest=None):
- self.endtransfer()
- if type in ('d', 'D'):
- cmd = 'TYPE A'
- isdir = 1
- else:
- cmd = 'TYPE ' + type
- isdir = 0
- try:
- self.ftp.voidcmd(cmd)
- except ftplib.all_errors:
- self.init()
- self.ftp.voidcmd(cmd)
- conn = None
- if file and not isdir:
- # Use nlst to see if the file exists at all
- try:
- self.ftp.nlst(file)
- except ftplib.error_perm, reason:
- raise IOError('ftp error', reason), sys.exc_info()[2]
- # Restore the transfer mode!
- self.ftp.voidcmd(cmd)
- # Try to retrieve as a file
- try:
- cmd = 'RETR ' + file
- conn = self.ftp.ntransfercmd(cmd, rest)
- except ftplib.error_perm, reason:
- if str(reason).startswith('501'):
- # workaround for REST not supported error
- fp, retrlen = self.retrfile(file, type)
- fp = RangeableFileObject(fp, (rest,''))
- return (fp, retrlen)
- elif not str(reason).startswith('550'):
- raise IOError('ftp error', reason), sys.exc_info()[2]
- if not conn:
- # Set transfer mode to ASCII!
- self.ftp.voidcmd('TYPE A')
- # Try a directory listing
- if file:
- cmd = 'LIST ' + file
- else:
- cmd = 'LIST'
- conn = self.ftp.ntransfercmd(cmd)
- self.busy = 1
- # Pass back both a suitably decorated object and a retrieval length
- return (addclosehook(conn[0].makefile('rb'),
- self.endtransfer), conn[1])
-
-
-####################################################################
-# Range Tuple Functions
-# XXX: These range tuple functions might go better in a class.
-
-_rangere = None
-def range_header_to_tuple(range_header):
- """Get a (firstbyte,lastbyte) tuple from a Range header value.
-
- Range headers have the form "bytes=<firstbyte>-<lastbyte>". This
- function pulls the firstbyte and lastbyte values and returns
- a (firstbyte,lastbyte) tuple. If lastbyte is not specified in
- the header value, it is returned as an empty string in the
- tuple.
-
- Return None if range_header is None
- Return () if range_header does not conform to the range spec
- pattern.
-
- """
- global _rangere
- if range_header is None:
- return None
- if _rangere is None:
- import re
- _rangere = re.compile(r'^bytes=(\d{1,})-(\d*)')
- match = _rangere.match(range_header)
- if match:
- tup = range_tuple_normalize(match.group(1, 2))
- if tup and tup[1]:
- tup = (tup[0], tup[1]+1)
- return tup
- return ()
-
-def range_tuple_to_header(range_tup):
- """Convert a range tuple to a Range header value.
- Return a string of the form "bytes=<firstbyte>-<lastbyte>" or None
- if no range is needed.
- """
- if range_tup is None:
- return None
- range_tup = range_tuple_normalize(range_tup)
- if range_tup:
- if range_tup[1]:
- range_tup = (range_tup[0], range_tup[1] - 1)
- return 'bytes=%s-%s' % range_tup
-
-def range_tuple_normalize(range_tup):
- """Normalize a (first_byte,last_byte) range tuple.
- Return a tuple whose first element is guaranteed to be an int
- and whose second element will be '' (meaning: the last byte) or
- an int. Finally, return None if the normalized tuple == (0,'')
- as that is equivelant to retrieving the entire file.
- """
- if range_tup is None:
- return None
- # handle first byte
- fb = range_tup[0]
- if fb in (None, ''):
- fb = 0
- else:
- fb = int(fb)
- # handle last byte
- try:
- lb = range_tup[1]
- except IndexError:
- lb = ''
- else:
- if lb is None:
- lb = ''
- elif lb != '':
- lb = int(lb)
- # check if range is over the entire file
- if (fb, lb) == (0, ''):
- return None
- # check that the range is valid
- if lb < fb:
- raise RangeError('Invalid byte range: %s-%s' % (fb, lb))
- return (fb, lb)
diff --git a/sys/src/cmd/hg/mercurial/changegroup.py b/sys/src/cmd/hg/mercurial/changegroup.py
deleted file mode 100644
index a4ada4eb6..000000000
--- a/sys/src/cmd/hg/mercurial/changegroup.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# changegroup.py - Mercurial changegroup manipulation functions
-#
-# Copyright 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import util
-import struct, os, bz2, zlib, tempfile
-
-def getchunk(source):
- """get a chunk from a changegroup"""
- d = source.read(4)
- if not d:
- return ""
- l = struct.unpack(">l", d)[0]
- if l <= 4:
- return ""
- d = source.read(l - 4)
- if len(d) < l - 4:
- raise util.Abort(_("premature EOF reading chunk"
- " (got %d bytes, expected %d)")
- % (len(d), l - 4))
- return d
-
-def chunkiter(source):
- """iterate through the chunks in source"""
- while 1:
- c = getchunk(source)
- if not c:
- break
- yield c
-
-def chunkheader(length):
- """build a changegroup chunk header"""
- return struct.pack(">l", length + 4)
-
-def closechunk():
- return struct.pack(">l", 0)
-
-class nocompress(object):
- def compress(self, x):
- return x
- def flush(self):
- return ""
-
-bundletypes = {
- "": ("", nocompress),
- "HG10UN": ("HG10UN", nocompress),
- "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
- "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
-}
-
-# hgweb uses this list to communicate its preferred type
-bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
-
-def writebundle(cg, filename, bundletype):
- """Write a bundle file and return its filename.
-
- Existing files will not be overwritten.
- If no filename is specified, a temporary file is created.
- bz2 compression can be turned off.
- The bundle file will be deleted in case of errors.
- """
-
- fh = None
- cleanup = None
- try:
- if filename:
- fh = open(filename, "wb")
- else:
- fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
- fh = os.fdopen(fd, "wb")
- cleanup = filename
-
- header, compressor = bundletypes[bundletype]
- fh.write(header)
- z = compressor()
-
- # parse the changegroup data, otherwise we will block
- # in case of sshrepo because we don't know the end of the stream
-
- # an empty chunkiter is the end of the changegroup
- # a changegroup has at least 2 chunkiters (changelog and manifest).
- # after that, an empty chunkiter is the end of the changegroup
- empty = False
- count = 0
- while not empty or count <= 2:
- empty = True
- count += 1
- for chunk in chunkiter(cg):
- empty = False
- fh.write(z.compress(chunkheader(len(chunk))))
- pos = 0
- while pos < len(chunk):
- next = pos + 2**20
- fh.write(z.compress(chunk[pos:next]))
- pos = next
- fh.write(z.compress(closechunk()))
- fh.write(z.flush())
- cleanup = None
- return filename
- finally:
- if fh is not None:
- fh.close()
- if cleanup is not None:
- os.unlink(cleanup)
-
-def unbundle(header, fh):
- if header == 'HG10UN':
- return fh
- elif not header.startswith('HG'):
- # old client with uncompressed bundle
- def generator(f):
- yield header
- for chunk in f:
- yield chunk
- elif header == 'HG10GZ':
- def generator(f):
- zd = zlib.decompressobj()
- for chunk in f:
- yield zd.decompress(chunk)
- elif header == 'HG10BZ':
- def generator(f):
- zd = bz2.BZ2Decompressor()
- zd.decompress("BZ")
- for chunk in util.filechunkiter(f, 4096):
- yield zd.decompress(chunk)
- return util.chunkbuffer(generator(fh))
-
-def readbundle(fh, fname):
- header = fh.read(6)
- if not header.startswith('HG'):
- raise util.Abort(_('%s: not a Mercurial bundle file') % fname)
- if not header.startswith('HG10'):
- raise util.Abort(_('%s: unknown bundle version') % fname)
- elif header not in bundletypes:
- raise util.Abort(_('%s: unknown bundle compression type') % fname)
- return unbundle(header, fh)
diff --git a/sys/src/cmd/hg/mercurial/changelog.py b/sys/src/cmd/hg/mercurial/changelog.py
deleted file mode 100644
index f99479022..000000000
--- a/sys/src/cmd/hg/mercurial/changelog.py
+++ /dev/null
@@ -1,228 +0,0 @@
-# changelog.py - changelog class for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import bin, hex, nullid
-from i18n import _
-import util, error, revlog, encoding
-
-def _string_escape(text):
- """
- >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
- >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
- >>> s
- 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
- >>> res = _string_escape(s)
- >>> s == res.decode('string_escape')
- True
- """
- # subset of the string_escape codec
- text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
- return text.replace('\0', '\\0')
-
-def decodeextra(text):
- extra = {}
- for l in text.split('\0'):
- if l:
- k, v = l.decode('string_escape').split(':', 1)
- extra[k] = v
- return extra
-
-def encodeextra(d):
- # keys must be sorted to produce a deterministic changelog entry
- items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
- return "\0".join(items)
-
-class appender(object):
- '''the changelog index must be updated last on disk, so we use this class
- to delay writes to it'''
- def __init__(self, fp, buf):
- self.data = buf
- self.fp = fp
- self.offset = fp.tell()
- self.size = util.fstat(fp).st_size
-
- def end(self):
- return self.size + len("".join(self.data))
- def tell(self):
- return self.offset
- def flush(self):
- pass
- def close(self):
- self.fp.close()
-
- def seek(self, offset, whence=0):
- '''virtual file offset spans real file and data'''
- if whence == 0:
- self.offset = offset
- elif whence == 1:
- self.offset += offset
- elif whence == 2:
- self.offset = self.end() + offset
- if self.offset < self.size:
- self.fp.seek(self.offset)
-
- def read(self, count=-1):
- '''only trick here is reads that span real file and data'''
- ret = ""
- if self.offset < self.size:
- s = self.fp.read(count)
- ret = s
- self.offset += len(s)
- if count > 0:
- count -= len(s)
- if count != 0:
- doff = self.offset - self.size
- self.data.insert(0, "".join(self.data))
- del self.data[1:]
- s = self.data[0][doff:doff+count]
- self.offset += len(s)
- ret += s
- return ret
-
- def write(self, s):
- self.data.append(str(s))
- self.offset += len(s)
-
-def delayopener(opener, target, divert, buf):
- def o(name, mode='r'):
- if name != target:
- return opener(name, mode)
- if divert:
- return opener(name + ".a", mode.replace('a', 'w'))
- # otherwise, divert to memory
- return appender(opener(name, mode), buf)
- return o
-
-class changelog(revlog.revlog):
- def __init__(self, opener):
- revlog.revlog.__init__(self, opener, "00changelog.i")
- self._realopener = opener
- self._delayed = False
- self._divert = False
-
- def delayupdate(self):
- "delay visibility of index updates to other readers"
- self._delayed = True
- self._divert = (len(self) == 0)
- self._delaybuf = []
- self.opener = delayopener(self._realopener, self.indexfile,
- self._divert, self._delaybuf)
-
- def finalize(self, tr):
- "finalize index updates"
- self._delayed = False
- self.opener = self._realopener
- # move redirected index data back into place
- if self._divert:
- n = self.opener(self.indexfile + ".a").name
- util.rename(n, n[:-2])
- elif self._delaybuf:
- fp = self.opener(self.indexfile, 'a')
- fp.write("".join(self._delaybuf))
- fp.close()
- self._delaybuf = []
- # split when we're done
- self.checkinlinesize(tr)
-
- def readpending(self, file):
- r = revlog.revlog(self.opener, file)
- self.index = r.index
- self.nodemap = r.nodemap
- self._chunkcache = r._chunkcache
-
- def writepending(self):
- "create a file containing the unfinalized state for pretxnchangegroup"
- if self._delaybuf:
- # make a temporary copy of the index
- fp1 = self._realopener(self.indexfile)
- fp2 = self._realopener(self.indexfile + ".a", "w")
- fp2.write(fp1.read())
- # add pending data
- fp2.write("".join(self._delaybuf))
- fp2.close()
- # switch modes so finalize can simply rename
- self._delaybuf = []
- self._divert = True
-
- if self._divert:
- return True
-
- return False
-
- def checkinlinesize(self, tr, fp=None):
- if not self._delayed:
- revlog.revlog.checkinlinesize(self, tr, fp)
-
- def read(self, node):
- """
- format used:
- nodeid\n : manifest node in ascii
- user\n : user, no \n or \r allowed
- time tz extra\n : date (time is int or float, timezone is int)
- : extra is metadatas, encoded and separated by '\0'
- : older versions ignore it
- files\n\n : files modified by the cset, no \n or \r allowed
- (.*) : comment (free text, ideally utf-8)
-
- changelog v0 doesn't use extra
- """
- text = self.revision(node)
- if not text:
- return (nullid, "", (0, 0), [], "", {'branch': 'default'})
- last = text.index("\n\n")
- desc = encoding.tolocal(text[last + 2:])
- l = text[:last].split('\n')
- manifest = bin(l[0])
- user = encoding.tolocal(l[1])
-
- extra_data = l[2].split(' ', 2)
- if len(extra_data) != 3:
- time = float(extra_data.pop(0))
- try:
- # various tools did silly things with the time zone field.
- timezone = int(extra_data[0])
- except:
- timezone = 0
- extra = {}
- else:
- time, timezone, extra = extra_data
- time, timezone = float(time), int(timezone)
- extra = decodeextra(extra)
- if not extra.get('branch'):
- extra['branch'] = 'default'
- files = l[3:]
- return (manifest, user, (time, timezone), files, desc, extra)
-
- def add(self, manifest, files, desc, transaction, p1, p2,
- user, date=None, extra={}):
- user = user.strip()
- # An empty username or a username with a "\n" will make the
- # revision text contain two "\n\n" sequences -> corrupt
- # repository since read cannot unpack the revision.
- if not user:
- raise error.RevlogError(_("empty username"))
- if "\n" in user:
- raise error.RevlogError(_("username %s contains a newline")
- % repr(user))
-
- # strip trailing whitespace and leading and trailing empty lines
- desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
-
- user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
-
- if date:
- parseddate = "%d %d" % util.parsedate(date)
- else:
- parseddate = "%d %d" % util.makedate()
- if extra and extra.get("branch") in ("default", ""):
- del extra["branch"]
- if extra:
- extra = encodeextra(extra)
- parseddate = "%s %s" % (parseddate, extra)
- l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
- text = "\n".join(l)
- return self.addrevision(text, transaction, len(self), p1, p2)
diff --git a/sys/src/cmd/hg/mercurial/cmdutil.py b/sys/src/cmd/hg/mercurial/cmdutil.py
deleted file mode 100644
index 1c58d6bdc..000000000
--- a/sys/src/cmd/hg/mercurial/cmdutil.py
+++ /dev/null
@@ -1,1254 +0,0 @@
-# cmdutil.py - help for command processing in mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import hex, nullid, nullrev, short
-from i18n import _
-import os, sys, errno, re, glob
-import mdiff, bdiff, util, templater, patch, error, encoding
-import match as _match
-
-revrangesep = ':'
-
-def findpossible(cmd, table, strict=False):
- """
- Return cmd -> (aliases, command table entry)
- for each matching command.
- Return debug commands (or their aliases) only if no normal command matches.
- """
- choice = {}
- debugchoice = {}
- for e in table.keys():
- aliases = e.lstrip("^").split("|")
- found = None
- if cmd in aliases:
- found = cmd
- elif not strict:
- for a in aliases:
- if a.startswith(cmd):
- found = a
- break
- if found is not None:
- if aliases[0].startswith("debug") or found.startswith("debug"):
- debugchoice[found] = (aliases, table[e])
- else:
- choice[found] = (aliases, table[e])
-
- if not choice and debugchoice:
- choice = debugchoice
-
- return choice
-
-def findcmd(cmd, table, strict=True):
- """Return (aliases, command table entry) for command string."""
- choice = findpossible(cmd, table, strict)
-
- if cmd in choice:
- return choice[cmd]
-
- if len(choice) > 1:
- clist = choice.keys()
- clist.sort()
- raise error.AmbiguousCommand(cmd, clist)
-
- if choice:
- return choice.values()[0]
-
- raise error.UnknownCommand(cmd)
-
-def bail_if_changed(repo):
- if repo.dirstate.parents()[1] != nullid:
- raise util.Abort(_('outstanding uncommitted merge'))
- modified, added, removed, deleted = repo.status()[:4]
- if modified or added or removed or deleted:
- raise util.Abort(_("outstanding uncommitted changes"))
-
-def logmessage(opts):
- """ get the log message according to -m and -l option """
- message = opts.get('message')
- logfile = opts.get('logfile')
-
- if message and logfile:
- raise util.Abort(_('options --message and --logfile are mutually '
- 'exclusive'))
- if not message and logfile:
- try:
- if logfile == '-':
- message = sys.stdin.read()
- else:
- message = open(logfile).read()
- except IOError, inst:
- raise util.Abort(_("can't read commit message '%s': %s") %
- (logfile, inst.strerror))
- return message
-
-def loglimit(opts):
- """get the log limit according to option -l/--limit"""
- limit = opts.get('limit')
- if limit:
- try:
- limit = int(limit)
- except ValueError:
- raise util.Abort(_('limit must be a positive integer'))
- if limit <= 0: raise util.Abort(_('limit must be positive'))
- else:
- limit = sys.maxint
- return limit
-
-def remoteui(src, opts):
- 'build a remote ui from ui or repo and opts'
- if hasattr(src, 'baseui'): # looks like a repository
- dst = src.baseui.copy() # drop repo-specific config
- src = src.ui # copy target options from repo
- else: # assume it's a global ui object
- dst = src.copy() # keep all global options
-
- # copy ssh-specific options
- for o in 'ssh', 'remotecmd':
- v = opts.get(o) or src.config('ui', o)
- if v:
- dst.setconfig("ui", o, v)
- # copy bundle-specific options
- r = src.config('bundle', 'mainreporoot')
- if r:
- dst.setconfig('bundle', 'mainreporoot', r)
-
- return dst
-
-def revpair(repo, revs):
- '''return pair of nodes, given list of revisions. second item can
- be None, meaning use working dir.'''
-
- def revfix(repo, val, defval):
- if not val and val != 0 and defval is not None:
- val = defval
- return repo.lookup(val)
-
- if not revs:
- return repo.dirstate.parents()[0], None
- end = None
- if len(revs) == 1:
- if revrangesep in revs[0]:
- start, end = revs[0].split(revrangesep, 1)
- start = revfix(repo, start, 0)
- end = revfix(repo, end, len(repo) - 1)
- else:
- start = revfix(repo, revs[0], None)
- elif len(revs) == 2:
- if revrangesep in revs[0] or revrangesep in revs[1]:
- raise util.Abort(_('too many revisions specified'))
- start = revfix(repo, revs[0], None)
- end = revfix(repo, revs[1], None)
- else:
- raise util.Abort(_('too many revisions specified'))
- return start, end
-
-def revrange(repo, revs):
- """Yield revision as strings from a list of revision specifications."""
-
- def revfix(repo, val, defval):
- if not val and val != 0 and defval is not None:
- return defval
- return repo.changelog.rev(repo.lookup(val))
-
- seen, l = set(), []
- for spec in revs:
- if revrangesep in spec:
- start, end = spec.split(revrangesep, 1)
- start = revfix(repo, start, 0)
- end = revfix(repo, end, len(repo) - 1)
- step = start > end and -1 or 1
- for rev in xrange(start, end+step, step):
- if rev in seen:
- continue
- seen.add(rev)
- l.append(rev)
- else:
- rev = revfix(repo, spec, None)
- if rev in seen:
- continue
- seen.add(rev)
- l.append(rev)
-
- return l
-
-def make_filename(repo, pat, node,
- total=None, seqno=None, revwidth=None, pathname=None):
- node_expander = {
- 'H': lambda: hex(node),
- 'R': lambda: str(repo.changelog.rev(node)),
- 'h': lambda: short(node),
- }
- expander = {
- '%': lambda: '%',
- 'b': lambda: os.path.basename(repo.root),
- }
-
- try:
- if node:
- expander.update(node_expander)
- if node:
- expander['r'] = (lambda:
- str(repo.changelog.rev(node)).zfill(revwidth or 0))
- if total is not None:
- expander['N'] = lambda: str(total)
- if seqno is not None:
- expander['n'] = lambda: str(seqno)
- if total is not None and seqno is not None:
- expander['n'] = lambda: str(seqno).zfill(len(str(total)))
- if pathname is not None:
- expander['s'] = lambda: os.path.basename(pathname)
- expander['d'] = lambda: os.path.dirname(pathname) or '.'
- expander['p'] = lambda: pathname
-
- newname = []
- patlen = len(pat)
- i = 0
- while i < patlen:
- c = pat[i]
- if c == '%':
- i += 1
- c = pat[i]
- c = expander[c]()
- newname.append(c)
- i += 1
- return ''.join(newname)
- except KeyError, inst:
- raise util.Abort(_("invalid format spec '%%%s' in output filename") %
- inst.args[0])
-
-def make_file(repo, pat, node=None,
- total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
-
- writable = 'w' in mode or 'a' in mode
-
- if not pat or pat == '-':
- return writable and sys.stdout or sys.stdin
- if hasattr(pat, 'write') and writable:
- return pat
- if hasattr(pat, 'read') and 'r' in mode:
- return pat
- return open(make_filename(repo, pat, node, total, seqno, revwidth,
- pathname),
- mode)
-
-def expandpats(pats):
- if not util.expandglobs:
- return list(pats)
- ret = []
- for p in pats:
- kind, name = _match._patsplit(p, None)
- if kind is None:
- try:
- globbed = glob.glob(name)
- except re.error:
- globbed = [name]
- if globbed:
- ret.extend(globbed)
- continue
- ret.append(p)
- return ret
-
-def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
- if not globbed and default == 'relpath':
- pats = expandpats(pats or [])
- m = _match.match(repo.root, repo.getcwd(), pats,
- opts.get('include'), opts.get('exclude'), default)
- def badfn(f, msg):
- repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
- m.bad = badfn
- return m
-
-def matchall(repo):
- return _match.always(repo.root, repo.getcwd())
-
-def matchfiles(repo, files):
- return _match.exact(repo.root, repo.getcwd(), files)
-
-def findrenames(repo, added, removed, threshold):
- '''find renamed files -- yields (before, after, score) tuples'''
- ctx = repo['.']
- for a in added:
- aa = repo.wread(a)
- bestname, bestscore = None, threshold
- for r in removed:
- if r not in ctx:
- continue
- rr = ctx.filectx(r).data()
-
- # bdiff.blocks() returns blocks of matching lines
- # count the number of bytes in each
- equal = 0
- alines = mdiff.splitnewlines(aa)
- matches = bdiff.blocks(aa, rr)
- for x1,x2,y1,y2 in matches:
- for line in alines[x1:x2]:
- equal += len(line)
-
- lengths = len(aa) + len(rr)
- if lengths:
- myscore = equal*2.0 / lengths
- if myscore >= bestscore:
- bestname, bestscore = r, myscore
- if bestname:
- yield bestname, a, bestscore
-
-def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
- if dry_run is None:
- dry_run = opts.get('dry_run')
- if similarity is None:
- similarity = float(opts.get('similarity') or 0)
- # we'd use status here, except handling of symlinks and ignore is tricky
- added, unknown, deleted, removed = [], [], [], []
- audit_path = util.path_auditor(repo.root)
- m = match(repo, pats, opts)
- for abs in repo.walk(m):
- target = repo.wjoin(abs)
- good = True
- try:
- audit_path(abs)
- except:
- good = False
- rel = m.rel(abs)
- exact = m.exact(abs)
- if good and abs not in repo.dirstate:
- unknown.append(abs)
- if repo.ui.verbose or not exact:
- repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
- elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
- or (os.path.isdir(target) and not os.path.islink(target))):
- deleted.append(abs)
- if repo.ui.verbose or not exact:
- repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
- # for finding renames
- elif repo.dirstate[abs] == 'r':
- removed.append(abs)
- elif repo.dirstate[abs] == 'a':
- added.append(abs)
- if not dry_run:
- repo.remove(deleted)
- repo.add(unknown)
- if similarity > 0:
- for old, new, score in findrenames(repo, added + unknown,
- removed + deleted, similarity):
- if repo.ui.verbose or not m.exact(old) or not m.exact(new):
- repo.ui.status(_('recording removal of %s as rename to %s '
- '(%d%% similar)\n') %
- (m.rel(old), m.rel(new), score * 100))
- if not dry_run:
- repo.copy(old, new)
-
-def copy(ui, repo, pats, opts, rename=False):
- # called with the repo lock held
- #
- # hgsep => pathname that uses "/" to separate directories
- # ossep => pathname that uses os.sep to separate directories
- cwd = repo.getcwd()
- targets = {}
- after = opts.get("after")
- dryrun = opts.get("dry_run")
-
- def walkpat(pat):
- srcs = []
- m = match(repo, [pat], opts, globbed=True)
- for abs in repo.walk(m):
- state = repo.dirstate[abs]
- rel = m.rel(abs)
- exact = m.exact(abs)
- if state in '?r':
- if exact and state == '?':
- ui.warn(_('%s: not copying - file is not managed\n') % rel)
- if exact and state == 'r':
- ui.warn(_('%s: not copying - file has been marked for'
- ' remove\n') % rel)
- continue
- # abs: hgsep
- # rel: ossep
- srcs.append((abs, rel, exact))
- return srcs
-
- # abssrc: hgsep
- # relsrc: ossep
- # otarget: ossep
- def copyfile(abssrc, relsrc, otarget, exact):
- abstarget = util.canonpath(repo.root, cwd, otarget)
- reltarget = repo.pathto(abstarget, cwd)
- target = repo.wjoin(abstarget)
- src = repo.wjoin(abssrc)
- state = repo.dirstate[abstarget]
-
- # check for collisions
- prevsrc = targets.get(abstarget)
- if prevsrc is not None:
- ui.warn(_('%s: not overwriting - %s collides with %s\n') %
- (reltarget, repo.pathto(abssrc, cwd),
- repo.pathto(prevsrc, cwd)))
- return
-
- # check for overwrites
- exists = os.path.exists(target)
- if not after and exists or after and state in 'mn':
- if not opts['force']:
- ui.warn(_('%s: not overwriting - file exists\n') %
- reltarget)
- return
-
- if after:
- if not exists:
- return
- elif not dryrun:
- try:
- if exists:
- os.unlink(target)
- targetdir = os.path.dirname(target) or '.'
- if not os.path.isdir(targetdir):
- os.makedirs(targetdir)
- util.copyfile(src, target)
- except IOError, inst:
- if inst.errno == errno.ENOENT:
- ui.warn(_('%s: deleted in working copy\n') % relsrc)
- else:
- ui.warn(_('%s: cannot copy - %s\n') %
- (relsrc, inst.strerror))
- return True # report a failure
-
- if ui.verbose or not exact:
- if rename:
- ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
- else:
- ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
-
- targets[abstarget] = abssrc
-
- # fix up dirstate
- origsrc = repo.dirstate.copied(abssrc) or abssrc
- if abstarget == origsrc: # copying back a copy?
- if state not in 'mn' and not dryrun:
- repo.dirstate.normallookup(abstarget)
- else:
- if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
- if not ui.quiet:
- ui.warn(_("%s has not been committed yet, so no copy "
- "data will be stored for %s.\n")
- % (repo.pathto(origsrc, cwd), reltarget))
- if repo.dirstate[abstarget] in '?r' and not dryrun:
- repo.add([abstarget])
- elif not dryrun:
- repo.copy(origsrc, abstarget)
-
- if rename and not dryrun:
- repo.remove([abssrc], not after)
-
- # pat: ossep
- # dest ossep
- # srcs: list of (hgsep, hgsep, ossep, bool)
- # return: function that takes hgsep and returns ossep
- def targetpathfn(pat, dest, srcs):
- if os.path.isdir(pat):
- abspfx = util.canonpath(repo.root, cwd, pat)
- abspfx = util.localpath(abspfx)
- if destdirexists:
- striplen = len(os.path.split(abspfx)[0])
- else:
- striplen = len(abspfx)
- if striplen:
- striplen += len(os.sep)
- res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
- elif destdirexists:
- res = lambda p: os.path.join(dest,
- os.path.basename(util.localpath(p)))
- else:
- res = lambda p: dest
- return res
-
- # pat: ossep
- # dest ossep
- # srcs: list of (hgsep, hgsep, ossep, bool)
- # return: function that takes hgsep and returns ossep
- def targetpathafterfn(pat, dest, srcs):
- if _match.patkind(pat):
- # a mercurial pattern
- res = lambda p: os.path.join(dest,
- os.path.basename(util.localpath(p)))
- else:
- abspfx = util.canonpath(repo.root, cwd, pat)
- if len(abspfx) < len(srcs[0][0]):
- # A directory. Either the target path contains the last
- # component of the source path or it does not.
- def evalpath(striplen):
- score = 0
- for s in srcs:
- t = os.path.join(dest, util.localpath(s[0])[striplen:])
- if os.path.exists(t):
- score += 1
- return score
-
- abspfx = util.localpath(abspfx)
- striplen = len(abspfx)
- if striplen:
- striplen += len(os.sep)
- if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
- score = evalpath(striplen)
- striplen1 = len(os.path.split(abspfx)[0])
- if striplen1:
- striplen1 += len(os.sep)
- if evalpath(striplen1) > score:
- striplen = striplen1
- res = lambda p: os.path.join(dest,
- util.localpath(p)[striplen:])
- else:
- # a file
- if destdirexists:
- res = lambda p: os.path.join(dest,
- os.path.basename(util.localpath(p)))
- else:
- res = lambda p: dest
- return res
-
-
- pats = expandpats(pats)
- if not pats:
- raise util.Abort(_('no source or destination specified'))
- if len(pats) == 1:
- raise util.Abort(_('no destination specified'))
- dest = pats.pop()
- destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
- if not destdirexists:
- if len(pats) > 1 or _match.patkind(pats[0]):
- raise util.Abort(_('with multiple sources, destination must be an '
- 'existing directory'))
- if util.endswithsep(dest):
- raise util.Abort(_('destination %s is not a directory') % dest)
-
- tfn = targetpathfn
- if after:
- tfn = targetpathafterfn
- copylist = []
- for pat in pats:
- srcs = walkpat(pat)
- if not srcs:
- continue
- copylist.append((tfn(pat, dest, srcs), srcs))
- if not copylist:
- raise util.Abort(_('no files to copy'))
-
- errors = 0
- for targetpath, srcs in copylist:
- for abssrc, relsrc, exact in srcs:
- if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
- errors += 1
-
- if errors:
- ui.warn(_('(consider using --after)\n'))
-
- return errors
-
-def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None):
- '''Run a command as a service.'''
-
- if opts['daemon'] and not opts['daemon_pipefds']:
- rfd, wfd = os.pipe()
- args = sys.argv[:]
- args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
- # Don't pass --cwd to the child process, because we've already
- # changed directory.
- for i in xrange(1,len(args)):
- if args[i].startswith('--cwd='):
- del args[i]
- break
- elif args[i].startswith('--cwd'):
- del args[i:i+2]
- break
- pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
- args[0], args)
- os.close(wfd)
- os.read(rfd, 1)
- if parentfn:
- return parentfn(pid)
- else:
- os._exit(0)
-
- if initfn:
- initfn()
-
- if opts['pid_file']:
- fp = open(opts['pid_file'], 'w')
- fp.write(str(os.getpid()) + '\n')
- fp.close()
-
- if opts['daemon_pipefds']:
- rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
- os.close(rfd)
- try:
- os.setsid()
- except AttributeError:
- pass
- os.write(wfd, 'y')
- os.close(wfd)
- sys.stdout.flush()
- sys.stderr.flush()
-
- nullfd = os.open(util.nulldev, os.O_RDWR)
- logfilefd = nullfd
- if logfile:
- logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
- os.dup2(nullfd, 0)
- os.dup2(logfilefd, 1)
- os.dup2(logfilefd, 2)
- if nullfd not in (0, 1, 2):
- os.close(nullfd)
- if logfile and logfilefd not in (0, 1, 2):
- os.close(logfilefd)
-
- if runfn:
- return runfn()
-
-class changeset_printer(object):
- '''show changeset information when templating not requested.'''
-
- def __init__(self, ui, repo, patch, diffopts, buffered):
- self.ui = ui
- self.repo = repo
- self.buffered = buffered
- self.patch = patch
- self.diffopts = diffopts
- self.header = {}
- self.hunk = {}
- self.lastheader = None
-
- def flush(self, rev):
- if rev in self.header:
- h = self.header[rev]
- if h != self.lastheader:
- self.lastheader = h
- self.ui.write(h)
- del self.header[rev]
- if rev in self.hunk:
- self.ui.write(self.hunk[rev])
- del self.hunk[rev]
- return 1
- return 0
-
- def show(self, ctx, copies=(), **props):
- if self.buffered:
- self.ui.pushbuffer()
- self._show(ctx, copies, props)
- self.hunk[ctx.rev()] = self.ui.popbuffer()
- else:
- self._show(ctx, copies, props)
-
- def _show(self, ctx, copies, props):
- '''show a single changeset or file revision'''
- changenode = ctx.node()
- rev = ctx.rev()
-
- if self.ui.quiet:
- self.ui.write("%d:%s\n" % (rev, short(changenode)))
- return
-
- log = self.repo.changelog
- changes = log.read(changenode)
- date = util.datestr(changes[2])
- extra = changes[5]
- branch = extra.get("branch")
-
- hexfunc = self.ui.debugflag and hex or short
-
- parents = [(p, hexfunc(log.node(p)))
- for p in self._meaningful_parentrevs(log, rev)]
-
- self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
-
- # don't show the default branch name
- if branch != 'default':
- branch = encoding.tolocal(branch)
- self.ui.write(_("branch: %s\n") % branch)
- for tag in self.repo.nodetags(changenode):
- self.ui.write(_("tag: %s\n") % tag)
- for parent in parents:
- self.ui.write(_("parent: %d:%s\n") % parent)
-
- if self.ui.debugflag:
- self.ui.write(_("manifest: %d:%s\n") %
- (self.repo.manifest.rev(changes[0]), hex(changes[0])))
- self.ui.write(_("user: %s\n") % changes[1])
- self.ui.write(_("date: %s\n") % date)
-
- if self.ui.debugflag:
- files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
- for key, value in zip([_("files:"), _("files+:"), _("files-:")],
- files):
- if value:
- self.ui.write("%-12s %s\n" % (key, " ".join(value)))
- elif changes[3] and self.ui.verbose:
- self.ui.write(_("files: %s\n") % " ".join(changes[3]))
- if copies and self.ui.verbose:
- copies = ['%s (%s)' % c for c in copies]
- self.ui.write(_("copies: %s\n") % ' '.join(copies))
-
- if extra and self.ui.debugflag:
- for key, value in sorted(extra.items()):
- self.ui.write(_("extra: %s=%s\n")
- % (key, value.encode('string_escape')))
-
- description = changes[4].strip()
- if description:
- if self.ui.verbose:
- self.ui.write(_("description:\n"))
- self.ui.write(description)
- self.ui.write("\n\n")
- else:
- self.ui.write(_("summary: %s\n") %
- description.splitlines()[0])
- self.ui.write("\n")
-
- self.showpatch(changenode)
-
- def showpatch(self, node):
- if self.patch:
- prev = self.repo.changelog.parents(node)[0]
- chunks = patch.diff(self.repo, prev, node, match=self.patch,
- opts=patch.diffopts(self.ui, self.diffopts))
- for chunk in chunks:
- self.ui.write(chunk)
- self.ui.write("\n")
-
- def _meaningful_parentrevs(self, log, rev):
- """Return list of meaningful (or all if debug) parentrevs for rev.
-
- For merges (two non-nullrev revisions) both parents are meaningful.
- Otherwise the first parent revision is considered meaningful if it
- is not the preceding revision.
- """
- parents = log.parentrevs(rev)
- if not self.ui.debugflag and parents[1] == nullrev:
- if parents[0] >= rev - 1:
- parents = []
- else:
- parents = [parents[0]]
- return parents
-
-
-class changeset_templater(changeset_printer):
- '''format changeset information.'''
-
- def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
- changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
- formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
- self.t = templater.templater(mapfile, {'formatnode': formatnode},
- cache={
- 'parent': '{rev}:{node|formatnode} ',
- 'manifest': '{rev}:{node|formatnode}',
- 'filecopy': '{name} ({source})'})
-
- def use_template(self, t):
- '''set template string to use'''
- self.t.cache['changeset'] = t
-
- def _meaningful_parentrevs(self, ctx):
- """Return list of meaningful (or all if debug) parentrevs for rev.
- """
- parents = ctx.parents()
- if len(parents) > 1:
- return parents
- if self.ui.debugflag:
- return [parents[0], self.repo['null']]
- if parents[0].rev() >= ctx.rev() - 1:
- return []
- return parents
-
- def _show(self, ctx, copies, props):
- '''show a single changeset or file revision'''
-
- def showlist(name, values, plural=None, **args):
- '''expand set of values.
- name is name of key in template map.
- values is list of strings or dicts.
- plural is plural of name, if not simply name + 's'.
-
- expansion works like this, given name 'foo'.
-
- if values is empty, expand 'no_foos'.
-
- if 'foo' not in template map, return values as a string,
- joined by space.
-
- expand 'start_foos'.
-
- for each value, expand 'foo'. if 'last_foo' in template
- map, expand it instead of 'foo' for last key.
-
- expand 'end_foos'.
- '''
- if plural: names = plural
- else: names = name + 's'
- if not values:
- noname = 'no_' + names
- if noname in self.t:
- yield self.t(noname, **args)
- return
- if name not in self.t:
- if isinstance(values[0], str):
- yield ' '.join(values)
- else:
- for v in values:
- yield dict(v, **args)
- return
- startname = 'start_' + names
- if startname in self.t:
- yield self.t(startname, **args)
- vargs = args.copy()
- def one(v, tag=name):
- try:
- vargs.update(v)
- except (AttributeError, ValueError):
- try:
- for a, b in v:
- vargs[a] = b
- except ValueError:
- vargs[name] = v
- return self.t(tag, **vargs)
- lastname = 'last_' + name
- if lastname in self.t:
- last = values.pop()
- else:
- last = None
- for v in values:
- yield one(v)
- if last is not None:
- yield one(last, tag=lastname)
- endname = 'end_' + names
- if endname in self.t:
- yield self.t(endname, **args)
-
- def showbranches(**args):
- branch = ctx.branch()
- if branch != 'default':
- branch = encoding.tolocal(branch)
- return showlist('branch', [branch], plural='branches', **args)
-
- def showparents(**args):
- parents = [[('rev', p.rev()), ('node', p.hex())]
- for p in self._meaningful_parentrevs(ctx)]
- return showlist('parent', parents, **args)
-
- def showtags(**args):
- return showlist('tag', ctx.tags(), **args)
-
- def showextras(**args):
- for key, value in sorted(ctx.extra().items()):
- args = args.copy()
- args.update(dict(key=key, value=value))
- yield self.t('extra', **args)
-
- def showcopies(**args):
- c = [{'name': x[0], 'source': x[1]} for x in copies]
- return showlist('file_copy', c, plural='file_copies', **args)
-
- files = []
- def getfiles():
- if not files:
- files[:] = self.repo.status(ctx.parents()[0].node(),
- ctx.node())[:3]
- return files
- def showfiles(**args):
- return showlist('file', ctx.files(), **args)
- def showmods(**args):
- return showlist('file_mod', getfiles()[0], **args)
- def showadds(**args):
- return showlist('file_add', getfiles()[1], **args)
- def showdels(**args):
- return showlist('file_del', getfiles()[2], **args)
- def showmanifest(**args):
- args = args.copy()
- args.update(dict(rev=self.repo.manifest.rev(ctx.changeset()[0]),
- node=hex(ctx.changeset()[0])))
- return self.t('manifest', **args)
-
- def showdiffstat(**args):
- diff = patch.diff(self.repo, ctx.parents()[0].node(), ctx.node())
- files, adds, removes = 0, 0, 0
- for i in patch.diffstatdata(util.iterlines(diff)):
- files += 1
- adds += i[1]
- removes += i[2]
- return '%s: +%s/-%s' % (files, adds, removes)
-
- defprops = {
- 'author': ctx.user(),
- 'branches': showbranches,
- 'date': ctx.date(),
- 'desc': ctx.description().strip(),
- 'file_adds': showadds,
- 'file_dels': showdels,
- 'file_mods': showmods,
- 'files': showfiles,
- 'file_copies': showcopies,
- 'manifest': showmanifest,
- 'node': ctx.hex(),
- 'parents': showparents,
- 'rev': ctx.rev(),
- 'tags': showtags,
- 'extras': showextras,
- 'diffstat': showdiffstat,
- }
- props = props.copy()
- props.update(defprops)
-
- # find correct templates for current mode
-
- tmplmodes = [
- (True, None),
- (self.ui.verbose, 'verbose'),
- (self.ui.quiet, 'quiet'),
- (self.ui.debugflag, 'debug'),
- ]
-
- types = {'header': '', 'changeset': 'changeset'}
- for mode, postfix in tmplmodes:
- for type in types:
- cur = postfix and ('%s_%s' % (type, postfix)) or type
- if mode and cur in self.t:
- types[type] = cur
-
- try:
-
- # write header
- if types['header']:
- h = templater.stringify(self.t(types['header'], **props))
- if self.buffered:
- self.header[ctx.rev()] = h
- else:
- self.ui.write(h)
-
- # write changeset metadata, then patch if requested
- key = types['changeset']
- self.ui.write(templater.stringify(self.t(key, **props)))
- self.showpatch(ctx.node())
-
- except KeyError, inst:
- msg = _("%s: no key named '%s'")
- raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
- except SyntaxError, inst:
- raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
-
-def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
- """show one changeset using template or regular display.
-
- Display format will be the first non-empty hit of:
- 1. option 'template'
- 2. option 'style'
- 3. [ui] setting 'logtemplate'
- 4. [ui] setting 'style'
- If all of these values are either the unset or the empty string,
- regular display via changeset_printer() is done.
- """
- # options
- patch = False
- if opts.get('patch'):
- patch = matchfn or matchall(repo)
-
- tmpl = opts.get('template')
- style = None
- if tmpl:
- tmpl = templater.parsestring(tmpl, quoted=False)
- else:
- style = opts.get('style')
-
- # ui settings
- if not (tmpl or style):
- tmpl = ui.config('ui', 'logtemplate')
- if tmpl:
- tmpl = templater.parsestring(tmpl)
- else:
- style = ui.config('ui', 'style')
-
- if not (tmpl or style):
- return changeset_printer(ui, repo, patch, opts, buffered)
-
- mapfile = None
- if style and not tmpl:
- mapfile = style
- if not os.path.split(mapfile)[0]:
- mapname = (templater.templatepath('map-cmdline.' + mapfile)
- or templater.templatepath(mapfile))
- if mapname: mapfile = mapname
-
- try:
- t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
- except SyntaxError, inst:
- raise util.Abort(inst.args[0])
- if tmpl: t.use_template(tmpl)
- return t
-
-def finddate(ui, repo, date):
- """Find the tipmost changeset that matches the given date spec"""
- df = util.matchdate(date)
- get = util.cachefunc(lambda r: repo[r].changeset())
- changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
- results = {}
- for st, rev, fns in changeiter:
- if st == 'add':
- d = get(rev)[2]
- if df(d[0]):
- results[rev] = d
- elif st == 'iter':
- if rev in results:
- ui.status(_("Found revision %s from %s\n") %
- (rev, util.datestr(results[rev])))
- return str(rev)
-
- raise util.Abort(_("revision matching date not found"))
-
-def walkchangerevs(ui, repo, pats, change, opts):
- '''Iterate over files and the revs in which they changed.
-
- Callers most commonly need to iterate backwards over the history
- in which they are interested. Doing so has awful (quadratic-looking)
- performance, so we use iterators in a "windowed" way.
-
- We walk a window of revisions in the desired order. Within the
- window, we first walk forwards to gather data, then in the desired
- order (usually backwards) to display it.
-
- This function returns an (iterator, matchfn) tuple. The iterator
- yields 3-tuples. They will be of one of the following forms:
-
- "window", incrementing, lastrev: stepping through a window,
- positive if walking forwards through revs, last rev in the
- sequence iterated over - use to reset state for the current window
-
- "add", rev, fns: out-of-order traversal of the given filenames
- fns, which changed during revision rev - use to gather data for
- possible display
-
- "iter", rev, None: in-order traversal of the revs earlier iterated
- over with "add" - use to display data'''
-
- def increasing_windows(start, end, windowsize=8, sizelimit=512):
- if start < end:
- while start < end:
- yield start, min(windowsize, end-start)
- start += windowsize
- if windowsize < sizelimit:
- windowsize *= 2
- else:
- while start > end:
- yield start, min(windowsize, start-end-1)
- start -= windowsize
- if windowsize < sizelimit:
- windowsize *= 2
-
- m = match(repo, pats, opts)
- follow = opts.get('follow') or opts.get('follow_first')
-
- if not len(repo):
- return [], m
-
- if follow:
- defrange = '%s:0' % repo['.'].rev()
- else:
- defrange = '-1:0'
- revs = revrange(repo, opts['rev'] or [defrange])
- wanted = set()
- slowpath = m.anypats() or (m.files() and opts.get('removed'))
- fncache = {}
-
- if not slowpath and not m.files():
- # No files, no patterns. Display all revs.
- wanted = set(revs)
- copies = []
- if not slowpath:
- # Only files, no patterns. Check the history of each file.
- def filerevgen(filelog, node):
- cl_count = len(repo)
- if node is None:
- last = len(filelog) - 1
- else:
- last = filelog.rev(node)
- for i, window in increasing_windows(last, nullrev):
- revs = []
- for j in xrange(i - window, i + 1):
- n = filelog.node(j)
- revs.append((filelog.linkrev(j),
- follow and filelog.renamed(n)))
- for rev in reversed(revs):
- # only yield rev for which we have the changelog, it can
- # happen while doing "hg log" during a pull or commit
- if rev[0] < cl_count:
- yield rev
- def iterfiles():
- for filename in m.files():
- yield filename, None
- for filename_node in copies:
- yield filename_node
- minrev, maxrev = min(revs), max(revs)
- for file_, node in iterfiles():
- filelog = repo.file(file_)
- if not len(filelog):
- if node is None:
- # A zero count may be a directory or deleted file, so
- # try to find matching entries on the slow path.
- if follow:
- raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
- slowpath = True
- break
- else:
- ui.warn(_('%s:%s copy source revision cannot be found!\n')
- % (file_, short(node)))
- continue
- for rev, copied in filerevgen(filelog, node):
- if rev <= maxrev:
- if rev < minrev:
- break
- fncache.setdefault(rev, [])
- fncache[rev].append(file_)
- wanted.add(rev)
- if follow and copied:
- copies.append(copied)
- if slowpath:
- if follow:
- raise util.Abort(_('can only follow copies/renames for explicit '
- 'filenames'))
-
- # The slow path checks files modified in every changeset.
- def changerevgen():
- for i, window in increasing_windows(len(repo) - 1, nullrev):
- for j in xrange(i - window, i + 1):
- yield j, change(j)[3]
-
- for rev, changefiles in changerevgen():
- matches = filter(m, changefiles)
- if matches:
- fncache[rev] = matches
- wanted.add(rev)
-
- class followfilter(object):
- def __init__(self, onlyfirst=False):
- self.startrev = nullrev
- self.roots = []
- self.onlyfirst = onlyfirst
-
- def match(self, rev):
- def realparents(rev):
- if self.onlyfirst:
- return repo.changelog.parentrevs(rev)[0:1]
- else:
- return filter(lambda x: x != nullrev,
- repo.changelog.parentrevs(rev))
-
- if self.startrev == nullrev:
- self.startrev = rev
- return True
-
- if rev > self.startrev:
- # forward: all descendants
- if not self.roots:
- self.roots.append(self.startrev)
- for parent in realparents(rev):
- if parent in self.roots:
- self.roots.append(rev)
- return True
- else:
- # backwards: all parents
- if not self.roots:
- self.roots.extend(realparents(self.startrev))
- if rev in self.roots:
- self.roots.remove(rev)
- self.roots.extend(realparents(rev))
- return True
-
- return False
-
- # it might be worthwhile to do this in the iterator if the rev range
- # is descending and the prune args are all within that range
- for rev in opts.get('prune', ()):
- rev = repo.changelog.rev(repo.lookup(rev))
- ff = followfilter()
- stop = min(revs[0], revs[-1])
- for x in xrange(rev, stop-1, -1):
- if ff.match(x):
- wanted.discard(x)
-
- def iterate():
- if follow and not m.files():
- ff = followfilter(onlyfirst=opts.get('follow_first'))
- def want(rev):
- return ff.match(rev) and rev in wanted
- else:
- def want(rev):
- return rev in wanted
-
- for i, window in increasing_windows(0, len(revs)):
- yield 'window', revs[0] < revs[-1], revs[-1]
- nrevs = [rev for rev in revs[i:i+window] if want(rev)]
- for rev in sorted(nrevs):
- fns = fncache.get(rev)
- if not fns:
- def fns_generator():
- for f in change(rev)[3]:
- if m(f):
- yield f
- fns = fns_generator()
- yield 'add', rev, fns
- for rev in nrevs:
- yield 'iter', rev, None
- return iterate(), m
-
-def commit(ui, repo, commitfunc, pats, opts):
- '''commit the specified files or all outstanding changes'''
- date = opts.get('date')
- if date:
- opts['date'] = util.parsedate(date)
- message = logmessage(opts)
-
- # extract addremove carefully -- this function can be called from a command
- # that doesn't support addremove
- if opts.get('addremove'):
- addremove(repo, pats, opts)
-
- return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
-
-def commiteditor(repo, ctx, subs):
- if ctx.description():
- return ctx.description()
- return commitforceeditor(repo, ctx, subs)
-
-def commitforceeditor(repo, ctx, subs):
- edittext = []
- modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
- if ctx.description():
- edittext.append(ctx.description())
- edittext.append("")
- edittext.append("") # Empty line between message and comments.
- edittext.append(_("HG: Enter commit message."
- " Lines beginning with 'HG:' are removed."))
- edittext.append(_("HG: Leave message empty to abort commit."))
- edittext.append("HG: --")
- edittext.append(_("HG: user: %s") % ctx.user())
- if ctx.p2():
- edittext.append(_("HG: branch merge"))
- if ctx.branch():
- edittext.append(_("HG: branch '%s'")
- % encoding.tolocal(ctx.branch()))
- edittext.extend([_("HG: subrepo %s") % s for s in subs])
- edittext.extend([_("HG: added %s") % f for f in added])
- edittext.extend([_("HG: changed %s") % f for f in modified])
- edittext.extend([_("HG: removed %s") % f for f in removed])
- if not added and not modified and not removed:
- edittext.append(_("HG: no files changed"))
- edittext.append("")
- # run editor in the repository root
- olddir = os.getcwd()
- os.chdir(repo.root)
- text = repo.ui.edit("\n".join(edittext), ctx.user())
- text = re.sub("(?m)^HG:.*\n", "", text)
- os.chdir(olddir)
-
- if not text.strip():
- raise util.Abort(_("empty commit message"))
-
- return text
diff --git a/sys/src/cmd/hg/mercurial/commands.py b/sys/src/cmd/hg/mercurial/commands.py
deleted file mode 100644
index 2dfd3def0..000000000
--- a/sys/src/cmd/hg/mercurial/commands.py
+++ /dev/null
@@ -1,3565 +0,0 @@
-# commands.py - command processing for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import hex, nullid, nullrev, short
-from lock import release
-from i18n import _, gettext
-import os, re, sys, subprocess, difflib, time, tempfile
-import hg, util, revlog, bundlerepo, extensions, copies, context, error
-import patch, help, mdiff, url, encoding
-import archival, changegroup, cmdutil, sshserver, hbisect
-from hgweb import server
-import merge as merge_
-import minirst
-
-# Commands start here, listed alphabetically
-
-def add(ui, repo, *pats, **opts):
- """add the specified files on the next commit
-
- Schedule files to be version controlled and added to the
- repository.
-
- The files will be added to the repository at the next commit. To
- undo an add before that, see hg forget.
-
- If no names are given, add all files to the repository.
- """
-
- bad = []
- exacts = {}
- names = []
- m = cmdutil.match(repo, pats, opts)
- oldbad = m.bad
- m.bad = lambda x,y: bad.append(x) or oldbad(x,y)
-
- for f in repo.walk(m):
- exact = m.exact(f)
- if exact or f not in repo.dirstate:
- names.append(f)
- if ui.verbose or not exact:
- ui.status(_('adding %s\n') % m.rel(f))
- if not opts.get('dry_run'):
- bad += [f for f in repo.add(names) if f in m.files()]
- return bad and 1 or 0
-
-def addremove(ui, repo, *pats, **opts):
- """add all new files, delete all missing files
-
- Add all new files and remove all missing files from the
- repository.
-
- New files are ignored if they match any of the patterns in
- .hgignore. As with add, these changes take effect at the next
- commit.
-
- Use the -s/--similarity option to detect renamed files. With a
- parameter greater than 0, this compares every removed file with
- every added file and records those similar enough as renames. This
- option takes a percentage between 0 (disabled) and 100 (files must
- be identical) as its parameter. Detecting renamed files this way
- can be expensive.
- """
- try:
- sim = float(opts.get('similarity') or 0)
- except ValueError:
- raise util.Abort(_('similarity must be a number'))
- if sim < 0 or sim > 100:
- raise util.Abort(_('similarity must be between 0 and 100'))
- return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
-
-def annotate(ui, repo, *pats, **opts):
- """show changeset information by line for each file
-
- List changes in files, showing the revision id responsible for
- each line
-
- This command is useful for discovering when a change was made and
- by whom.
-
- Without the -a/--text option, annotate will avoid processing files
- it detects as binary. With -a, annotate will annotate the file
- anyway, although the results will probably be neither useful
- nor desirable.
- """
- datefunc = ui.quiet and util.shortdate or util.datestr
- getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
-
- if not pats:
- raise util.Abort(_('at least one filename or pattern is required'))
-
- opmap = [('user', lambda x: ui.shortuser(x[0].user())),
- ('number', lambda x: str(x[0].rev())),
- ('changeset', lambda x: short(x[0].node())),
- ('date', getdate),
- ('follow', lambda x: x[0].path()),
- ]
-
- if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
- and not opts.get('follow')):
- opts['number'] = 1
-
- linenumber = opts.get('line_number') is not None
- if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
- raise util.Abort(_('at least one of -n/-c is required for -l'))
-
- funcmap = [func for op, func in opmap if opts.get(op)]
- if linenumber:
- lastfunc = funcmap[-1]
- funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
-
- ctx = repo[opts.get('rev')]
-
- m = cmdutil.match(repo, pats, opts)
- for abs in ctx.walk(m):
- fctx = ctx[abs]
- if not opts.get('text') and util.binary(fctx.data()):
- ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
- continue
-
- lines = fctx.annotate(follow=opts.get('follow'),
- linenumber=linenumber)
- pieces = []
-
- for f in funcmap:
- l = [f(n) for n, dummy in lines]
- if l:
- ml = max(map(len, l))
- pieces.append(["%*s" % (ml, x) for x in l])
-
- if pieces:
- for p, l in zip(zip(*pieces), lines):
- ui.write("%s: %s" % (" ".join(p), l[1]))
-
-def archive(ui, repo, dest, **opts):
- '''create an unversioned archive of a repository revision
-
- By default, the revision used is the parent of the working
- directory; use -r/--rev to specify a different revision.
-
- To specify the type of archive to create, use -t/--type. Valid
- types are::
-
- "files" (default): a directory full of files
- "tar": tar archive, uncompressed
- "tbz2": tar archive, compressed using bzip2
- "tgz": tar archive, compressed using gzip
- "uzip": zip archive, uncompressed
- "zip": zip archive, compressed using deflate
-
- The exact name of the destination archive or directory is given
- using a format string; see 'hg help export' for details.
-
- Each member added to an archive file has a directory prefix
- prepended. Use -p/--prefix to specify a format string for the
- prefix. The default is the basename of the archive, with suffixes
- removed.
- '''
-
- ctx = repo[opts.get('rev')]
- if not ctx:
- raise util.Abort(_('no working directory: please specify a revision'))
- node = ctx.node()
- dest = cmdutil.make_filename(repo, dest, node)
- if os.path.realpath(dest) == repo.root:
- raise util.Abort(_('repository root cannot be destination'))
- matchfn = cmdutil.match(repo, [], opts)
- kind = opts.get('type') or 'files'
- prefix = opts.get('prefix')
- if dest == '-':
- if kind == 'files':
- raise util.Abort(_('cannot archive plain files to stdout'))
- dest = sys.stdout
- if not prefix: prefix = os.path.basename(repo.root) + '-%h'
- prefix = cmdutil.make_filename(repo, prefix, node)
- archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
- matchfn, prefix)
-
-def backout(ui, repo, node=None, rev=None, **opts):
- '''reverse effect of earlier changeset
-
- Commit the backed out changes as a new changeset. The new
- changeset is a child of the backed out changeset.
-
- If you backout a changeset other than the tip, a new head is
- created. This head will be the new tip and you should merge this
- backout changeset with another head.
-
- The --merge option remembers the parent of the working directory
- before starting the backout, then merges the new head with that
- changeset afterwards. This saves you from doing the merge by hand.
- The result of this merge is not committed, as with a normal merge.
-
- See 'hg help dates' for a list of formats valid for -d/--date.
- '''
- if rev and node:
- raise util.Abort(_("please specify just one revision"))
-
- if not rev:
- rev = node
-
- if not rev:
- raise util.Abort(_("please specify a revision to backout"))
-
- date = opts.get('date')
- if date:
- opts['date'] = util.parsedate(date)
-
- cmdutil.bail_if_changed(repo)
- node = repo.lookup(rev)
-
- op1, op2 = repo.dirstate.parents()
- a = repo.changelog.ancestor(op1, node)
- if a != node:
- raise util.Abort(_('cannot backout change on a different branch'))
-
- p1, p2 = repo.changelog.parents(node)
- if p1 == nullid:
- raise util.Abort(_('cannot backout a change with no parents'))
- if p2 != nullid:
- if not opts.get('parent'):
- raise util.Abort(_('cannot backout a merge changeset without '
- '--parent'))
- p = repo.lookup(opts['parent'])
- if p not in (p1, p2):
- raise util.Abort(_('%s is not a parent of %s') %
- (short(p), short(node)))
- parent = p
- else:
- if opts.get('parent'):
- raise util.Abort(_('cannot use --parent on non-merge changeset'))
- parent = p1
-
- # the backout should appear on the same branch
- branch = repo.dirstate.branch()
- hg.clean(repo, node, show_stats=False)
- repo.dirstate.setbranch(branch)
- revert_opts = opts.copy()
- revert_opts['date'] = None
- revert_opts['all'] = True
- revert_opts['rev'] = hex(parent)
- revert_opts['no_backup'] = None
- revert(ui, repo, **revert_opts)
- commit_opts = opts.copy()
- commit_opts['addremove'] = False
- if not commit_opts['message'] and not commit_opts['logfile']:
- # we don't translate commit messages
- commit_opts['message'] = "Backed out changeset %s" % short(node)
- commit_opts['force_editor'] = True
- commit(ui, repo, **commit_opts)
- def nice(node):
- return '%d:%s' % (repo.changelog.rev(node), short(node))
- ui.status(_('changeset %s backs out changeset %s\n') %
- (nice(repo.changelog.tip()), nice(node)))
- if op1 != node:
- hg.clean(repo, op1, show_stats=False)
- if opts.get('merge'):
- ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
- hg.merge(repo, hex(repo.changelog.tip()))
- else:
- ui.status(_('the backout changeset is a new head - '
- 'do not forget to merge\n'))
- ui.status(_('(use "backout --merge" '
- 'if you want to auto-merge)\n'))
-
-def bisect(ui, repo, rev=None, extra=None, command=None,
- reset=None, good=None, bad=None, skip=None, noupdate=None):
- """subdivision search of changesets
-
- This command helps to find changesets which introduce problems. To
- use, mark the earliest changeset you know exhibits the problem as
- bad, then mark the latest changeset which is free from the problem
- as good. Bisect will update your working directory to a revision
- for testing (unless the -U/--noupdate option is specified). Once
- you have performed tests, mark the working directory as good or
- bad, and bisect will either update to another candidate changeset
- or announce that it has found the bad revision.
-
- As a shortcut, you can also use the revision argument to mark a
- revision as good or bad without checking it out first.
-
- If you supply a command, it will be used for automatic bisection.
- Its exit status will be used to mark revisions as good or bad:
- status 0 means good, 125 means to skip the revision, 127
- (command not found) will abort the bisection, and any other
- non-zero exit status means the revision is bad.
- """
- def print_result(nodes, good):
- displayer = cmdutil.show_changeset(ui, repo, {})
- if len(nodes) == 1:
- # narrowed it down to a single revision
- if good:
- ui.write(_("The first good revision is:\n"))
- else:
- ui.write(_("The first bad revision is:\n"))
- displayer.show(repo[nodes[0]])
- else:
- # multiple possible revisions
- if good:
- ui.write(_("Due to skipped revisions, the first "
- "good revision could be any of:\n"))
- else:
- ui.write(_("Due to skipped revisions, the first "
- "bad revision could be any of:\n"))
- for n in nodes:
- displayer.show(repo[n])
-
- def check_state(state, interactive=True):
- if not state['good'] or not state['bad']:
- if (good or bad or skip or reset) and interactive:
- return
- if not state['good']:
- raise util.Abort(_('cannot bisect (no known good revisions)'))
- else:
- raise util.Abort(_('cannot bisect (no known bad revisions)'))
- return True
-
- # backward compatibility
- if rev in "good bad reset init".split():
- ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
- cmd, rev, extra = rev, extra, None
- if cmd == "good":
- good = True
- elif cmd == "bad":
- bad = True
- else:
- reset = True
- elif extra or good + bad + skip + reset + bool(command) > 1:
- raise util.Abort(_('incompatible arguments'))
-
- if reset:
- p = repo.join("bisect.state")
- if os.path.exists(p):
- os.unlink(p)
- return
-
- state = hbisect.load_state(repo)
-
- if command:
- commandpath = util.find_exe(command)
- if commandpath is None:
- raise util.Abort(_("cannot find executable: %s") % command)
- changesets = 1
- try:
- while changesets:
- # update state
- status = subprocess.call([commandpath])
- if status == 125:
- transition = "skip"
- elif status == 0:
- transition = "good"
- # status < 0 means process was killed
- elif status == 127:
- raise util.Abort(_("failed to execute %s") % command)
- elif status < 0:
- raise util.Abort(_("%s killed") % command)
- else:
- transition = "bad"
- ctx = repo[rev or '.']
- state[transition].append(ctx.node())
- ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
- check_state(state, interactive=False)
- # bisect
- nodes, changesets, good = hbisect.bisect(repo.changelog, state)
- # update to next check
- cmdutil.bail_if_changed(repo)
- hg.clean(repo, nodes[0], show_stats=False)
- finally:
- hbisect.save_state(repo, state)
- return print_result(nodes, not status)
-
- # update state
- node = repo.lookup(rev or '.')
- if good:
- state['good'].append(node)
- elif bad:
- state['bad'].append(node)
- elif skip:
- state['skip'].append(node)
-
- hbisect.save_state(repo, state)
-
- if not check_state(state):
- return
-
- # actually bisect
- nodes, changesets, good = hbisect.bisect(repo.changelog, state)
- if changesets == 0:
- print_result(nodes, good)
- else:
- assert len(nodes) == 1 # only a single node can be tested next
- node = nodes[0]
- # compute the approximate number of remaining tests
- tests, size = 0, 2
- while size <= changesets:
- tests, size = tests + 1, size * 2
- rev = repo.changelog.rev(node)
- ui.write(_("Testing changeset %d:%s "
- "(%d changesets remaining, ~%d tests)\n")
- % (rev, short(node), changesets, tests))
- if not noupdate:
- cmdutil.bail_if_changed(repo)
- return hg.clean(repo, node)
-
-def branch(ui, repo, label=None, **opts):
- """set or show the current branch name
-
- With no argument, show the current branch name. With one argument,
- set the working directory branch name (the branch will not exist
- in the repository until the next commit). Standard practice
- recommends that primary development take place on the 'default'
- branch.
-
- Unless -f/--force is specified, branch will not let you set a
- branch name that already exists, even if it's inactive.
-
- Use -C/--clean to reset the working directory branch to that of
- the parent of the working directory, negating a previous branch
- change.
-
- Use the command 'hg update' to switch to an existing branch. Use
- 'hg commit --close-branch' to mark this branch as closed.
- """
-
- if opts.get('clean'):
- label = repo[None].parents()[0].branch()
- repo.dirstate.setbranch(label)
- ui.status(_('reset working directory to branch %s\n') % label)
- elif label:
- if not opts.get('force') and label in repo.branchtags():
- if label not in [p.branch() for p in repo.parents()]:
- raise util.Abort(_('a branch of the same name already exists'
- ' (use --force to override)'))
- repo.dirstate.setbranch(encoding.fromlocal(label))
- ui.status(_('marked working directory as branch %s\n') % label)
- else:
- ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
-
-def branches(ui, repo, active=False, closed=False):
- """list repository named branches
-
- List the repository's named branches, indicating which ones are
- inactive. If -c/--closed is specified, also list branches which have
- been marked closed (see hg commit --close-branch).
-
- If -a/--active is specified, only show active branches. A branch
- is considered active if it contains repository heads.
-
- Use the command 'hg update' to switch to an existing branch.
- """
-
- hexfunc = ui.debugflag and hex or short
- activebranches = [encoding.tolocal(repo[n].branch())
- for n in repo.heads()]
- def testactive(tag, node):
- realhead = tag in activebranches
- open = node in repo.branchheads(tag, closed=False)
- return realhead and open
- branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
- for tag, node in repo.branchtags().items()],
- reverse=True)
-
- for isactive, node, tag in branches:
- if (not active) or isactive:
- if ui.quiet:
- ui.write("%s\n" % tag)
- else:
- hn = repo.lookup(node)
- if isactive:
- notice = ''
- elif hn not in repo.branchheads(tag, closed=False):
- if not closed:
- continue
- notice = ' (closed)'
- else:
- notice = ' (inactive)'
- rev = str(node).rjust(31 - encoding.colwidth(tag))
- data = tag, rev, hexfunc(hn), notice
- ui.write("%s %s:%s%s\n" % data)
-
-def bundle(ui, repo, fname, dest=None, **opts):
- """create a changegroup file
-
- Generate a compressed changegroup file collecting changesets not
- known to be in another repository.
-
- If no destination repository is specified the destination is
- assumed to have all the nodes specified by one or more --base
- parameters. To create a bundle containing all changesets, use
- -a/--all (or --base null).
-
- You can change compression method with the -t/--type option.
- The available compression methods are: none, bzip2, and
- gzip (by default, bundles are compressed using bzip2).
-
- The bundle file can then be transferred using conventional means
- and applied to another repository with the unbundle or pull
- command. This is useful when direct push and pull are not
- available or when exporting an entire repository is undesirable.
-
- Applying bundles preserves all changeset contents including
- permissions, copy/rename information, and revision history.
- """
- revs = opts.get('rev') or None
- if revs:
- revs = [repo.lookup(rev) for rev in revs]
- if opts.get('all'):
- base = ['null']
- else:
- base = opts.get('base')
- if base:
- if dest:
- raise util.Abort(_("--base is incompatible with specifying "
- "a destination"))
- base = [repo.lookup(rev) for rev in base]
- # create the right base
- # XXX: nodesbetween / changegroup* should be "fixed" instead
- o = []
- has = set((nullid,))
- for n in base:
- has.update(repo.changelog.reachable(n))
- if revs:
- visit = list(revs)
- else:
- visit = repo.changelog.heads()
- seen = {}
- while visit:
- n = visit.pop(0)
- parents = [p for p in repo.changelog.parents(n) if p not in has]
- if len(parents) == 0:
- o.insert(0, n)
- else:
- for p in parents:
- if p not in seen:
- seen[p] = 1
- visit.append(p)
- else:
- dest, revs, checkout = hg.parseurl(
- ui.expandpath(dest or 'default-push', dest or 'default'), revs)
- other = hg.repository(cmdutil.remoteui(repo, opts), dest)
- o = repo.findoutgoing(other, force=opts.get('force'))
-
- if revs:
- cg = repo.changegroupsubset(o, revs, 'bundle')
- else:
- cg = repo.changegroup(o, 'bundle')
-
- bundletype = opts.get('type', 'bzip2').lower()
- btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
- bundletype = btypes.get(bundletype)
- if bundletype not in changegroup.bundletypes:
- raise util.Abort(_('unknown bundle type specified with --type'))
-
- changegroup.writebundle(cg, fname, bundletype)
-
-def cat(ui, repo, file1, *pats, **opts):
- """output the current or given revision of files
-
- Print the specified files as they were at the given revision. If
- no revision is given, the parent of the working directory is used,
- or tip if no revision is checked out.
-
- Output may be to a file, in which case the name of the file is
- given using a format string. The formatting rules are the same as
- for the export command, with the following additions::
-
- %s basename of file being printed
- %d dirname of file being printed, or '.' if in repository root
- %p root-relative path name of file being printed
- """
- ctx = repo[opts.get('rev')]
- err = 1
- m = cmdutil.match(repo, (file1,) + pats, opts)
- for abs in ctx.walk(m):
- fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
- data = ctx[abs].data()
- if opts.get('decode'):
- data = repo.wwritedata(abs, data)
- fp.write(data)
- err = 0
- return err
-
-def clone(ui, source, dest=None, **opts):
- """make a copy of an existing repository
-
- Create a copy of an existing repository in a new directory.
-
- If no destination directory name is specified, it defaults to the
- basename of the source.
-
- The location of the source is added to the new repository's
- .hg/hgrc file, as the default to be used for future pulls.
-
- If you use the -r/--rev option to clone up to a specific revision,
- no subsequent revisions (including subsequent tags) will be
- present in the cloned repository. This option implies --pull, even
- on local repositories.
-
- By default, clone will check out the head of the 'default' branch.
- If the -U/--noupdate option is used, the new clone will contain
- only a repository (.hg) and no working copy (the working copy
- parent is the null revision).
-
- See 'hg help urls' for valid source format details.
-
- It is possible to specify an ssh:// URL as the destination, but no
- .hg/hgrc and working directory will be created on the remote side.
- Please see 'hg help urls' for important details about ssh:// URLs.
-
- For efficiency, hardlinks are used for cloning whenever the source
- and destination are on the same filesystem (note this applies only
- to the repository data, not to the checked out files). Some
- filesystems, such as AFS, implement hardlinking incorrectly, but
- do not report errors. In these cases, use the --pull option to
- avoid hardlinking.
-
- In some cases, you can clone repositories and checked out files
- using full hardlinks with ::
-
- $ cp -al REPO REPOCLONE
-
- This is the fastest way to clone, but it is not always safe. The
- operation is not atomic (making sure REPO is not modified during
- the operation is up to you) and you have to make sure your editor
- breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
- this is not compatible with certain extensions that place their
- metadata under the .hg directory, such as mq.
- """
- hg.clone(cmdutil.remoteui(ui, opts), source, dest,
- pull=opts.get('pull'),
- stream=opts.get('uncompressed'),
- rev=opts.get('rev'),
- update=not opts.get('noupdate'))
-
-def commit(ui, repo, *pats, **opts):
- """commit the specified files or all outstanding changes
-
- Commit changes to the given files into the repository. Unlike a
- centralized RCS, this operation is a local operation. See hg push
- for a way to actively distribute your changes.
-
- If a list of files is omitted, all changes reported by "hg status"
- will be committed.
-
- If you are committing the result of a merge, do not provide any
- filenames or -I/-X filters.
-
- If no commit message is specified, the configured editor is
- started to prompt you for a message.
-
- See 'hg help dates' for a list of formats valid for -d/--date.
- """
- extra = {}
- if opts.get('close_branch'):
- extra['close'] = 1
- e = cmdutil.commiteditor
- if opts.get('force_editor'):
- e = cmdutil.commitforceeditor
-
- def commitfunc(ui, repo, message, match, opts):
- return repo.commit(message, opts.get('user'), opts.get('date'), match,
- editor=e, extra=extra)
-
- node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
- if not node:
- ui.status(_("nothing changed\n"))
- return
- cl = repo.changelog
- rev = cl.rev(node)
- parents = cl.parentrevs(rev)
- if rev - 1 in parents:
- # one of the parents was the old tip
- pass
- elif (parents == (nullrev, nullrev) or
- len(cl.heads(cl.node(parents[0]))) > 1 and
- (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
- ui.status(_('created new head\n'))
-
- if ui.debugflag:
- ui.write(_('committed changeset %d:%s\n') % (rev, hex(node)))
- elif ui.verbose:
- ui.write(_('committed changeset %d:%s\n') % (rev, short(node)))
-
-def copy(ui, repo, *pats, **opts):
- """mark files as copied for the next commit
-
- Mark dest as having copies of source files. If dest is a
- directory, copies are put in that directory. If dest is a file,
- the source must be a single file.
-
- By default, this command copies the contents of files as they
- exist in the working directory. If invoked with -A/--after, the
- operation is recorded, but no copying is performed.
-
- This command takes effect with the next commit. To undo a copy
- before that, see hg revert.
- """
- wlock = repo.wlock(False)
- try:
- return cmdutil.copy(ui, repo, pats, opts)
- finally:
- wlock.release()
-
-def debugancestor(ui, repo, *args):
- """find the ancestor revision of two revisions in a given index"""
- if len(args) == 3:
- index, rev1, rev2 = args
- r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
- lookup = r.lookup
- elif len(args) == 2:
- if not repo:
- raise util.Abort(_("There is no Mercurial repository here "
- "(.hg not found)"))
- rev1, rev2 = args
- r = repo.changelog
- lookup = repo.lookup
- else:
- raise util.Abort(_('either two or three arguments required'))
- a = r.ancestor(lookup(rev1), lookup(rev2))
- ui.write("%d:%s\n" % (r.rev(a), hex(a)))
-
-def debugcommands(ui, cmd='', *args):
- for cmd, vals in sorted(table.iteritems()):
- cmd = cmd.split('|')[0].strip('^')
- opts = ', '.join([i[1] for i in vals[1]])
- ui.write('%s: %s\n' % (cmd, opts))
-
-def debugcomplete(ui, cmd='', **opts):
- """returns the completion list associated with the given command"""
-
- if opts.get('options'):
- options = []
- otables = [globalopts]
- if cmd:
- aliases, entry = cmdutil.findcmd(cmd, table, False)
- otables.append(entry[1])
- for t in otables:
- for o in t:
- if o[0]:
- options.append('-%s' % o[0])
- options.append('--%s' % o[1])
- ui.write("%s\n" % "\n".join(options))
- return
-
- cmdlist = cmdutil.findpossible(cmd, table)
- if ui.verbose:
- cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
- ui.write("%s\n" % "\n".join(sorted(cmdlist)))
-
-def debugfsinfo(ui, path = "."):
- open('.debugfsinfo', 'w').write('')
- ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
- ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
- ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
- and 'yes' or 'no'))
- os.unlink('.debugfsinfo')
-
-def debugrebuildstate(ui, repo, rev="tip"):
- """rebuild the dirstate as it would look like for the given revision"""
- ctx = repo[rev]
- wlock = repo.wlock()
- try:
- repo.dirstate.rebuild(ctx.node(), ctx.manifest())
- finally:
- wlock.release()
-
-def debugcheckstate(ui, repo):
- """validate the correctness of the current dirstate"""
- parent1, parent2 = repo.dirstate.parents()
- m1 = repo[parent1].manifest()
- m2 = repo[parent2].manifest()
- errors = 0
- for f in repo.dirstate:
- state = repo.dirstate[f]
- if state in "nr" and f not in m1:
- ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
- errors += 1
- if state in "a" and f in m1:
- ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
- errors += 1
- if state in "m" and f not in m1 and f not in m2:
- ui.warn(_("%s in state %s, but not in either manifest\n") %
- (f, state))
- errors += 1
- for f in m1:
- state = repo.dirstate[f]
- if state not in "nrm":
- ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
- errors += 1
- if errors:
- error = _(".hg/dirstate inconsistent with current parent's manifest")
- raise util.Abort(error)
-
-def showconfig(ui, repo, *values, **opts):
- """show combined config settings from all hgrc files
-
- With no arguments, print names and values of all config items.
-
- With one argument of the form section.name, print just the value
- of that config item.
-
- With multiple arguments, print names and values of all config
- items with matching section names.
-
- With --debug, the source (filename and line number) is printed
- for each config item.
- """
-
- untrusted = bool(opts.get('untrusted'))
- if values:
- if len([v for v in values if '.' in v]) > 1:
- raise util.Abort(_('only one config item permitted'))
- for section, name, value in ui.walkconfig(untrusted=untrusted):
- sectname = section + '.' + name
- if values:
- for v in values:
- if v == section:
- ui.debug('%s: ' %
- ui.configsource(section, name, untrusted))
- ui.write('%s=%s\n' % (sectname, value))
- elif v == sectname:
- ui.debug('%s: ' %
- ui.configsource(section, name, untrusted))
- ui.write(value, '\n')
- else:
- ui.debug('%s: ' %
- ui.configsource(section, name, untrusted))
- ui.write('%s=%s\n' % (sectname, value))
-
-def debugsetparents(ui, repo, rev1, rev2=None):
- """manually set the parents of the current working directory
-
- This is useful for writing repository conversion tools, but should
- be used with care.
- """
-
- if not rev2:
- rev2 = hex(nullid)
-
- wlock = repo.wlock()
- try:
- repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
- finally:
- wlock.release()
-
-def debugstate(ui, repo, nodates=None):
- """show the contents of the current dirstate"""
- timestr = ""
- showdate = not nodates
- for file_, ent in sorted(repo.dirstate._map.iteritems()):
- if showdate:
- if ent[3] == -1:
- # Pad or slice to locale representation
- locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
- timestr = 'unset'
- timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
- else:
- timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
- if ent[1] & 020000:
- mode = 'lnk'
- else:
- mode = '%3o' % (ent[1] & 0777)
- ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
- for f in repo.dirstate.copies():
- ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
-
-def debugsub(ui, repo, rev=None):
- if rev == '':
- rev = None
- for k,v in sorted(repo[rev].substate.items()):
- ui.write('path %s\n' % k)
- ui.write(' source %s\n' % v[0])
- ui.write(' revision %s\n' % v[1])
-
-def debugdata(ui, file_, rev):
- """dump the contents of a data file revision"""
- r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
- try:
- ui.write(r.revision(r.lookup(rev)))
- except KeyError:
- raise util.Abort(_('invalid revision identifier %s') % rev)
-
-def debugdate(ui, date, range=None, **opts):
- """parse and display a date"""
- if opts["extended"]:
- d = util.parsedate(date, util.extendeddateformats)
- else:
- d = util.parsedate(date)
- ui.write("internal: %s %s\n" % d)
- ui.write("standard: %s\n" % util.datestr(d))
- if range:
- m = util.matchdate(range)
- ui.write("match: %s\n" % m(d[0]))
-
-def debugindex(ui, file_):
- """dump the contents of an index file"""
- r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
- ui.write(" rev offset length base linkrev"
- " nodeid p1 p2\n")
- for i in r:
- node = r.node(i)
- try:
- pp = r.parents(node)
- except:
- pp = [nullid, nullid]
- ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
- i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
- short(node), short(pp[0]), short(pp[1])))
-
-def debugindexdot(ui, file_):
- """dump an index DAG as a graphviz dot file"""
- r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
- ui.write("digraph G {\n")
- for i in r:
- node = r.node(i)
- pp = r.parents(node)
- ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
- if pp[1] != nullid:
- ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
- ui.write("}\n")
-
-def debuginstall(ui):
- '''test Mercurial installation'''
-
- def writetemp(contents):
- (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
- f = os.fdopen(fd, "wb")
- f.write(contents)
- f.close()
- return name
-
- problems = 0
-
- # encoding
- ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
- try:
- encoding.fromlocal("test")
- except util.Abort, inst:
- ui.write(" %s\n" % inst)
- ui.write(_(" (check that your locale is properly set)\n"))
- problems += 1
-
- # compiled modules
- ui.status(_("Checking extensions...\n"))
- try:
- import bdiff, mpatch, base85
- except Exception, inst:
- ui.write(" %s\n" % inst)
- ui.write(_(" One or more extensions could not be found"))
- ui.write(_(" (check that you compiled the extensions)\n"))
- problems += 1
-
- # templates
- ui.status(_("Checking templates...\n"))
- try:
- import templater
- templater.templater(templater.templatepath("map-cmdline.default"))
- except Exception, inst:
- ui.write(" %s\n" % inst)
- ui.write(_(" (templates seem to have been installed incorrectly)\n"))
- problems += 1
-
- # patch
- ui.status(_("Checking patch...\n"))
- patchproblems = 0
- a = "1\n2\n3\n4\n"
- b = "1\n2\n3\ninsert\n4\n"
- fa = writetemp(a)
- d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
- os.path.basename(fa))
- fd = writetemp(d)
-
- files = {}
- try:
- patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
- except util.Abort, e:
- ui.write(_(" patch call failed:\n"))
- ui.write(" " + str(e) + "\n")
- patchproblems += 1
- else:
- if list(files) != [os.path.basename(fa)]:
- ui.write(_(" unexpected patch output!\n"))
- patchproblems += 1
- a = open(fa).read()
- if a != b:
- ui.write(_(" patch test failed!\n"))
- patchproblems += 1
-
- if patchproblems:
- if ui.config('ui', 'patch'):
- ui.write(_(" (Current patch tool may be incompatible with patch,"
- " or misconfigured. Please check your .hgrc file)\n"))
- else:
- ui.write(_(" Internal patcher failure, please report this error"
- " to http://mercurial.selenic.com/bts/\n"))
- problems += patchproblems
-
- os.unlink(fa)
- os.unlink(fd)
-
- # editor
- ui.status(_("Checking commit editor...\n"))
- editor = ui.geteditor()
- cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
- if not cmdpath:
- if editor == 'vi':
- ui.write(_(" No commit editor set and can't find vi in PATH\n"))
- ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
- else:
- ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
- ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
- problems += 1
-
- # check username
- ui.status(_("Checking username...\n"))
- user = os.environ.get("HGUSER")
- if user is None:
- user = ui.config("ui", "username")
- if user is None:
- user = os.environ.get("EMAIL")
- if not user:
- ui.warn(" ")
- ui.username()
- ui.write(_(" (specify a username in your .hgrc file)\n"))
-
- if not problems:
- ui.status(_("No problems detected\n"))
- else:
- ui.write(_("%s problems detected,"
- " please check your install!\n") % problems)
-
- return problems
-
-def debugrename(ui, repo, file1, *pats, **opts):
- """dump rename information"""
-
- ctx = repo[opts.get('rev')]
- m = cmdutil.match(repo, (file1,) + pats, opts)
- for abs in ctx.walk(m):
- fctx = ctx[abs]
- o = fctx.filelog().renamed(fctx.filenode())
- rel = m.rel(abs)
- if o:
- ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
- else:
- ui.write(_("%s not renamed\n") % rel)
-
-def debugwalk(ui, repo, *pats, **opts):
- """show how files match on given patterns"""
- m = cmdutil.match(repo, pats, opts)
- items = list(repo.walk(m))
- if not items:
- return
- fmt = 'f %%-%ds %%-%ds %%s' % (
- max([len(abs) for abs in items]),
- max([len(m.rel(abs)) for abs in items]))
- for abs in items:
- line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
- ui.write("%s\n" % line.rstrip())
-
-def diff(ui, repo, *pats, **opts):
- """diff repository (or selected files)
-
- Show differences between revisions for the specified files.
-
- Differences between files are shown using the unified diff format.
-
- NOTE: diff may generate unexpected results for merges, as it will
- default to comparing against the working directory's first parent
- changeset if no revisions are specified.
-
- When two revision arguments are given, then changes are shown
- between those revisions. If only one revision is specified then
- that revision is compared to the working directory, and, when no
- revisions are specified, the working directory files are compared
- to its parent.
-
- Without the -a/--text option, diff will avoid generating diffs of
- files it detects as binary. With -a, diff will generate a diff
- anyway, probably with undesirable results.
-
- Use the -g/--git option to generate diffs in the git extended diff
- format. For more information, read 'hg help diffs'.
- """
-
- revs = opts.get('rev')
- change = opts.get('change')
-
- if revs and change:
- msg = _('cannot specify --rev and --change at the same time')
- raise util.Abort(msg)
- elif change:
- node2 = repo.lookup(change)
- node1 = repo[node2].parents()[0].node()
- else:
- node1, node2 = cmdutil.revpair(repo, revs)
-
- m = cmdutil.match(repo, pats, opts)
- it = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
- for chunk in it:
- ui.write(chunk)
-
-def export(ui, repo, *changesets, **opts):
- """dump the header and diffs for one or more changesets
-
- Print the changeset header and diffs for one or more revisions.
-
- The information shown in the changeset header is: author,
- changeset hash, parent(s) and commit comment.
-
- NOTE: export may generate unexpected diff output for merge
- changesets, as it will compare the merge changeset against its
- first parent only.
-
- Output may be to a file, in which case the name of the file is
- given using a format string. The formatting rules are as follows::
-
- %% literal "%" character
- %H changeset hash (40 bytes of hexadecimal)
- %N number of patches being generated
- %R changeset revision number
- %b basename of the exporting repository
- %h short-form changeset hash (12 bytes of hexadecimal)
- %n zero-padded sequence number, starting at 1
- %r zero-padded changeset revision number
-
- Without the -a/--text option, export will avoid generating diffs
- of files it detects as binary. With -a, export will generate a
- diff anyway, probably with undesirable results.
-
- Use the -g/--git option to generate diffs in the git extended diff
- format. See 'hg help diffs' for more information.
-
- With the --switch-parent option, the diff will be against the
- second parent. It can be useful to review a merge.
- """
- if not changesets:
- raise util.Abort(_("export requires at least one changeset"))
- revs = cmdutil.revrange(repo, changesets)
- if len(revs) > 1:
- ui.note(_('exporting patches:\n'))
- else:
- ui.note(_('exporting patch:\n'))
- patch.export(repo, revs, template=opts.get('output'),
- switch_parent=opts.get('switch_parent'),
- opts=patch.diffopts(ui, opts))
-
-def forget(ui, repo, *pats, **opts):
- """forget the specified files on the next commit
-
- Mark the specified files so they will no longer be tracked
- after the next commit.
-
- This only removes files from the current branch, not from the
- entire project history, and it does not delete them from the
- working directory.
-
- To undo a forget before the next commit, see hg add.
- """
-
- if not pats:
- raise util.Abort(_('no files specified'))
-
- m = cmdutil.match(repo, pats, opts)
- s = repo.status(match=m, clean=True)
- forget = sorted(s[0] + s[1] + s[3] + s[6])
-
- for f in m.files():
- if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
- ui.warn(_('not removing %s: file is already untracked\n')
- % m.rel(f))
-
- for f in forget:
- if ui.verbose or not m.exact(f):
- ui.status(_('removing %s\n') % m.rel(f))
-
- repo.remove(forget, unlink=False)
-
-def grep(ui, repo, pattern, *pats, **opts):
- """search for a pattern in specified files and revisions
-
- Search revisions of files for a regular expression.
-
- This command behaves differently than Unix grep. It only accepts
- Python/Perl regexps. It searches repository history, not the
- working directory. It always prints the revision number in which a
- match appears.
-
- By default, grep only prints output for the first revision of a
- file in which it finds a match. To get it to print every revision
- that contains a change in match status ("-" for a match that
- becomes a non-match, or "+" for a non-match that becomes a match),
- use the --all flag.
- """
- reflags = 0
- if opts.get('ignore_case'):
- reflags |= re.I
- try:
- regexp = re.compile(pattern, reflags)
- except Exception, inst:
- ui.warn(_("grep: invalid match pattern: %s\n") % inst)
- return None
- sep, eol = ':', '\n'
- if opts.get('print0'):
- sep = eol = '\0'
-
- getfile = util.lrucachefunc(repo.file)
-
- def matchlines(body):
- begin = 0
- linenum = 0
- while True:
- match = regexp.search(body, begin)
- if not match:
- break
- mstart, mend = match.span()
- linenum += body.count('\n', begin, mstart) + 1
- lstart = body.rfind('\n', begin, mstart) + 1 or begin
- begin = body.find('\n', mend) + 1 or len(body)
- lend = begin - 1
- yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
-
- class linestate(object):
- def __init__(self, line, linenum, colstart, colend):
- self.line = line
- self.linenum = linenum
- self.colstart = colstart
- self.colend = colend
-
- def __hash__(self):
- return hash((self.linenum, self.line))
-
- def __eq__(self, other):
- return self.line == other.line
-
- matches = {}
- copies = {}
- def grepbody(fn, rev, body):
- matches[rev].setdefault(fn, [])
- m = matches[rev][fn]
- for lnum, cstart, cend, line in matchlines(body):
- s = linestate(line, lnum, cstart, cend)
- m.append(s)
-
- def difflinestates(a, b):
- sm = difflib.SequenceMatcher(None, a, b)
- for tag, alo, ahi, blo, bhi in sm.get_opcodes():
- if tag == 'insert':
- for i in xrange(blo, bhi):
- yield ('+', b[i])
- elif tag == 'delete':
- for i in xrange(alo, ahi):
- yield ('-', a[i])
- elif tag == 'replace':
- for i in xrange(alo, ahi):
- yield ('-', a[i])
- for i in xrange(blo, bhi):
- yield ('+', b[i])
-
- def display(fn, r, pstates, states):
- datefunc = ui.quiet and util.shortdate or util.datestr
- found = False
- filerevmatches = {}
- if opts.get('all'):
- iter = difflinestates(pstates, states)
- else:
- iter = [('', l) for l in states]
- for change, l in iter:
- cols = [fn, str(r)]
- if opts.get('line_number'):
- cols.append(str(l.linenum))
- if opts.get('all'):
- cols.append(change)
- if opts.get('user'):
- cols.append(ui.shortuser(get(r)[1]))
- if opts.get('date'):
- cols.append(datefunc(get(r)[2]))
- if opts.get('files_with_matches'):
- c = (fn, r)
- if c in filerevmatches:
- continue
- filerevmatches[c] = 1
- else:
- cols.append(l.line)
- ui.write(sep.join(cols), eol)
- found = True
- return found
-
- skip = {}
- revfiles = {}
- get = util.cachefunc(lambda r: repo[r].changeset())
- changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
- found = False
- follow = opts.get('follow')
- for st, rev, fns in changeiter:
- if st == 'window':
- matches.clear()
- revfiles.clear()
- elif st == 'add':
- ctx = repo[rev]
- pctx = ctx.parents()[0]
- parent = pctx.rev()
- matches.setdefault(rev, {})
- matches.setdefault(parent, {})
- files = revfiles.setdefault(rev, [])
- for fn in fns:
- flog = getfile(fn)
- try:
- fnode = ctx.filenode(fn)
- except error.LookupError:
- continue
-
- copied = flog.renamed(fnode)
- copy = follow and copied and copied[0]
- if copy:
- copies.setdefault(rev, {})[fn] = copy
- if fn in skip:
- if copy:
- skip[copy] = True
- continue
- files.append(fn)
-
- if not matches[rev].has_key(fn):
- grepbody(fn, rev, flog.read(fnode))
-
- pfn = copy or fn
- if not matches[parent].has_key(pfn):
- try:
- fnode = pctx.filenode(pfn)
- grepbody(pfn, parent, flog.read(fnode))
- except error.LookupError:
- pass
- elif st == 'iter':
- parent = repo[rev].parents()[0].rev()
- for fn in sorted(revfiles.get(rev, [])):
- states = matches[rev][fn]
- copy = copies.get(rev, {}).get(fn)
- if fn in skip:
- if copy:
- skip[copy] = True
- continue
- pstates = matches.get(parent, {}).get(copy or fn, [])
- if pstates or states:
- r = display(fn, rev, pstates, states)
- found = found or r
- if r and not opts.get('all'):
- skip[fn] = True
- if copy:
- skip[copy] = True
-
-def heads(ui, repo, *branchrevs, **opts):
- """show current repository heads or show branch heads
-
- With no arguments, show all repository head changesets.
-
- Repository "heads" are changesets that don't have child
- changesets. They are where development generally takes place and
- are the usual targets for update and merge operations.
-
- If one or more REV is given, the "branch heads" will be shown for
- the named branch associated with that revision. The name of the
- branch is called the revision's branch tag.
-
- Branch heads are revisions on a given named branch that do not have
- any descendants on the same branch. A branch head could be a true head
- or it could be the last changeset on a branch before a new branch
- was created. If none of the branch heads are true heads, the branch
- is considered inactive. If -c/--closed is specified, also show branch
- heads marked closed (see hg commit --close-branch).
-
- If STARTREV is specified only those heads (or branch heads) that
- are descendants of STARTREV will be displayed.
- """
- if opts.get('rev'):
- start = repo.lookup(opts['rev'])
- else:
- start = None
- closed = opts.get('closed')
- hideinactive, _heads = opts.get('active'), None
- if not branchrevs:
- if closed:
- raise error.Abort(_('you must specify a branch to use --closed'))
- # Assume we're looking repo-wide heads if no revs were specified.
- heads = repo.heads(start)
- else:
- if hideinactive:
- _heads = repo.heads(start)
- heads = []
- visitedset = set()
- for branchrev in branchrevs:
- branch = repo[branchrev].branch()
- if branch in visitedset:
- continue
- visitedset.add(branch)
- bheads = repo.branchheads(branch, start, closed=closed)
- if not bheads:
- if not opts.get('rev'):
- ui.warn(_("no open branch heads on branch %s\n") % branch)
- elif branch != branchrev:
- ui.warn(_("no changes on branch %s containing %s are "
- "reachable from %s\n")
- % (branch, branchrev, opts.get('rev')))
- else:
- ui.warn(_("no changes on branch %s are reachable from %s\n")
- % (branch, opts.get('rev')))
- if hideinactive:
- bheads = [bhead for bhead in bheads if bhead in _heads]
- heads.extend(bheads)
- if not heads:
- return 1
- displayer = cmdutil.show_changeset(ui, repo, opts)
- for n in heads:
- displayer.show(repo[n])
-
-def help_(ui, name=None, with_version=False):
- """show help for a given topic or a help overview
-
- With no arguments, print a list of commands with short help messages.
-
- Given a topic, extension, or command name, print help for that
- topic."""
- option_lists = []
- textwidth = util.termwidth() - 2
-
- def addglobalopts(aliases):
- if ui.verbose:
- option_lists.append((_("global options:"), globalopts))
- if name == 'shortlist':
- option_lists.append((_('use "hg help" for the full list '
- 'of commands'), ()))
- else:
- if name == 'shortlist':
- msg = _('use "hg help" for the full list of commands '
- 'or "hg -v" for details')
- elif aliases:
- msg = _('use "hg -v help%s" to show aliases and '
- 'global options') % (name and " " + name or "")
- else:
- msg = _('use "hg -v help %s" to show global options') % name
- option_lists.append((msg, ()))
-
- def helpcmd(name):
- if with_version:
- version_(ui)
- ui.write('\n')
-
- try:
- aliases, i = cmdutil.findcmd(name, table, False)
- except error.AmbiguousCommand, inst:
- # py3k fix: except vars can't be used outside the scope of the
- # except block, nor can be used inside a lambda. python issue4617
- prefix = inst.args[0]
- select = lambda c: c.lstrip('^').startswith(prefix)
- helplist(_('list of commands:\n\n'), select)
- return
-
- # synopsis
- if len(i) > 2:
- if i[2].startswith('hg'):
- ui.write("%s\n" % i[2])
- else:
- ui.write('hg %s %s\n' % (aliases[0], i[2]))
- else:
- ui.write('hg %s\n' % aliases[0])
-
- # aliases
- if not ui.quiet and len(aliases) > 1:
- ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
-
- # description
- doc = gettext(i[0].__doc__)
- if not doc:
- doc = _("(no help text available)")
- if ui.quiet:
- doc = doc.splitlines()[0]
- ui.write("\n%s\n" % minirst.format(doc, textwidth))
-
- if not ui.quiet:
- # options
- if i[1]:
- option_lists.append((_("options:\n"), i[1]))
-
- addglobalopts(False)
-
- def helplist(header, select=None):
- h = {}
- cmds = {}
- for c, e in table.iteritems():
- f = c.split("|", 1)[0]
- if select and not select(f):
- continue
- if (not select and name != 'shortlist' and
- e[0].__module__ != __name__):
- continue
- if name == "shortlist" and not f.startswith("^"):
- continue
- f = f.lstrip("^")
- if not ui.debugflag and f.startswith("debug"):
- continue
- doc = e[0].__doc__
- if doc and 'DEPRECATED' in doc and not ui.verbose:
- continue
- doc = gettext(doc)
- if not doc:
- doc = _("(no help text available)")
- h[f] = doc.splitlines()[0].rstrip()
- cmds[f] = c.lstrip("^")
-
- if not h:
- ui.status(_('no commands defined\n'))
- return
-
- ui.status(header)
- fns = sorted(h)
- m = max(map(len, fns))
- for f in fns:
- if ui.verbose:
- commands = cmds[f].replace("|",", ")
- ui.write(" %s:\n %s\n"%(commands, h[f]))
- else:
- ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
-
- if name != 'shortlist':
- exts, maxlength = extensions.enabled()
- text = help.listexts(_('enabled extensions:'), exts, maxlength)
- if text:
- ui.write("\n%s\n" % minirst.format(text, textwidth))
-
- if not ui.quiet:
- addglobalopts(True)
-
- def helptopic(name):
- for names, header, doc in help.helptable:
- if name in names:
- break
- else:
- raise error.UnknownCommand(name)
-
- # description
- if not doc:
- doc = _("(no help text available)")
- if hasattr(doc, '__call__'):
- doc = doc()
-
- ui.write("%s\n\n" % header)
- ui.write("%s\n" % minirst.format(doc, textwidth))
-
- def helpext(name):
- try:
- mod = extensions.find(name)
- except KeyError:
- raise error.UnknownCommand(name)
-
- doc = gettext(mod.__doc__) or _('no help text available')
- if '\n' not in doc:
- head, tail = doc, ""
- else:
- head, tail = doc.split('\n', 1)
- ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
- if tail:
- ui.write(minirst.format(tail, textwidth))
- ui.status('\n\n')
-
- try:
- ct = mod.cmdtable
- except AttributeError:
- ct = {}
-
- modcmds = set([c.split('|', 1)[0] for c in ct])
- helplist(_('list of commands:\n\n'), modcmds.__contains__)
-
- if name and name != 'shortlist':
- i = None
- for f in (helptopic, helpcmd, helpext):
- try:
- f(name)
- i = None
- break
- except error.UnknownCommand, inst:
- i = inst
- if i:
- raise i
-
- else:
- # program name
- if ui.verbose or with_version:
- version_(ui)
- else:
- ui.status(_("Mercurial Distributed SCM\n"))
- ui.status('\n')
-
- # list of commands
- if name == "shortlist":
- header = _('basic commands:\n\n')
- else:
- header = _('list of commands:\n\n')
-
- helplist(header)
-
- # list all option lists
- opt_output = []
- for title, options in option_lists:
- opt_output.append(("\n%s" % title, None))
- for shortopt, longopt, default, desc in options:
- if "DEPRECATED" in desc and not ui.verbose: continue
- opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
- longopt and " --%s" % longopt),
- "%s%s" % (desc,
- default
- and _(" (default: %s)") % default
- or "")))
-
- if not name:
- ui.write(_("\nadditional help topics:\n\n"))
- topics = []
- for names, header, doc in help.helptable:
- names = [(-len(name), name) for name in names]
- names.sort()
- topics.append((names[0][1], header))
- topics_len = max([len(s[0]) for s in topics])
- for t, desc in topics:
- ui.write(" %-*s %s\n" % (topics_len, t, desc))
-
- if opt_output:
- opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
- for first, second in opt_output:
- if second:
- second = util.wrap(second, opts_len + 3)
- ui.write(" %-*s %s\n" % (opts_len, first, second))
- else:
- ui.write("%s\n" % first)
-
-def identify(ui, repo, source=None,
- rev=None, num=None, id=None, branch=None, tags=None):
- """identify the working copy or specified revision
-
- With no revision, print a summary of the current state of the
- repository.
-
- Specifying a path to a repository root or Mercurial bundle will
- cause lookup to operate on that repository/bundle.
-
- This summary identifies the repository state using one or two
- parent hash identifiers, followed by a "+" if there are
- uncommitted changes in the working directory, a list of tags for
- this revision and a branch name for non-default branches.
- """
-
- if not repo and not source:
- raise util.Abort(_("There is no Mercurial repository here "
- "(.hg not found)"))
-
- hexfunc = ui.debugflag and hex or short
- default = not (num or id or branch or tags)
- output = []
-
- revs = []
- if source:
- source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
- repo = hg.repository(ui, source)
-
- if not repo.local():
- if not rev and revs:
- rev = revs[0]
- if not rev:
- rev = "tip"
- if num or branch or tags:
- raise util.Abort(
- "can't query remote revision number, branch, or tags")
- output = [hexfunc(repo.lookup(rev))]
- elif not rev:
- ctx = repo[None]
- parents = ctx.parents()
- changed = False
- if default or id or num:
- changed = ctx.files() + ctx.deleted()
- if default or id:
- output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
- (changed) and "+" or "")]
- if num:
- output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
- (changed) and "+" or ""))
- else:
- ctx = repo[rev]
- if default or id:
- output = [hexfunc(ctx.node())]
- if num:
- output.append(str(ctx.rev()))
-
- if repo.local() and default and not ui.quiet:
- b = encoding.tolocal(ctx.branch())
- if b != 'default':
- output.append("(%s)" % b)
-
- # multiple tags for a single parent separated by '/'
- t = "/".join(ctx.tags())
- if t:
- output.append(t)
-
- if branch:
- output.append(encoding.tolocal(ctx.branch()))
-
- if tags:
- output.extend(ctx.tags())
-
- ui.write("%s\n" % ' '.join(output))
-
-def import_(ui, repo, patch1, *patches, **opts):
- """import an ordered set of patches
-
- Import a list of patches and commit them individually.
-
- If there are outstanding changes in the working directory, import
- will abort unless given the -f/--force flag.
-
- You can import a patch straight from a mail message. Even patches
- as attachments work (to use the body part, it must have type
- text/plain or text/x-patch). From and Subject headers of email
- message are used as default committer and commit message. All
- text/plain body parts before first diff are added to commit
- message.
-
- If the imported patch was generated by hg export, user and
- description from patch override values from message headers and
- body. Values given on command line with -m/--message and -u/--user
- override these.
-
- If --exact is specified, import will set the working directory to
- the parent of each patch before applying it, and will abort if the
- resulting changeset has a different ID than the one recorded in
- the patch. This may happen due to character set problems or other
- deficiencies in the text patch format.
-
- With -s/--similarity, hg will attempt to discover renames and
- copies in the patch in the same way as 'addremove'.
-
- To read a patch from standard input, use "-" as the patch name. If
- a URL is specified, the patch will be downloaded from it.
- See 'hg help dates' for a list of formats valid for -d/--date.
- """
- patches = (patch1,) + patches
-
- date = opts.get('date')
- if date:
- opts['date'] = util.parsedate(date)
-
- try:
- sim = float(opts.get('similarity') or 0)
- except ValueError:
- raise util.Abort(_('similarity must be a number'))
- if sim < 0 or sim > 100:
- raise util.Abort(_('similarity must be between 0 and 100'))
-
- if opts.get('exact') or not opts.get('force'):
- cmdutil.bail_if_changed(repo)
-
- d = opts["base"]
- strip = opts["strip"]
- wlock = lock = None
- try:
- wlock = repo.wlock()
- lock = repo.lock()
- for p in patches:
- pf = os.path.join(d, p)
-
- if pf == '-':
- ui.status(_("applying patch from stdin\n"))
- pf = sys.stdin
- else:
- ui.status(_("applying %s\n") % p)
- pf = url.open(ui, pf)
- data = patch.extract(ui, pf)
- tmpname, message, user, date, branch, nodeid, p1, p2 = data
-
- if tmpname is None:
- raise util.Abort(_('no diffs found'))
-
- try:
- cmdline_message = cmdutil.logmessage(opts)
- if cmdline_message:
- # pickup the cmdline msg
- message = cmdline_message
- elif message:
- # pickup the patch msg
- message = message.strip()
- else:
- # launch the editor
- message = None
- ui.debug(_('message:\n%s\n') % message)
-
- wp = repo.parents()
- if opts.get('exact'):
- if not nodeid or not p1:
- raise util.Abort(_('not a Mercurial patch'))
- p1 = repo.lookup(p1)
- p2 = repo.lookup(p2 or hex(nullid))
-
- if p1 != wp[0].node():
- hg.clean(repo, p1)
- repo.dirstate.setparents(p1, p2)
- elif p2:
- try:
- p1 = repo.lookup(p1)
- p2 = repo.lookup(p2)
- if p1 == wp[0].node():
- repo.dirstate.setparents(p1, p2)
- except error.RepoError:
- pass
- if opts.get('exact') or opts.get('import_branch'):
- repo.dirstate.setbranch(branch or 'default')
-
- files = {}
- try:
- patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
- files=files, eolmode=None)
- finally:
- files = patch.updatedir(ui, repo, files, similarity=sim/100.)
- if not opts.get('no_commit'):
- m = cmdutil.matchfiles(repo, files or [])
- n = repo.commit(message, opts.get('user') or user,
- opts.get('date') or date, match=m,
- editor=cmdutil.commiteditor)
- if opts.get('exact'):
- if hex(n) != nodeid:
- repo.rollback()
- raise util.Abort(_('patch is damaged'
- ' or loses information'))
- # Force a dirstate write so that the next transaction
- # backups an up-do-date file.
- repo.dirstate.write()
- finally:
- os.unlink(tmpname)
- finally:
- release(lock, wlock)
-
-def incoming(ui, repo, source="default", **opts):
- """show new changesets found in source
-
- Show new changesets found in the specified path/URL or the default
- pull location. These are the changesets that would have been pulled
- if a pull at the time you issued this command.
-
- For remote repository, using --bundle avoids downloading the
- changesets twice if the incoming is followed by a pull.
-
- See pull for valid source format details.
- """
- limit = cmdutil.loglimit(opts)
- source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
- other = hg.repository(cmdutil.remoteui(repo, opts), source)
- ui.status(_('comparing with %s\n') % url.hidepassword(source))
- if revs:
- revs = [other.lookup(rev) for rev in revs]
- common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
- force=opts["force"])
- if not incoming:
- try:
- os.unlink(opts["bundle"])
- except:
- pass
- ui.status(_("no changes found\n"))
- return 1
-
- cleanup = None
- try:
- fname = opts["bundle"]
- if fname or not other.local():
- # create a bundle (uncompressed if other repo is not local)
-
- if revs is None and other.capable('changegroupsubset'):
- revs = rheads
-
- if revs is None:
- cg = other.changegroup(incoming, "incoming")
- else:
- cg = other.changegroupsubset(incoming, revs, 'incoming')
- bundletype = other.local() and "HG10BZ" or "HG10UN"
- fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
- # keep written bundle?
- if opts["bundle"]:
- cleanup = None
- if not other.local():
- # use the created uncompressed bundlerepo
- other = bundlerepo.bundlerepository(ui, repo.root, fname)
-
- o = other.changelog.nodesbetween(incoming, revs)[0]
- if opts.get('newest_first'):
- o.reverse()
- displayer = cmdutil.show_changeset(ui, other, opts)
- count = 0
- for n in o:
- if count >= limit:
- break
- parents = [p for p in other.changelog.parents(n) if p != nullid]
- if opts.get('no_merges') and len(parents) == 2:
- continue
- count += 1
- displayer.show(other[n])
- finally:
- if hasattr(other, 'close'):
- other.close()
- if cleanup:
- os.unlink(cleanup)
-
-def init(ui, dest=".", **opts):
- """create a new repository in the given directory
-
- Initialize a new repository in the given directory. If the given
- directory does not exist, it will be created.
-
- If no directory is given, the current directory is used.
-
- It is possible to specify an ssh:// URL as the destination.
- See 'hg help urls' for more information.
- """
- hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
-
-def locate(ui, repo, *pats, **opts):
- """locate files matching specific patterns
-
- Print files under Mercurial control in the working directory whose
- names match the given patterns.
-
- By default, this command searches all directories in the working
- directory. To search just the current directory and its
- subdirectories, use "--include .".
-
- If no patterns are given to match, this command prints the names
- of all files under Mercurial control in the working directory.
-
- If you want to feed the output of this command into the "xargs"
- command, use the -0 option to both this command and "xargs". This
- will avoid the problem of "xargs" treating single filenames that
- contain whitespace as multiple filenames.
- """
- end = opts.get('print0') and '\0' or '\n'
- rev = opts.get('rev') or None
-
- ret = 1
- m = cmdutil.match(repo, pats, opts, default='relglob')
- m.bad = lambda x,y: False
- for abs in repo[rev].walk(m):
- if not rev and abs not in repo.dirstate:
- continue
- if opts.get('fullpath'):
- ui.write(repo.wjoin(abs), end)
- else:
- ui.write(((pats and m.rel(abs)) or abs), end)
- ret = 0
-
- return ret
-
-def log(ui, repo, *pats, **opts):
- """show revision history of entire repository or files
-
- Print the revision history of the specified files or the entire
- project.
-
- File history is shown without following rename or copy history of
- files. Use -f/--follow with a filename to follow history across
- renames and copies. --follow without a filename will only show
- ancestors or descendants of the starting revision. --follow-first
- only follows the first parent of merge revisions.
-
- If no revision range is specified, the default is tip:0 unless
- --follow is set, in which case the working directory parent is
- used as the starting revision.
-
- See 'hg help dates' for a list of formats valid for -d/--date.
-
- By default this command prints revision number and changeset id,
- tags, non-trivial parents, user, date and time, and a summary for
- each commit. When the -v/--verbose switch is used, the list of
- changed files and full commit message are shown.
-
- NOTE: log -p/--patch may generate unexpected diff output for merge
- changesets, as it will only compare the merge changeset against
- its first parent. Also, only files different from BOTH parents
- will appear in files:.
- """
-
- get = util.cachefunc(lambda r: repo[r].changeset())
- changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
-
- limit = cmdutil.loglimit(opts)
- count = 0
-
- if opts.get('copies') and opts.get('rev'):
- endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
- else:
- endrev = len(repo)
- rcache = {}
- ncache = {}
- def getrenamed(fn, rev):
- '''looks up all renames for a file (up to endrev) the first
- time the file is given. It indexes on the changerev and only
- parses the manifest if linkrev != changerev.
- Returns rename info for fn at changerev rev.'''
- if fn not in rcache:
- rcache[fn] = {}
- ncache[fn] = {}
- fl = repo.file(fn)
- for i in fl:
- node = fl.node(i)
- lr = fl.linkrev(i)
- renamed = fl.renamed(node)
- rcache[fn][lr] = renamed
- if renamed:
- ncache[fn][node] = renamed
- if lr >= endrev:
- break
- if rev in rcache[fn]:
- return rcache[fn][rev]
-
- # If linkrev != rev (i.e. rev not found in rcache) fallback to
- # filectx logic.
-
- try:
- return repo[rev][fn].renamed()
- except error.LookupError:
- pass
- return None
-
- df = False
- if opts["date"]:
- df = util.matchdate(opts["date"])
-
- only_branches = opts.get('only_branch')
-
- displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
- for st, rev, fns in changeiter:
- if st == 'add':
- parents = [p for p in repo.changelog.parentrevs(rev)
- if p != nullrev]
- if opts.get('no_merges') and len(parents) == 2:
- continue
- if opts.get('only_merges') and len(parents) != 2:
- continue
-
- if only_branches:
- revbranch = get(rev)[5]['branch']
- if revbranch not in only_branches:
- continue
-
- if df:
- changes = get(rev)
- if not df(changes[2][0]):
- continue
-
- if opts.get('keyword'):
- changes = get(rev)
- miss = 0
- for k in [kw.lower() for kw in opts['keyword']]:
- if not (k in changes[1].lower() or
- k in changes[4].lower() or
- k in " ".join(changes[3]).lower()):
- miss = 1
- break
- if miss:
- continue
-
- if opts['user']:
- changes = get(rev)
- if not [k for k in opts['user'] if k in changes[1]]:
- continue
-
- copies = []
- if opts.get('copies') and rev:
- for fn in get(rev)[3]:
- rename = getrenamed(fn, rev)
- if rename:
- copies.append((fn, rename[0]))
- displayer.show(context.changectx(repo, rev), copies=copies)
- elif st == 'iter':
- if count == limit: break
- if displayer.flush(rev):
- count += 1
-
-def manifest(ui, repo, node=None, rev=None):
- """output the current or given revision of the project manifest
-
- Print a list of version controlled files for the given revision.
- If no revision is given, the first parent of the working directory
- is used, or the null revision if no revision is checked out.
-
- With -v, print file permissions, symlink and executable bits.
- With --debug, print file revision hashes.
- """
-
- if rev and node:
- raise util.Abort(_("please specify just one revision"))
-
- if not node:
- node = rev
-
- decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
- ctx = repo[node]
- for f in ctx:
- if ui.debugflag:
- ui.write("%40s " % hex(ctx.manifest()[f]))
- if ui.verbose:
- ui.write(decor[ctx.flags(f)])
- ui.write("%s\n" % f)
-
-def merge(ui, repo, node=None, **opts):
- """merge working directory with another revision
-
- The current working directory is updated with all changes made in
- the requested revision since the last common predecessor revision.
-
- Files that changed between either parent are marked as changed for
- the next commit and a commit must be performed before any further
- updates to the repository are allowed. The next commit will have
- two parents.
-
- If no revision is specified, the working directory's parent is a
- head revision, and the current branch contains exactly one other
- head, the other head is merged with by default. Otherwise, an
- explicit revision with which to merge with must be provided.
- """
-
- if opts.get('rev') and node:
- raise util.Abort(_("please specify just one revision"))
- if not node:
- node = opts.get('rev')
-
- if not node:
- branch = repo.changectx(None).branch()
- bheads = repo.branchheads(branch)
- if len(bheads) > 2:
- raise util.Abort(_("branch '%s' has %d heads - "
- "please merge with an explicit rev") %
- (branch, len(bheads)))
-
- parent = repo.dirstate.parents()[0]
- if len(bheads) == 1:
- if len(repo.heads()) > 1:
- raise util.Abort(_("branch '%s' has one head - "
- "please merge with an explicit rev") %
- branch)
- msg = _('there is nothing to merge')
- if parent != repo.lookup(repo[None].branch()):
- msg = _('%s - use "hg update" instead') % msg
- raise util.Abort(msg)
-
- if parent not in bheads:
- raise util.Abort(_('working dir not at a head rev - '
- 'use "hg update" or merge with an explicit rev'))
- node = parent == bheads[0] and bheads[-1] or bheads[0]
-
- if opts.get('preview'):
- p1 = repo['.']
- p2 = repo[node]
- common = p1.ancestor(p2)
- roots, heads = [common.node()], [p2.node()]
- displayer = cmdutil.show_changeset(ui, repo, opts)
- for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
- displayer.show(repo[node])
- return 0
-
- return hg.merge(repo, node, force=opts.get('force'))
-
-def outgoing(ui, repo, dest=None, **opts):
- """show changesets not found in destination
-
- Show changesets not found in the specified destination repository
- or the default push location. These are the changesets that would
- be pushed if a push was requested.
-
- See pull for valid destination format details.
- """
- limit = cmdutil.loglimit(opts)
- dest, revs, checkout = hg.parseurl(
- ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
- if revs:
- revs = [repo.lookup(rev) for rev in revs]
-
- other = hg.repository(cmdutil.remoteui(repo, opts), dest)
- ui.status(_('comparing with %s\n') % url.hidepassword(dest))
- o = repo.findoutgoing(other, force=opts.get('force'))
- if not o:
- ui.status(_("no changes found\n"))
- return 1
- o = repo.changelog.nodesbetween(o, revs)[0]
- if opts.get('newest_first'):
- o.reverse()
- displayer = cmdutil.show_changeset(ui, repo, opts)
- count = 0
- for n in o:
- if count >= limit:
- break
- parents = [p for p in repo.changelog.parents(n) if p != nullid]
- if opts.get('no_merges') and len(parents) == 2:
- continue
- count += 1
- displayer.show(repo[n])
-
-def parents(ui, repo, file_=None, **opts):
- """show the parents of the working directory or revision
-
- Print the working directory's parent revisions. If a revision is
- given via -r/--rev, the parent of that revision will be printed.
- If a file argument is given, the revision in which the file was
- last changed (before the working directory revision or the
- argument to --rev if given) is printed.
- """
- rev = opts.get('rev')
- if rev:
- ctx = repo[rev]
- else:
- ctx = repo[None]
-
- if file_:
- m = cmdutil.match(repo, (file_,), opts)
- if m.anypats() or len(m.files()) != 1:
- raise util.Abort(_('can only specify an explicit filename'))
- file_ = m.files()[0]
- filenodes = []
- for cp in ctx.parents():
- if not cp:
- continue
- try:
- filenodes.append(cp.filenode(file_))
- except error.LookupError:
- pass
- if not filenodes:
- raise util.Abort(_("'%s' not found in manifest!") % file_)
- fl = repo.file(file_)
- p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
- else:
- p = [cp.node() for cp in ctx.parents()]
-
- displayer = cmdutil.show_changeset(ui, repo, opts)
- for n in p:
- if n != nullid:
- displayer.show(repo[n])
-
-def paths(ui, repo, search=None):
- """show aliases for remote repositories
-
- Show definition of symbolic path name NAME. If no name is given,
- show definition of all available names.
-
- Path names are defined in the [paths] section of /etc/mercurial/hgrc
- and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
-
- See 'hg help urls' for more information.
- """
- if search:
- for name, path in ui.configitems("paths"):
- if name == search:
- ui.write("%s\n" % url.hidepassword(path))
- return
- ui.warn(_("not found!\n"))
- return 1
- else:
- for name, path in ui.configitems("paths"):
- ui.write("%s = %s\n" % (name, url.hidepassword(path)))
-
-def postincoming(ui, repo, modheads, optupdate, checkout):
- if modheads == 0:
- return
- if optupdate:
- if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
- return hg.update(repo, checkout)
- else:
- ui.status(_("not updating, since new heads added\n"))
- if modheads > 1:
- ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
- else:
- ui.status(_("(run 'hg update' to get a working copy)\n"))
-
-def pull(ui, repo, source="default", **opts):
- """pull changes from the specified source
-
- Pull changes from a remote repository to a local one.
-
- This finds all changes from the repository at the specified path
- or URL and adds them to a local repository (the current one unless
- -R is specified). By default, this does not update the copy of the
- project in the working directory.
-
- Use hg incoming if you want to see what would have been added by a
- pull at the time you issued this command. If you then decide to
- added those changes to the repository, you should use pull -r X
- where X is the last changeset listed by hg incoming.
-
- If SOURCE is omitted, the 'default' path will be used.
- See 'hg help urls' for more information.
- """
- source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
- other = hg.repository(cmdutil.remoteui(repo, opts), source)
- ui.status(_('pulling from %s\n') % url.hidepassword(source))
- if revs:
- try:
- revs = [other.lookup(rev) for rev in revs]
- except error.CapabilityError:
- err = _("Other repository doesn't support revision lookup, "
- "so a rev cannot be specified.")
- raise util.Abort(err)
-
- modheads = repo.pull(other, heads=revs, force=opts.get('force'))
- return postincoming(ui, repo, modheads, opts.get('update'), checkout)
-
-def push(ui, repo, dest=None, **opts):
- """push changes to the specified destination
-
- Push changes from the local repository to the given destination.
-
- This is the symmetrical operation for pull. It moves changes from
- the current repository to a different one. If the destination is
- local this is identical to a pull in that directory from the
- current one.
-
- By default, push will refuse to run if it detects the result would
- increase the number of remote heads. This generally indicates the
- user forgot to pull and merge before pushing.
-
- If -r/--rev is used, the named revision and all its ancestors will
- be pushed to the remote repository.
-
- Please see 'hg help urls' for important details about ssh://
- URLs. If DESTINATION is omitted, a default path will be used.
- """
- dest, revs, checkout = hg.parseurl(
- ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
- other = hg.repository(cmdutil.remoteui(repo, opts), dest)
- ui.status(_('pushing to %s\n') % url.hidepassword(dest))
- if revs:
- revs = [repo.lookup(rev) for rev in revs]
-
- # push subrepos depth-first for coherent ordering
- c = repo['']
- subs = c.substate # only repos that are committed
- for s in sorted(subs):
- c.sub(s).push(opts.get('force'))
-
- r = repo.push(other, opts.get('force'), revs=revs)
- return r == 0
-
-def recover(ui, repo):
- """roll back an interrupted transaction
-
- Recover from an interrupted commit or pull.
-
- This command tries to fix the repository status after an
- interrupted operation. It should only be necessary when Mercurial
- suggests it.
- """
- if repo.recover():
- return hg.verify(repo)
- return 1
-
-def remove(ui, repo, *pats, **opts):
- """remove the specified files on the next commit
-
- Schedule the indicated files for removal from the repository.
-
- This only removes files from the current branch, not from the
- entire project history. -A/--after can be used to remove only
- files that have already been deleted, -f/--force can be used to
- force deletion, and -Af can be used to remove files from the next
- revision without deleting them from the working directory.
-
- The following table details the behavior of remove for different
- file states (columns) and option combinations (rows). The file
- states are Added [A], Clean [C], Modified [M] and Missing [!] (as
- reported by hg status). The actions are Warn, Remove (from branch)
- and Delete (from disk)::
-
- A C M !
- none W RD W R
- -f R RD RD R
- -A W W W R
- -Af R R R R
-
- This command schedules the files to be removed at the next commit.
- To undo a remove before that, see hg revert.
- """
-
- after, force = opts.get('after'), opts.get('force')
- if not pats and not after:
- raise util.Abort(_('no files specified'))
-
- m = cmdutil.match(repo, pats, opts)
- s = repo.status(match=m, clean=True)
- modified, added, deleted, clean = s[0], s[1], s[3], s[6]
-
- for f in m.files():
- if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
- ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
-
- def warn(files, reason):
- for f in files:
- ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
- % (m.rel(f), reason))
-
- if force:
- remove, forget = modified + deleted + clean, added
- elif after:
- remove, forget = deleted, []
- warn(modified + added + clean, _('still exists'))
- else:
- remove, forget = deleted + clean, []
- warn(modified, _('is modified'))
- warn(added, _('has been marked for add'))
-
- for f in sorted(remove + forget):
- if ui.verbose or not m.exact(f):
- ui.status(_('removing %s\n') % m.rel(f))
-
- repo.forget(forget)
- repo.remove(remove, unlink=not after)
-
-def rename(ui, repo, *pats, **opts):
- """rename files; equivalent of copy + remove
-
- Mark dest as copies of sources; mark sources for deletion. If dest
- is a directory, copies are put in that directory. If dest is a
- file, there can only be one source.
-
- By default, this command copies the contents of files as they
- exist in the working directory. If invoked with -A/--after, the
- operation is recorded, but no copying is performed.
-
- This command takes effect at the next commit. To undo a rename
- before that, see hg revert.
- """
- wlock = repo.wlock(False)
- try:
- return cmdutil.copy(ui, repo, pats, opts, rename=True)
- finally:
- wlock.release()
-
-def resolve(ui, repo, *pats, **opts):
- """retry file merges from a merge or update
-
- This command will cleanly retry unresolved file merges using file
- revisions preserved from the last update or merge. To attempt to
- resolve all unresolved files, use the -a/--all switch.
-
- If a conflict is resolved manually, please note that the changes
- will be overwritten if the merge is retried with resolve. The
- -m/--mark switch should be used to mark the file as resolved.
-
- This command also allows listing resolved files and manually
- indicating whether or not files are resolved. All files must be
- marked as resolved before a commit is permitted.
-
- The codes used to show the status of files are::
-
- U = unresolved
- R = resolved
- """
-
- all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()]
-
- if (show and (mark or unmark)) or (mark and unmark):
- raise util.Abort(_("too many options specified"))
- if pats and all:
- raise util.Abort(_("can't specify --all and patterns"))
- if not (all or pats or show or mark or unmark):
- raise util.Abort(_('no files or directories specified; '
- 'use --all to remerge all files'))
-
- ms = merge_.mergestate(repo)
- m = cmdutil.match(repo, pats, opts)
-
- for f in ms:
- if m(f):
- if show:
- ui.write("%s %s\n" % (ms[f].upper(), f))
- elif mark:
- ms.mark(f, "r")
- elif unmark:
- ms.mark(f, "u")
- else:
- wctx = repo[None]
- mctx = wctx.parents()[-1]
-
- # backup pre-resolve (merge uses .orig for its own purposes)
- a = repo.wjoin(f)
- util.copyfile(a, a + ".resolve")
-
- # resolve file
- ms.resolve(f, wctx, mctx)
-
- # replace filemerge's .orig file with our resolve file
- util.rename(a + ".resolve", a + ".orig")
-
-def revert(ui, repo, *pats, **opts):
- """restore individual files or directories to an earlier state
-
- (Use update -r to check out earlier revisions, revert does not
- change the working directory parents.)
-
- With no revision specified, revert the named files or directories
- to the contents they had in the parent of the working directory.
- This restores the contents of the affected files to an unmodified
- state and unschedules adds, removes, copies, and renames. If the
- working directory has two parents, you must explicitly specify the
- revision to revert to.
-
- Using the -r/--rev option, revert the given files or directories
- to their contents as of a specific revision. This can be helpful
- to "roll back" some or all of an earlier change. See 'hg help
- dates' for a list of formats valid for -d/--date.
-
- Revert modifies the working directory. It does not commit any
- changes, or change the parent of the working directory. If you
- revert to a revision other than the parent of the working
- directory, the reverted files will thus appear modified
- afterwards.
-
- If a file has been deleted, it is restored. If the executable mode
- of a file was changed, it is reset.
-
- If names are given, all files matching the names are reverted.
- If no arguments are given, no files are reverted.
-
- Modified files are saved with a .orig suffix before reverting.
- To disable these backups, use --no-backup.
- """
-
- if opts["date"]:
- if opts["rev"]:
- raise util.Abort(_("you can't specify a revision and a date"))
- opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
-
- if not pats and not opts.get('all'):
- raise util.Abort(_('no files or directories specified; '
- 'use --all to revert the whole repo'))
-
- parent, p2 = repo.dirstate.parents()
- if not opts.get('rev') and p2 != nullid:
- raise util.Abort(_('uncommitted merge - please provide a '
- 'specific revision'))
- ctx = repo[opts.get('rev')]
- node = ctx.node()
- mf = ctx.manifest()
- if node == parent:
- pmf = mf
- else:
- pmf = None
-
- # need all matching names in dirstate and manifest of target rev,
- # so have to walk both. do not print errors if files exist in one
- # but not other.
-
- names = {}
-
- wlock = repo.wlock()
- try:
- # walk dirstate.
-
- m = cmdutil.match(repo, pats, opts)
- m.bad = lambda x,y: False
- for abs in repo.walk(m):
- names[abs] = m.rel(abs), m.exact(abs)
-
- # walk target manifest.
-
- def badfn(path, msg):
- if path in names:
- return
- path_ = path + '/'
- for f in names:
- if f.startswith(path_):
- return
- ui.warn("%s: %s\n" % (m.rel(path), msg))
-
- m = cmdutil.match(repo, pats, opts)
- m.bad = badfn
- for abs in repo[node].walk(m):
- if abs not in names:
- names[abs] = m.rel(abs), m.exact(abs)
-
- m = cmdutil.matchfiles(repo, names)
- changes = repo.status(match=m)[:4]
- modified, added, removed, deleted = map(set, changes)
-
- # if f is a rename, also revert the source
- cwd = repo.getcwd()
- for f in added:
- src = repo.dirstate.copied(f)
- if src and src not in names and repo.dirstate[src] == 'r':
- removed.add(src)
- names[src] = (repo.pathto(src, cwd), True)
-
- def removeforget(abs):
- if repo.dirstate[abs] == 'a':
- return _('forgetting %s\n')
- return _('removing %s\n')
-
- revert = ([], _('reverting %s\n'))
- add = ([], _('adding %s\n'))
- remove = ([], removeforget)
- undelete = ([], _('undeleting %s\n'))
-
- disptable = (
- # dispatch table:
- # file state
- # action if in target manifest
- # action if not in target manifest
- # make backup if in target manifest
- # make backup if not in target manifest
- (modified, revert, remove, True, True),
- (added, revert, remove, True, False),
- (removed, undelete, None, False, False),
- (deleted, revert, remove, False, False),
- )
-
- for abs, (rel, exact) in sorted(names.items()):
- mfentry = mf.get(abs)
- target = repo.wjoin(abs)
- def handle(xlist, dobackup):
- xlist[0].append(abs)
- if dobackup and not opts.get('no_backup') and util.lexists(target):
- bakname = "%s.orig" % rel
- ui.note(_('saving current version of %s as %s\n') %
- (rel, bakname))
- if not opts.get('dry_run'):
- util.copyfile(target, bakname)
- if ui.verbose or not exact:
- msg = xlist[1]
- if not isinstance(msg, basestring):
- msg = msg(abs)
- ui.status(msg % rel)
- for table, hitlist, misslist, backuphit, backupmiss in disptable:
- if abs not in table: continue
- # file has changed in dirstate
- if mfentry:
- handle(hitlist, backuphit)
- elif misslist is not None:
- handle(misslist, backupmiss)
- break
- else:
- if abs not in repo.dirstate:
- if mfentry:
- handle(add, True)
- elif exact:
- ui.warn(_('file not managed: %s\n') % rel)
- continue
- # file has not changed in dirstate
- if node == parent:
- if exact: ui.warn(_('no changes needed to %s\n') % rel)
- continue
- if pmf is None:
- # only need parent manifest in this unlikely case,
- # so do not read by default
- pmf = repo[parent].manifest()
- if abs in pmf:
- if mfentry:
- # if version of file is same in parent and target
- # manifests, do nothing
- if (pmf[abs] != mfentry or
- pmf.flags(abs) != mf.flags(abs)):
- handle(revert, False)
- else:
- handle(remove, False)
-
- if not opts.get('dry_run'):
- def checkout(f):
- fc = ctx[f]
- repo.wwrite(f, fc.data(), fc.flags())
-
- audit_path = util.path_auditor(repo.root)
- for f in remove[0]:
- if repo.dirstate[f] == 'a':
- repo.dirstate.forget(f)
- continue
- audit_path(f)
- try:
- util.unlink(repo.wjoin(f))
- except OSError:
- pass
- repo.dirstate.remove(f)
-
- normal = None
- if node == parent:
- # We're reverting to our parent. If possible, we'd like status
- # to report the file as clean. We have to use normallookup for
- # merges to avoid losing information about merged/dirty files.
- if p2 != nullid:
- normal = repo.dirstate.normallookup
- else:
- normal = repo.dirstate.normal
- for f in revert[0]:
- checkout(f)
- if normal:
- normal(f)
-
- for f in add[0]:
- checkout(f)
- repo.dirstate.add(f)
-
- normal = repo.dirstate.normallookup
- if node == parent and p2 == nullid:
- normal = repo.dirstate.normal
- for f in undelete[0]:
- checkout(f)
- normal(f)
-
- finally:
- wlock.release()
-
-def rollback(ui, repo):
- """roll back the last transaction
-
- This command should be used with care. There is only one level of
- rollback, and there is no way to undo a rollback. It will also
- restore the dirstate at the time of the last transaction, losing
- any dirstate changes since that time. This command does not alter
- the working directory.
-
- Transactions are used to encapsulate the effects of all commands
- that create new changesets or propagate existing changesets into a
- repository. For example, the following commands are transactional,
- and their effects can be rolled back::
-
- commit
- import
- pull
- push (with this repository as destination)
- unbundle
-
- This command is not intended for use on public repositories. Once
- changes are visible for pull by other users, rolling a transaction
- back locally is ineffective (someone else may already have pulled
- the changes). Furthermore, a race is possible with readers of the
- repository; for example an in-progress pull from the repository
- may fail if a rollback is performed.
- """
- repo.rollback()
-
-def root(ui, repo):
- """print the root (top) of the current working directory
-
- Print the root directory of the current repository.
- """
- ui.write(repo.root + "\n")
-
-def serve(ui, repo, **opts):
- """export the repository via HTTP
-
- Start a local HTTP repository browser and pull server.
-
- By default, the server logs accesses to stdout and errors to
- stderr. Use the -A/--accesslog and -E/--errorlog options to log to
- files.
- """
-
- if opts["stdio"]:
- if repo is None:
- raise error.RepoError(_("There is no Mercurial repository here"
- " (.hg not found)"))
- s = sshserver.sshserver(ui, repo)
- s.serve_forever()
-
- baseui = repo and repo.baseui or ui
- optlist = ("name templates style address port prefix ipv6"
- " accesslog errorlog webdir_conf certificate encoding")
- for o in optlist.split():
- if opts.get(o, None):
- baseui.setconfig("web", o, str(opts[o]))
- if (repo is not None) and (repo.ui != baseui):
- repo.ui.setconfig("web", o, str(opts[o]))
-
- if repo is None and not ui.config("web", "webdir_conf"):
- raise error.RepoError(_("There is no Mercurial repository here"
- " (.hg not found)"))
-
- class service(object):
- def init(self):
- util.set_signal_handler()
- self.httpd = server.create_server(baseui, repo)
-
- if not ui.verbose: return
-
- if self.httpd.prefix:
- prefix = self.httpd.prefix.strip('/') + '/'
- else:
- prefix = ''
-
- port = ':%d' % self.httpd.port
- if port == ':80':
- port = ''
-
- bindaddr = self.httpd.addr
- if bindaddr == '0.0.0.0':
- bindaddr = '*'
- elif ':' in bindaddr: # IPv6
- bindaddr = '[%s]' % bindaddr
-
- fqaddr = self.httpd.fqaddr
- if ':' in fqaddr:
- fqaddr = '[%s]' % fqaddr
- ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
- (fqaddr, port, prefix, bindaddr, self.httpd.port))
-
- def run(self):
- self.httpd.serve_forever()
-
- service = service()
-
- cmdutil.service(opts, initfn=service.init, runfn=service.run)
-
-def status(ui, repo, *pats, **opts):
- """show changed files in the working directory
-
- Show status of files in the repository. If names are given, only
- files that match are shown. Files that are clean or ignored or
- the source of a copy/move operation, are not listed unless
- -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
- Unless options described with "show only ..." are given, the
- options -mardu are used.
-
- Option -q/--quiet hides untracked (unknown and ignored) files
- unless explicitly requested with -u/--unknown or -i/--ignored.
-
- NOTE: status may appear to disagree with diff if permissions have
- changed or a merge has occurred. The standard diff format does not
- report permission changes and diff only reports changes relative
- to one merge parent.
-
- If one revision is given, it is used as the base revision.
- If two revisions are given, the differences between them are
- shown.
-
- The codes used to show the status of files are::
-
- M = modified
- A = added
- R = removed
- C = clean
- ! = missing (deleted by non-hg command, but still tracked)
- ? = not tracked
- I = ignored
- = origin of the previous file listed as A (added)
- """
-
- node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
- cwd = (pats and repo.getcwd()) or ''
- end = opts.get('print0') and '\0' or '\n'
- copy = {}
- states = 'modified added removed deleted unknown ignored clean'.split()
- show = [k for k in states if opts.get(k)]
- if opts.get('all'):
- show += ui.quiet and (states[:4] + ['clean']) or states
- if not show:
- show = ui.quiet and states[:4] or states[:5]
-
- stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
- 'ignored' in show, 'clean' in show, 'unknown' in show)
- changestates = zip(states, 'MAR!?IC', stat)
-
- if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
- ctxn = repo[nullid]
- ctx1 = repo[node1]
- ctx2 = repo[node2]
- added = stat[1]
- if node2 is None:
- added = stat[0] + stat[1] # merged?
-
- for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
- if k in added:
- copy[k] = v
- elif v in added:
- copy[v] = k
-
- for state, char, files in changestates:
- if state in show:
- format = "%s %%s%s" % (char, end)
- if opts.get('no_status'):
- format = "%%s%s" % end
-
- for f in files:
- ui.write(format % repo.pathto(f, cwd))
- if f in copy:
- ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
-
-def tag(ui, repo, name1, *names, **opts):
- """add one or more tags for the current or given revision
-
- Name a particular revision using <name>.
-
- Tags are used to name particular revisions of the repository and are
- very useful to compare different revisions, to go back to significant
- earlier versions or to mark branch points as releases, etc.
-
- If no revision is given, the parent of the working directory is
- used, or tip if no revision is checked out.
-
- To facilitate version control, distribution, and merging of tags,
- they are stored as a file named ".hgtags" which is managed
- similarly to other project files and can be hand-edited if
- necessary. The file '.hg/localtags' is used for local tags (not
- shared among repositories).
-
- See 'hg help dates' for a list of formats valid for -d/--date.
- """
-
- rev_ = "."
- names = (name1,) + names
- if len(names) != len(set(names)):
- raise util.Abort(_('tag names must be unique'))
- for n in names:
- if n in ['tip', '.', 'null']:
- raise util.Abort(_('the name \'%s\' is reserved') % n)
- if opts.get('rev') and opts.get('remove'):
- raise util.Abort(_("--rev and --remove are incompatible"))
- if opts.get('rev'):
- rev_ = opts['rev']
- message = opts.get('message')
- if opts.get('remove'):
- expectedtype = opts.get('local') and 'local' or 'global'
- for n in names:
- if not repo.tagtype(n):
- raise util.Abort(_('tag \'%s\' does not exist') % n)
- if repo.tagtype(n) != expectedtype:
- if expectedtype == 'global':
- raise util.Abort(_('tag \'%s\' is not a global tag') % n)
- else:
- raise util.Abort(_('tag \'%s\' is not a local tag') % n)
- rev_ = nullid
- if not message:
- # we don't translate commit messages
- message = 'Removed tag %s' % ', '.join(names)
- elif not opts.get('force'):
- for n in names:
- if n in repo.tags():
- raise util.Abort(_('tag \'%s\' already exists '
- '(use -f to force)') % n)
- if not rev_ and repo.dirstate.parents()[1] != nullid:
- raise util.Abort(_('uncommitted merge - please provide a '
- 'specific revision'))
- r = repo[rev_].node()
-
- if not message:
- # we don't translate commit messages
- message = ('Added tag %s for changeset %s' %
- (', '.join(names), short(r)))
-
- date = opts.get('date')
- if date:
- date = util.parsedate(date)
-
- repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
-
-def tags(ui, repo):
- """list repository tags
-
- This lists both regular and local tags. When the -v/--verbose
- switch is used, a third column "local" is printed for local tags.
- """
-
- hexfunc = ui.debugflag and hex or short
- tagtype = ""
-
- for t, n in reversed(repo.tagslist()):
- if ui.quiet:
- ui.write("%s\n" % t)
- continue
-
- try:
- hn = hexfunc(n)
- r = "%5d:%s" % (repo.changelog.rev(n), hn)
- except error.LookupError:
- r = " ?:%s" % hn
- else:
- spaces = " " * (30 - encoding.colwidth(t))
- if ui.verbose:
- if repo.tagtype(t) == 'local':
- tagtype = " local"
- else:
- tagtype = ""
- ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
-
-def tip(ui, repo, **opts):
- """show the tip revision
-
- The tip revision (usually just called the tip) is the changeset
- most recently added to the repository (and therefore the most
- recently changed head).
-
- If you have just made a commit, that commit will be the tip. If
- you have just pulled changes from another repository, the tip of
- that repository becomes the current tip. The "tip" tag is special
- and cannot be renamed or assigned to a different changeset.
- """
- cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1])
-
-def unbundle(ui, repo, fname1, *fnames, **opts):
- """apply one or more changegroup files
-
- Apply one or more compressed changegroup files generated by the
- bundle command.
- """
- fnames = (fname1,) + fnames
-
- lock = repo.lock()
- try:
- for fname in fnames:
- f = url.open(ui, fname)
- gen = changegroup.readbundle(f, fname)
- modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
- finally:
- lock.release()
-
- return postincoming(ui, repo, modheads, opts.get('update'), None)
-
-def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
- """update working directory
-
- Update the repository's working directory to the specified
- revision, or the tip of the current branch if none is specified.
- Use null as the revision to remove the working copy (like 'hg
- clone -U').
-
- When the working directory contains no uncommitted changes, it
- will be replaced by the state of the requested revision from the
- repository. When the requested revision is on a different branch,
- the working directory will additionally be switched to that
- branch.
-
- When there are uncommitted changes, use option -C/--clean to
- discard them, forcibly replacing the state of the working
- directory with the requested revision. Alternately, use -c/--check
- to abort.
-
- When there are uncommitted changes and option -C/--clean is not
- used, and the parent revision and requested revision are on the
- same branch, and one of them is an ancestor of the other, then the
- new working directory will contain the requested revision merged
- with the uncommitted changes. Otherwise, the update will fail with
- a suggestion to use 'merge' or 'update -C' instead.
-
- If you want to update just one file to an older revision, use
- revert.
-
- See 'hg help dates' for a list of formats valid for -d/--date.
- """
- if rev and node:
- raise util.Abort(_("please specify just one revision"))
-
- if not rev:
- rev = node
-
- if not clean and check:
- # we could use dirty() but we can ignore merge and branch trivia
- c = repo[None]
- if c.modified() or c.added() or c.removed():
- raise util.Abort(_("uncommitted local changes"))
-
- if date:
- if rev:
- raise util.Abort(_("you can't specify a revision and a date"))
- rev = cmdutil.finddate(ui, repo, date)
-
- if clean or check:
- return hg.clean(repo, rev)
- else:
- return hg.update(repo, rev)
-
-def verify(ui, repo):
- """verify the integrity of the repository
-
- Verify the integrity of the current repository.
-
- This will perform an extensive check of the repository's
- integrity, validating the hashes and checksums of each entry in
- the changelog, manifest, and tracked files, as well as the
- integrity of their crosslinks and indices.
- """
- return hg.verify(repo)
-
-def version_(ui):
- """output version and copyright information"""
- ui.write(_("Mercurial Distributed SCM (version %s)\n")
- % util.version())
- ui.status(_(
- "\nCopyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others\n"
- "This is free software; see the source for copying conditions. "
- "There is NO\nwarranty; "
- "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
- ))
-
-# Command options and aliases are listed here, alphabetically
-
-globalopts = [
- ('R', 'repository', '',
- _('repository root directory or symbolic path name')),
- ('', 'cwd', '', _('change working directory')),
- ('y', 'noninteractive', None,
- _('do not prompt, assume \'yes\' for any required answers')),
- ('q', 'quiet', None, _('suppress output')),
- ('v', 'verbose', None, _('enable additional output')),
- ('', 'config', [], _('set/override config option')),
- ('', 'debug', None, _('enable debugging output')),
- ('', 'debugger', None, _('start debugger')),
- ('', 'encoding', encoding.encoding, _('set the charset encoding')),
- ('', 'encodingmode', encoding.encodingmode,
- _('set the charset encoding mode')),
- ('', 'traceback', None, _('print traceback on exception')),
- ('', 'time', None, _('time how long the command takes')),
- ('', 'profile', None, _('print command execution profile')),
- ('', 'version', None, _('output version information and exit')),
- ('h', 'help', None, _('display help and exit')),
-]
-
-dryrunopts = [('n', 'dry-run', None,
- _('do not perform actions, just print output'))]
-
-remoteopts = [
- ('e', 'ssh', '', _('specify ssh command to use')),
- ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
-]
-
-walkopts = [
- ('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
-]
-
-commitopts = [
- ('m', 'message', '', _('use <text> as commit message')),
- ('l', 'logfile', '', _('read commit message from <file>')),
-]
-
-commitopts2 = [
- ('d', 'date', '', _('record datecode as commit date')),
- ('u', 'user', '', _('record the specified user as committer')),
-]
-
-templateopts = [
- ('', 'style', '', _('display using template map file')),
- ('', 'template', '', _('display with template')),
-]
-
-logopts = [
- ('p', 'patch', None, _('show patch')),
- ('g', 'git', None, _('use git extended diff format')),
- ('l', 'limit', '', _('limit number of changes displayed')),
- ('M', 'no-merges', None, _('do not show merges')),
-] + templateopts
-
-diffopts = [
- ('a', 'text', None, _('treat all files as text')),
- ('g', 'git', None, _('use git extended diff format')),
- ('', 'nodates', None, _("don't include dates in diff headers"))
-]
-
-diffopts2 = [
- ('p', 'show-function', None, _('show which function each change is in')),
- ('w', 'ignore-all-space', None,
- _('ignore white space when comparing lines')),
- ('b', 'ignore-space-change', None,
- _('ignore changes in the amount of white space')),
- ('B', 'ignore-blank-lines', None,
- _('ignore changes whose lines are all blank')),
- ('U', 'unified', '', _('number of lines of context to show'))
-]
-
-similarityopts = [
- ('s', 'similarity', '',
- _('guess renamed files by similarity (0<=s<=100)'))
-]
-
-table = {
- "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
- "addremove":
- (addremove, similarityopts + walkopts + dryrunopts,
- _('[OPTION]... [FILE]...')),
- "^annotate|blame":
- (annotate,
- [('r', 'rev', '', _('annotate the specified revision')),
- ('f', 'follow', None, _('follow file copies and renames')),
- ('a', 'text', None, _('treat all files as text')),
- ('u', 'user', None, _('list the author (long with -v)')),
- ('d', 'date', None, _('list the date (short with -q)')),
- ('n', 'number', None, _('list the revision number (default)')),
- ('c', 'changeset', None, _('list the changeset')),
- ('l', 'line-number', None,
- _('show line number at the first appearance'))
- ] + walkopts,
- _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
- "archive":
- (archive,
- [('', 'no-decode', None, _('do not pass files through decoders')),
- ('p', 'prefix', '', _('directory prefix for files in archive')),
- ('r', 'rev', '', _('revision to distribute')),
- ('t', 'type', '', _('type of distribution to create')),
- ] + walkopts,
- _('[OPTION]... DEST')),
- "backout":
- (backout,
- [('', 'merge', None,
- _('merge with old dirstate parent after backout')),
- ('', 'parent', '', _('parent to choose when backing out merge')),
- ('r', 'rev', '', _('revision to backout')),
- ] + walkopts + commitopts + commitopts2,
- _('[OPTION]... [-r] REV')),
- "bisect":
- (bisect,
- [('r', 'reset', False, _('reset bisect state')),
- ('g', 'good', False, _('mark changeset good')),
- ('b', 'bad', False, _('mark changeset bad')),
- ('s', 'skip', False, _('skip testing changeset')),
- ('c', 'command', '', _('use command to check changeset state')),
- ('U', 'noupdate', False, _('do not update to target'))],
- _("[-gbsr] [-c CMD] [REV]")),
- "branch":
- (branch,
- [('f', 'force', None,
- _('set branch name even if it shadows an existing branch')),
- ('C', 'clean', None, _('reset branch name to parent branch name'))],
- _('[-fC] [NAME]')),
- "branches":
- (branches,
- [('a', 'active', False,
- _('show only branches that have unmerged heads')),
- ('c', 'closed', False,
- _('show normal and closed branches'))],
- _('[-a]')),
- "bundle":
- (bundle,
- [('f', 'force', None,
- _('run even when remote repository is unrelated')),
- ('r', 'rev', [],
- _('a changeset up to which you would like to bundle')),
- ('', 'base', [],
- _('a base changeset to specify instead of a destination')),
- ('a', 'all', None, _('bundle all changesets in the repository')),
- ('t', 'type', 'bzip2', _('bundle compression type to use')),
- ] + remoteopts,
- _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
- "cat":
- (cat,
- [('o', 'output', '', _('print output to file with formatted name')),
- ('r', 'rev', '', _('print the given revision')),
- ('', 'decode', None, _('apply any matching decode filter')),
- ] + walkopts,
- _('[OPTION]... FILE...')),
- "^clone":
- (clone,
- [('U', 'noupdate', None,
- _('the clone will only contain a repository (no working copy)')),
- ('r', 'rev', [],
- _('a changeset you would like to have after cloning')),
- ('', 'pull', None, _('use pull protocol to copy metadata')),
- ('', 'uncompressed', None,
- _('use uncompressed transfer (fast over LAN)')),
- ] + remoteopts,
- _('[OPTION]... SOURCE [DEST]')),
- "^commit|ci":
- (commit,
- [('A', 'addremove', None,
- _('mark new/missing files as added/removed before committing')),
- ('', 'close-branch', None,
- _('mark a branch as closed, hiding it from the branch list')),
- ] + walkopts + commitopts + commitopts2,
- _('[OPTION]... [FILE]...')),
- "copy|cp":
- (copy,
- [('A', 'after', None, _('record a copy that has already occurred')),
- ('f', 'force', None,
- _('forcibly copy over an existing managed file')),
- ] + walkopts + dryrunopts,
- _('[OPTION]... [SOURCE]... DEST')),
- "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
- "debugcheckstate": (debugcheckstate, []),
- "debugcommands": (debugcommands, [], _('[COMMAND]')),
- "debugcomplete":
- (debugcomplete,
- [('o', 'options', None, _('show the command options'))],
- _('[-o] CMD')),
- "debugdate":
- (debugdate,
- [('e', 'extended', None, _('try extended date formats'))],
- _('[-e] DATE [RANGE]')),
- "debugdata": (debugdata, [], _('FILE REV')),
- "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
- "debugindex": (debugindex, [], _('FILE')),
- "debugindexdot": (debugindexdot, [], _('FILE')),
- "debuginstall": (debuginstall, []),
- "debugrebuildstate":
- (debugrebuildstate,
- [('r', 'rev', '', _('revision to rebuild to'))],
- _('[-r REV] [REV]')),
- "debugrename":
- (debugrename,
- [('r', 'rev', '', _('revision to debug'))],
- _('[-r REV] FILE')),
- "debugsetparents":
- (debugsetparents, [], _('REV1 [REV2]')),
- "debugstate":
- (debugstate,
- [('', 'nodates', None, _('do not display the saved mtime'))],
- _('[OPTION]...')),
- "debugsub":
- (debugsub,
- [('r', 'rev', '', _('revision to check'))],
- _('[-r REV] [REV]')),
- "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
- "^diff":
- (diff,
- [('r', 'rev', [], _('revision')),
- ('c', 'change', '', _('change made by revision'))
- ] + diffopts + diffopts2 + walkopts,
- _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
- "^export":
- (export,
- [('o', 'output', '', _('print output to file with formatted name')),
- ('', 'switch-parent', None, _('diff against the second parent'))
- ] + diffopts,
- _('[OPTION]... [-o OUTFILESPEC] REV...')),
- "^forget":
- (forget,
- [] + walkopts,
- _('[OPTION]... FILE...')),
- "grep":
- (grep,
- [('0', 'print0', None, _('end fields with NUL')),
- ('', 'all', None, _('print all revisions that match')),
- ('f', 'follow', None,
- _('follow changeset history, or file history across copies and renames')),
- ('i', 'ignore-case', None, _('ignore case when matching')),
- ('l', 'files-with-matches', None,
- _('print only filenames and revisions that match')),
- ('n', 'line-number', None, _('print matching line numbers')),
- ('r', 'rev', [], _('search in given revision range')),
- ('u', 'user', None, _('list the author (long with -v)')),
- ('d', 'date', None, _('list the date (short with -q)')),
- ] + walkopts,
- _('[OPTION]... PATTERN [FILE]...')),
- "heads":
- (heads,
- [('r', 'rev', '', _('show only heads which are descendants of REV')),
- ('a', 'active', False,
- _('show only the active branch heads from open branches')),
- ('c', 'closed', False,
- _('show normal and closed branch heads')),
- ] + templateopts,
- _('[-r STARTREV] [REV]...')),
- "help": (help_, [], _('[TOPIC]')),
- "identify|id":
- (identify,
- [('r', 'rev', '', _('identify the specified revision')),
- ('n', 'num', None, _('show local revision number')),
- ('i', 'id', None, _('show global revision id')),
- ('b', 'branch', None, _('show branch')),
- ('t', 'tags', None, _('show tags'))],
- _('[-nibt] [-r REV] [SOURCE]')),
- "import|patch":
- (import_,
- [('p', 'strip', 1,
- _('directory strip option for patch. This has the same '
- 'meaning as the corresponding patch option')),
- ('b', 'base', '', _('base path')),
- ('f', 'force', None,
- _('skip check for outstanding uncommitted changes')),
- ('', 'no-commit', None, _("don't commit, just update the working directory")),
- ('', 'exact', None,
- _('apply patch to the nodes from which it was generated')),
- ('', 'import-branch', None,
- _('use any branch information in patch (implied by --exact)'))] +
- commitopts + commitopts2 + similarityopts,
- _('[OPTION]... PATCH...')),
- "incoming|in":
- (incoming,
- [('f', 'force', None,
- _('run even when remote repository is unrelated')),
- ('n', 'newest-first', None, _('show newest record first')),
- ('', 'bundle', '', _('file to store the bundles into')),
- ('r', 'rev', [],
- _('a specific revision up to which you would like to pull')),
- ] + logopts + remoteopts,
- _('[-p] [-n] [-M] [-f] [-r REV]...'
- ' [--bundle FILENAME] [SOURCE]')),
- "^init":
- (init,
- remoteopts,
- _('[-e CMD] [--remotecmd CMD] [DEST]')),
- "locate":
- (locate,
- [('r', 'rev', '', _('search the repository as it stood at REV')),
- ('0', 'print0', None,
- _('end filenames with NUL, for use with xargs')),
- ('f', 'fullpath', None,
- _('print complete paths from the filesystem root')),
- ] + walkopts,
- _('[OPTION]... [PATTERN]...')),
- "^log|history":
- (log,
- [('f', 'follow', None,
- _('follow changeset history, or file history across copies and renames')),
- ('', 'follow-first', None,
- _('only follow the first parent of merge changesets')),
- ('d', 'date', '', _('show revisions matching date spec')),
- ('C', 'copies', None, _('show copied files')),
- ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
- ('r', 'rev', [], _('show the specified revision or range')),
- ('', 'removed', None, _('include revisions where files were removed')),
- ('m', 'only-merges', None, _('show only merges')),
- ('u', 'user', [], _('revisions committed by user')),
- ('b', 'only-branch', [],
- _('show only changesets within the given named branch')),
- ('P', 'prune', [], _('do not display revision or any of its ancestors')),
- ] + logopts + walkopts,
- _('[OPTION]... [FILE]')),
- "manifest":
- (manifest,
- [('r', 'rev', '', _('revision to display'))],
- _('[-r REV]')),
- "^merge":
- (merge,
- [('f', 'force', None, _('force a merge with outstanding changes')),
- ('r', 'rev', '', _('revision to merge')),
- ('P', 'preview', None,
- _('review revisions to merge (no merge is performed)'))],
- _('[-f] [[-r] REV]')),
- "outgoing|out":
- (outgoing,
- [('f', 'force', None,
- _('run even when remote repository is unrelated')),
- ('r', 'rev', [],
- _('a specific revision up to which you would like to push')),
- ('n', 'newest-first', None, _('show newest record first')),
- ] + logopts + remoteopts,
- _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
- "^parents":
- (parents,
- [('r', 'rev', '', _('show parents from the specified revision')),
- ] + templateopts,
- _('[-r REV] [FILE]')),
- "paths": (paths, [], _('[NAME]')),
- "^pull":
- (pull,
- [('u', 'update', None,
- _('update to new tip if changesets were pulled')),
- ('f', 'force', None,
- _('run even when remote repository is unrelated')),
- ('r', 'rev', [],
- _('a specific revision up to which you would like to pull')),
- ] + remoteopts,
- _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
- "^push":
- (push,
- [('f', 'force', None, _('force push')),
- ('r', 'rev', [],
- _('a specific revision up to which you would like to push')),
- ] + remoteopts,
- _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
- "recover": (recover, []),
- "^remove|rm":
- (remove,
- [('A', 'after', None, _('record delete for missing files')),
- ('f', 'force', None,
- _('remove (and delete) file even if added or modified')),
- ] + walkopts,
- _('[OPTION]... FILE...')),
- "rename|mv":
- (rename,
- [('A', 'after', None, _('record a rename that has already occurred')),
- ('f', 'force', None,
- _('forcibly copy over an existing managed file')),
- ] + walkopts + dryrunopts,
- _('[OPTION]... SOURCE... DEST')),
- "resolve":
- (resolve,
- [('a', 'all', None, _('remerge all unresolved files')),
- ('l', 'list', None, _('list state of files needing merge')),
- ('m', 'mark', None, _('mark files as resolved')),
- ('u', 'unmark', None, _('unmark files as resolved'))]
- + walkopts,
- _('[OPTION]... [FILE]...')),
- "revert":
- (revert,
- [('a', 'all', None, _('revert all changes when no arguments given')),
- ('d', 'date', '', _('tipmost revision matching date')),
- ('r', 'rev', '', _('revision to revert to')),
- ('', 'no-backup', None, _('do not save backup copies of files')),
- ] + walkopts + dryrunopts,
- _('[OPTION]... [-r REV] [NAME]...')),
- "rollback": (rollback, []),
- "root": (root, []),
- "^serve":
- (serve,
- [('A', 'accesslog', '', _('name of access log file to write to')),
- ('d', 'daemon', None, _('run server in background')),
- ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
- ('E', 'errorlog', '', _('name of error log file to write to')),
- ('p', 'port', 0, _('port to listen on (default: 8000)')),
- ('a', 'address', '', _('address to listen on (default: all interfaces)')),
- ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
- ('n', 'name', '',
- _('name to show in web pages (default: working directory)')),
- ('', 'webdir-conf', '', _('name of the webdir config file'
- ' (serve more than one repository)')),
- ('', 'pid-file', '', _('name of file to write process ID to')),
- ('', 'stdio', None, _('for remote clients')),
- ('t', 'templates', '', _('web templates to use')),
- ('', 'style', '', _('template style to use')),
- ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
- ('', 'certificate', '', _('SSL certificate file'))],
- _('[OPTION]...')),
- "showconfig|debugconfig":
- (showconfig,
- [('u', 'untrusted', None, _('show untrusted configuration options'))],
- _('[-u] [NAME]...')),
- "^status|st":
- (status,
- [('A', 'all', None, _('show status of all files')),
- ('m', 'modified', None, _('show only modified files')),
- ('a', 'added', None, _('show only added files')),
- ('r', 'removed', None, _('show only removed files')),
- ('d', 'deleted', None, _('show only deleted (but tracked) files')),
- ('c', 'clean', None, _('show only files without changes')),
- ('u', 'unknown', None, _('show only unknown (not tracked) files')),
- ('i', 'ignored', None, _('show only ignored files')),
- ('n', 'no-status', None, _('hide status prefix')),
- ('C', 'copies', None, _('show source of copied files')),
- ('0', 'print0', None,
- _('end filenames with NUL, for use with xargs')),
- ('', 'rev', [], _('show difference from revision')),
- ] + walkopts,
- _('[OPTION]... [FILE]...')),
- "tag":
- (tag,
- [('f', 'force', None, _('replace existing tag')),
- ('l', 'local', None, _('make the tag local')),
- ('r', 'rev', '', _('revision to tag')),
- ('', 'remove', None, _('remove a tag')),
- # -l/--local is already there, commitopts cannot be used
- ('m', 'message', '', _('use <text> as commit message')),
- ] + commitopts2,
- _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
- "tags": (tags, []),
- "tip":
- (tip,
- [('p', 'patch', None, _('show patch')),
- ('g', 'git', None, _('use git extended diff format')),
- ] + templateopts,
- _('[-p]')),
- "unbundle":
- (unbundle,
- [('u', 'update', None,
- _('update to new tip if changesets were unbundled'))],
- _('[-u] FILE...')),
- "^update|up|checkout|co":
- (update,
- [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
- ('c', 'check', None, _('check for uncommitted changes')),
- ('d', 'date', '', _('tipmost revision matching date')),
- ('r', 'rev', '', _('revision'))],
- _('[-C] [-d DATE] [[-r] REV]')),
- "verify": (verify, []),
- "version": (version_, []),
-}
-
-norepo = ("clone init version help debugcommands debugcomplete debugdata"
- " debugindex debugindexdot debugdate debuginstall debugfsinfo")
-optionalrepo = ("identify paths serve showconfig debugancestor")
diff --git a/sys/src/cmd/hg/mercurial/config.py b/sys/src/cmd/hg/mercurial/config.py
deleted file mode 100644
index 08a8c071b..000000000
--- a/sys/src/cmd/hg/mercurial/config.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# config.py - configuration parsing for Mercurial
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import error
-import re, os
-
-class sortdict(dict):
- 'a simple sorted dictionary'
- def __init__(self, data=None):
- self._list = []
- if data:
- self.update(data)
- def copy(self):
- return sortdict(self)
- def __setitem__(self, key, val):
- if key in self:
- self._list.remove(key)
- self._list.append(key)
- dict.__setitem__(self, key, val)
- def __iter__(self):
- return self._list.__iter__()
- def update(self, src):
- for k in src:
- self[k] = src[k]
- def items(self):
- return [(k, self[k]) for k in self._list]
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- self._list.remove(key)
-
-class config(object):
- def __init__(self, data=None):
- self._data = {}
- self._source = {}
- if data:
- for k in data._data:
- self._data[k] = data[k].copy()
- self._source = data._source.copy()
- def copy(self):
- return config(self)
- def __contains__(self, section):
- return section in self._data
- def __getitem__(self, section):
- return self._data.get(section, {})
- def __iter__(self):
- for d in self.sections():
- yield d
- def update(self, src):
- for s in src:
- if s not in self:
- self._data[s] = sortdict()
- self._data[s].update(src._data[s])
- self._source.update(src._source)
- def get(self, section, item, default=None):
- return self._data.get(section, {}).get(item, default)
- def source(self, section, item):
- return self._source.get((section, item), "")
- def sections(self):
- return sorted(self._data.keys())
- def items(self, section):
- return self._data.get(section, {}).items()
- def set(self, section, item, value, source=""):
- if section not in self:
- self._data[section] = sortdict()
- self._data[section][item] = value
- self._source[(section, item)] = source
-
- def parse(self, src, data, sections=None, remap=None, include=None):
- sectionre = re.compile(r'\[([^\[]+)\]')
- itemre = re.compile(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
- contre = re.compile(r'\s+(\S.*\S)')
- emptyre = re.compile(r'(;|#|\s*$)')
- unsetre = re.compile(r'%unset\s+(\S+)')
- includere = re.compile(r'%include\s+(\S.*\S)')
- section = ""
- item = None
- line = 0
- cont = False
-
- for l in data.splitlines(True):
- line += 1
- if cont:
- m = contre.match(l)
- if m:
- if sections and section not in sections:
- continue
- v = self.get(section, item) + "\n" + m.group(1)
- self.set(section, item, v, "%s:%d" % (src, line))
- continue
- item = None
- m = includere.match(l)
- if m:
- inc = m.group(1)
- base = os.path.dirname(src)
- inc = os.path.normpath(os.path.join(base, inc))
- if include:
- include(inc, remap=remap, sections=sections)
- continue
- if emptyre.match(l):
- continue
- m = sectionre.match(l)
- if m:
- section = m.group(1)
- if remap:
- section = remap.get(section, section)
- if section not in self:
- self._data[section] = sortdict()
- continue
- m = itemre.match(l)
- if m:
- item = m.group(1)
- cont = True
- if sections and section not in sections:
- continue
- self.set(section, item, m.group(2), "%s:%d" % (src, line))
- continue
- m = unsetre.match(l)
- if m:
- name = m.group(1)
- if sections and section not in sections:
- continue
- if self.get(section, name) != None:
- del self._data[section][name]
- continue
-
- raise error.ConfigError(_("config error at %s:%d: '%s'")
- % (src, line, l.rstrip()))
-
- def read(self, path, fp=None, sections=None, remap=None):
- if not fp:
- fp = open(path)
- self.parse(path, fp.read(), sections, remap, self.read)
diff --git a/sys/src/cmd/hg/mercurial/context.py b/sys/src/cmd/hg/mercurial/context.py
deleted file mode 100644
index 8ba3aee10..000000000
--- a/sys/src/cmd/hg/mercurial/context.py
+++ /dev/null
@@ -1,818 +0,0 @@
-# context.py - changeset and file context objects for mercurial
-#
-# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import nullid, nullrev, short, hex
-from i18n import _
-import ancestor, bdiff, error, util, subrepo
-import os, errno
-
-propertycache = util.propertycache
-
-class changectx(object):
- """A changecontext object makes access to data related to a particular
- changeset convenient."""
- def __init__(self, repo, changeid=''):
- """changeid is a revision number, node, or tag"""
- if changeid == '':
- changeid = '.'
- self._repo = repo
- if isinstance(changeid, (long, int)):
- self._rev = changeid
- self._node = self._repo.changelog.node(changeid)
- else:
- self._node = self._repo.lookup(changeid)
- self._rev = self._repo.changelog.rev(self._node)
-
- def __str__(self):
- return short(self.node())
-
- def __int__(self):
- return self.rev()
-
- def __repr__(self):
- return "<changectx %s>" % str(self)
-
- def __hash__(self):
- try:
- return hash(self._rev)
- except AttributeError:
- return id(self)
-
- def __eq__(self, other):
- try:
- return self._rev == other._rev
- except AttributeError:
- return False
-
- def __ne__(self, other):
- return not (self == other)
-
- def __nonzero__(self):
- return self._rev != nullrev
-
- @propertycache
- def _changeset(self):
- return self._repo.changelog.read(self.node())
-
- @propertycache
- def _manifest(self):
- return self._repo.manifest.read(self._changeset[0])
-
- @propertycache
- def _manifestdelta(self):
- return self._repo.manifest.readdelta(self._changeset[0])
-
- @propertycache
- def _parents(self):
- p = self._repo.changelog.parentrevs(self._rev)
- if p[1] == nullrev:
- p = p[:-1]
- return [changectx(self._repo, x) for x in p]
-
- @propertycache
- def substate(self):
- return subrepo.state(self)
-
- def __contains__(self, key):
- return key in self._manifest
-
- def __getitem__(self, key):
- return self.filectx(key)
-
- def __iter__(self):
- for f in sorted(self._manifest):
- yield f
-
- def changeset(self): return self._changeset
- def manifest(self): return self._manifest
- def manifestnode(self): return self._changeset[0]
-
- def rev(self): return self._rev
- def node(self): return self._node
- def hex(self): return hex(self._node)
- def user(self): return self._changeset[1]
- def date(self): return self._changeset[2]
- def files(self): return self._changeset[3]
- def description(self): return self._changeset[4]
- def branch(self): return self._changeset[5].get("branch")
- def extra(self): return self._changeset[5]
- def tags(self): return self._repo.nodetags(self._node)
-
- def parents(self):
- """return contexts for each parent changeset"""
- return self._parents
-
- def p1(self):
- return self._parents[0]
-
- def p2(self):
- if len(self._parents) == 2:
- return self._parents[1]
- return changectx(self._repo, -1)
-
- def children(self):
- """return contexts for each child changeset"""
- c = self._repo.changelog.children(self._node)
- return [changectx(self._repo, x) for x in c]
-
- def ancestors(self):
- for a in self._repo.changelog.ancestors(self._rev):
- yield changectx(self._repo, a)
-
- def descendants(self):
- for d in self._repo.changelog.descendants(self._rev):
- yield changectx(self._repo, d)
-
- def _fileinfo(self, path):
- if '_manifest' in self.__dict__:
- try:
- return self._manifest[path], self._manifest.flags(path)
- except KeyError:
- raise error.LookupError(self._node, path,
- _('not found in manifest'))
- if '_manifestdelta' in self.__dict__ or path in self.files():
- if path in self._manifestdelta:
- return self._manifestdelta[path], self._manifestdelta.flags(path)
- node, flag = self._repo.manifest.find(self._changeset[0], path)
- if not node:
- raise error.LookupError(self._node, path,
- _('not found in manifest'))
-
- return node, flag
-
- def filenode(self, path):
- return self._fileinfo(path)[0]
-
- def flags(self, path):
- try:
- return self._fileinfo(path)[1]
- except error.LookupError:
- return ''
-
- def filectx(self, path, fileid=None, filelog=None):
- """get a file context from this changeset"""
- if fileid is None:
- fileid = self.filenode(path)
- return filectx(self._repo, path, fileid=fileid,
- changectx=self, filelog=filelog)
-
- def ancestor(self, c2):
- """
- return the ancestor context of self and c2
- """
- n = self._repo.changelog.ancestor(self._node, c2._node)
- return changectx(self._repo, n)
-
- def walk(self, match):
- fset = set(match.files())
- # for dirstate.walk, files=['.'] means "walk the whole tree".
- # follow that here, too
- fset.discard('.')
- for fn in self:
- for ffn in fset:
- # match if the file is the exact name or a directory
- if ffn == fn or fn.startswith("%s/" % ffn):
- fset.remove(ffn)
- break
- if match(fn):
- yield fn
- for fn in sorted(fset):
- if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
- yield fn
-
- def sub(self, path):
- return subrepo.subrepo(self, path)
-
-class filectx(object):
- """A filecontext object makes access to data related to a particular
- filerevision convenient."""
- def __init__(self, repo, path, changeid=None, fileid=None,
- filelog=None, changectx=None):
- """changeid can be a changeset revision, node, or tag.
- fileid can be a file revision or node."""
- self._repo = repo
- self._path = path
-
- assert (changeid is not None
- or fileid is not None
- or changectx is not None), \
- ("bad args: changeid=%r, fileid=%r, changectx=%r"
- % (changeid, fileid, changectx))
-
- if filelog:
- self._filelog = filelog
-
- if changeid is not None:
- self._changeid = changeid
- if changectx is not None:
- self._changectx = changectx
- if fileid is not None:
- self._fileid = fileid
-
- @propertycache
- def _changectx(self):
- return changectx(self._repo, self._changeid)
-
- @propertycache
- def _filelog(self):
- return self._repo.file(self._path)
-
- @propertycache
- def _changeid(self):
- if '_changectx' in self.__dict__:
- return self._changectx.rev()
- else:
- return self._filelog.linkrev(self._filerev)
-
- @propertycache
- def _filenode(self):
- if '_fileid' in self.__dict__:
- return self._filelog.lookup(self._fileid)
- else:
- return self._changectx.filenode(self._path)
-
- @propertycache
- def _filerev(self):
- return self._filelog.rev(self._filenode)
-
- @propertycache
- def _repopath(self):
- return self._path
-
- def __nonzero__(self):
- try:
- self._filenode
- return True
- except error.LookupError:
- # file is missing
- return False
-
- def __str__(self):
- return "%s@%s" % (self.path(), short(self.node()))
-
- def __repr__(self):
- return "<filectx %s>" % str(self)
-
- def __hash__(self):
- try:
- return hash((self._path, self._fileid))
- except AttributeError:
- return id(self)
-
- def __eq__(self, other):
- try:
- return (self._path == other._path
- and self._fileid == other._fileid)
- except AttributeError:
- return False
-
- def __ne__(self, other):
- return not (self == other)
-
- def filectx(self, fileid):
- '''opens an arbitrary revision of the file without
- opening a new filelog'''
- return filectx(self._repo, self._path, fileid=fileid,
- filelog=self._filelog)
-
- def filerev(self): return self._filerev
- def filenode(self): return self._filenode
- def flags(self): return self._changectx.flags(self._path)
- def filelog(self): return self._filelog
-
- def rev(self):
- if '_changectx' in self.__dict__:
- return self._changectx.rev()
- if '_changeid' in self.__dict__:
- return self._changectx.rev()
- return self._filelog.linkrev(self._filerev)
-
- def linkrev(self): return self._filelog.linkrev(self._filerev)
- def node(self): return self._changectx.node()
- def hex(self): return hex(self.node())
- def user(self): return self._changectx.user()
- def date(self): return self._changectx.date()
- def files(self): return self._changectx.files()
- def description(self): return self._changectx.description()
- def branch(self): return self._changectx.branch()
- def manifest(self): return self._changectx.manifest()
- def changectx(self): return self._changectx
-
- def data(self): return self._filelog.read(self._filenode)
- def path(self): return self._path
- def size(self): return self._filelog.size(self._filerev)
-
- def cmp(self, text): return self._filelog.cmp(self._filenode, text)
-
- def renamed(self):
- """check if file was actually renamed in this changeset revision
-
- If rename logged in file revision, we report copy for changeset only
- if file revisions linkrev points back to the changeset in question
- or both changeset parents contain different file revisions.
- """
-
- renamed = self._filelog.renamed(self._filenode)
- if not renamed:
- return renamed
-
- if self.rev() == self.linkrev():
- return renamed
-
- name = self.path()
- fnode = self._filenode
- for p in self._changectx.parents():
- try:
- if fnode == p.filenode(name):
- return None
- except error.LookupError:
- pass
- return renamed
-
- def parents(self):
- p = self._path
- fl = self._filelog
- pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
-
- r = self._filelog.renamed(self._filenode)
- if r:
- pl[0] = (r[0], r[1], None)
-
- return [filectx(self._repo, p, fileid=n, filelog=l)
- for p,n,l in pl if n != nullid]
-
- def children(self):
- # hard for renames
- c = self._filelog.children(self._filenode)
- return [filectx(self._repo, self._path, fileid=x,
- filelog=self._filelog) for x in c]
-
- def annotate(self, follow=False, linenumber=None):
- '''returns a list of tuples of (ctx, line) for each line
- in the file, where ctx is the filectx of the node where
- that line was last changed.
- This returns tuples of ((ctx, linenumber), line) for each line,
- if "linenumber" parameter is NOT "None".
- In such tuples, linenumber means one at the first appearance
- in the managed file.
- To reduce annotation cost,
- this returns fixed value(False is used) as linenumber,
- if "linenumber" parameter is "False".'''
-
- def decorate_compat(text, rev):
- return ([rev] * len(text.splitlines()), text)
-
- def without_linenumber(text, rev):
- return ([(rev, False)] * len(text.splitlines()), text)
-
- def with_linenumber(text, rev):
- size = len(text.splitlines())
- return ([(rev, i) for i in xrange(1, size + 1)], text)
-
- decorate = (((linenumber is None) and decorate_compat) or
- (linenumber and with_linenumber) or
- without_linenumber)
-
- def pair(parent, child):
- for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
- child[0][b1:b2] = parent[0][a1:a2]
- return child
-
- getlog = util.lrucachefunc(lambda x: self._repo.file(x))
- def getctx(path, fileid):
- log = path == self._path and self._filelog or getlog(path)
- return filectx(self._repo, path, fileid=fileid, filelog=log)
- getctx = util.lrucachefunc(getctx)
-
- def parents(f):
- # we want to reuse filectx objects as much as possible
- p = f._path
- if f._filerev is None: # working dir
- pl = [(n.path(), n.filerev()) for n in f.parents()]
- else:
- pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
-
- if follow:
- r = f.renamed()
- if r:
- pl[0] = (r[0], getlog(r[0]).rev(r[1]))
-
- return [getctx(p, n) for p, n in pl if n != nullrev]
-
- # use linkrev to find the first changeset where self appeared
- if self.rev() != self.linkrev():
- base = self.filectx(self.filerev())
- else:
- base = self
-
- # find all ancestors
- needed = {base: 1}
- visit = [base]
- files = [base._path]
- while visit:
- f = visit.pop(0)
- for p in parents(f):
- if p not in needed:
- needed[p] = 1
- visit.append(p)
- if p._path not in files:
- files.append(p._path)
- else:
- # count how many times we'll use this
- needed[p] += 1
-
- # sort by revision (per file) which is a topological order
- visit = []
- for f in files:
- fn = [(n.rev(), n) for n in needed if n._path == f]
- visit.extend(fn)
-
- hist = {}
- for r, f in sorted(visit):
- curr = decorate(f.data(), f)
- for p in parents(f):
- if p != nullid:
- curr = pair(hist[p], curr)
- # trim the history of unneeded revs
- needed[p] -= 1
- if not needed[p]:
- del hist[p]
- hist[f] = curr
-
- return zip(hist[f][0], hist[f][1].splitlines(True))
-
- def ancestor(self, fc2):
- """
- find the common ancestor file context, if any, of self, and fc2
- """
-
- acache = {}
-
- # prime the ancestor cache for the working directory
- for c in (self, fc2):
- if c._filerev is None:
- pl = [(n.path(), n.filenode()) for n in c.parents()]
- acache[(c._path, None)] = pl
-
- flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
- def parents(vertex):
- if vertex in acache:
- return acache[vertex]
- f, n = vertex
- if f not in flcache:
- flcache[f] = self._repo.file(f)
- fl = flcache[f]
- pl = [(f, p) for p in fl.parents(n) if p != nullid]
- re = fl.renamed(n)
- if re:
- pl.append(re)
- acache[vertex] = pl
- return pl
-
- a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
- v = ancestor.ancestor(a, b, parents)
- if v:
- f, n = v
- return filectx(self._repo, f, fileid=n, filelog=flcache[f])
-
- return None
-
-class workingctx(changectx):
- """A workingctx object makes access to data related to
- the current working directory convenient.
- parents - a pair of parent nodeids, or None to use the dirstate.
- date - any valid date string or (unixtime, offset), or None.
- user - username string, or None.
- extra - a dictionary of extra values, or None.
- changes - a list of file lists as returned by localrepo.status()
- or None to use the repository status.
- """
- def __init__(self, repo, parents=None, text="", user=None, date=None,
- extra=None, changes=None):
- self._repo = repo
- self._rev = None
- self._node = None
- self._text = text
- if date:
- self._date = util.parsedate(date)
- if user:
- self._user = user
- if parents:
- self._parents = [changectx(self._repo, p) for p in parents]
- if changes:
- self._status = list(changes)
-
- self._extra = {}
- if extra:
- self._extra = extra.copy()
- if 'branch' not in self._extra:
- branch = self._repo.dirstate.branch()
- try:
- branch = branch.decode('UTF-8').encode('UTF-8')
- except UnicodeDecodeError:
- raise util.Abort(_('branch name not in UTF-8!'))
- self._extra['branch'] = branch
- if self._extra['branch'] == '':
- self._extra['branch'] = 'default'
-
- def __str__(self):
- return str(self._parents[0]) + "+"
-
- def __nonzero__(self):
- return True
-
- def __contains__(self, key):
- return self._repo.dirstate[key] not in "?r"
-
- @propertycache
- def _manifest(self):
- """generate a manifest corresponding to the working directory"""
-
- man = self._parents[0].manifest().copy()
- copied = self._repo.dirstate.copies()
- cf = lambda x: man.flags(copied.get(x, x))
- ff = self._repo.dirstate.flagfunc(cf)
- modified, added, removed, deleted, unknown = self._status[:5]
- for i, l in (("a", added), ("m", modified), ("u", unknown)):
- for f in l:
- man[f] = man.get(copied.get(f, f), nullid) + i
- try:
- man.set(f, ff(f))
- except OSError:
- pass
-
- for f in deleted + removed:
- if f in man:
- del man[f]
-
- return man
-
- @propertycache
- def _status(self):
- return self._repo.status(unknown=True)
-
- @propertycache
- def _user(self):
- return self._repo.ui.username()
-
- @propertycache
- def _date(self):
- return util.makedate()
-
- @propertycache
- def _parents(self):
- p = self._repo.dirstate.parents()
- if p[1] == nullid:
- p = p[:-1]
- self._parents = [changectx(self._repo, x) for x in p]
- return self._parents
-
- def manifest(self): return self._manifest
-
- def user(self): return self._user or self._repo.ui.username()
- def date(self): return self._date
- def description(self): return self._text
- def files(self):
- return sorted(self._status[0] + self._status[1] + self._status[2])
-
- def modified(self): return self._status[0]
- def added(self): return self._status[1]
- def removed(self): return self._status[2]
- def deleted(self): return self._status[3]
- def unknown(self): return self._status[4]
- def clean(self): return self._status[5]
- def branch(self): return self._extra['branch']
- def extra(self): return self._extra
-
- def tags(self):
- t = []
- [t.extend(p.tags()) for p in self.parents()]
- return t
-
- def children(self):
- return []
-
- def flags(self, path):
- if '_manifest' in self.__dict__:
- try:
- return self._manifest.flags(path)
- except KeyError:
- return ''
-
- pnode = self._parents[0].changeset()[0]
- orig = self._repo.dirstate.copies().get(path, path)
- node, flag = self._repo.manifest.find(pnode, orig)
- try:
- ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
- return ff(path)
- except OSError:
- pass
-
- if not node or path in self.deleted() or path in self.removed():
- return ''
- return flag
-
- def filectx(self, path, filelog=None):
- """get a file context from the working directory"""
- return workingfilectx(self._repo, path, workingctx=self,
- filelog=filelog)
-
- def ancestor(self, c2):
- """return the ancestor context of self and c2"""
- return self._parents[0].ancestor(c2) # punt on two parents for now
-
- def walk(self, match):
- return sorted(self._repo.dirstate.walk(match, True, False))
-
- def dirty(self, missing=False):
- "check whether a working directory is modified"
-
- return (self.p2() or self.branch() != self.p1().branch() or
- self.modified() or self.added() or self.removed() or
- (missing and self.deleted()))
-
-class workingfilectx(filectx):
- """A workingfilectx object makes access to data related to a particular
- file in the working directory convenient."""
- def __init__(self, repo, path, filelog=None, workingctx=None):
- """changeid can be a changeset revision, node, or tag.
- fileid can be a file revision or node."""
- self._repo = repo
- self._path = path
- self._changeid = None
- self._filerev = self._filenode = None
-
- if filelog:
- self._filelog = filelog
- if workingctx:
- self._changectx = workingctx
-
- @propertycache
- def _changectx(self):
- return workingctx(self._repo)
-
- def __nonzero__(self):
- return True
-
- def __str__(self):
- return "%s@%s" % (self.path(), self._changectx)
-
- def data(self): return self._repo.wread(self._path)
- def renamed(self):
- rp = self._repo.dirstate.copied(self._path)
- if not rp:
- return None
- return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
-
- def parents(self):
- '''return parent filectxs, following copies if necessary'''
- def filenode(ctx, path):
- return ctx._manifest.get(path, nullid)
-
- path = self._path
- fl = self._filelog
- pcl = self._changectx._parents
- renamed = self.renamed()
-
- if renamed:
- pl = [renamed + (None,)]
- else:
- pl = [(path, filenode(pcl[0], path), fl)]
-
- for pc in pcl[1:]:
- pl.append((path, filenode(pc, path), fl))
-
- return [filectx(self._repo, p, fileid=n, filelog=l)
- for p,n,l in pl if n != nullid]
-
- def children(self):
- return []
-
- def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
- def date(self):
- t, tz = self._changectx.date()
- try:
- return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
- except OSError, err:
- if err.errno != errno.ENOENT: raise
- return (t, tz)
-
- def cmp(self, text): return self._repo.wread(self._path) == text
-
-class memctx(object):
- """Use memctx to perform in-memory commits via localrepo.commitctx().
-
- Revision information is supplied at initialization time while
- related files data and is made available through a callback
- mechanism. 'repo' is the current localrepo, 'parents' is a
- sequence of two parent revisions identifiers (pass None for every
- missing parent), 'text' is the commit message and 'files' lists
- names of files touched by the revision (normalized and relative to
- repository root).
-
- filectxfn(repo, memctx, path) is a callable receiving the
- repository, the current memctx object and the normalized path of
- requested file, relative to repository root. It is fired by the
- commit function for every file in 'files', but calls order is
- undefined. If the file is available in the revision being
- committed (updated or added), filectxfn returns a memfilectx
- object. If the file was removed, filectxfn raises an
- IOError. Moved files are represented by marking the source file
- removed and the new file added with copy information (see
- memfilectx).
-
- user receives the committer name and defaults to current
- repository username, date is the commit date in any format
- supported by util.parsedate() and defaults to current date, extra
- is a dictionary of metadata or is left empty.
- """
- def __init__(self, repo, parents, text, files, filectxfn, user=None,
- date=None, extra=None):
- self._repo = repo
- self._rev = None
- self._node = None
- self._text = text
- self._date = date and util.parsedate(date) or util.makedate()
- self._user = user
- parents = [(p or nullid) for p in parents]
- p1, p2 = parents
- self._parents = [changectx(self._repo, p) for p in (p1, p2)]
- files = sorted(set(files))
- self._status = [files, [], [], [], []]
- self._filectxfn = filectxfn
-
- self._extra = extra and extra.copy() or {}
- if 'branch' not in self._extra:
- self._extra['branch'] = 'default'
- elif self._extra.get('branch') == '':
- self._extra['branch'] = 'default'
-
- def __str__(self):
- return str(self._parents[0]) + "+"
-
- def __int__(self):
- return self._rev
-
- def __nonzero__(self):
- return True
-
- def __getitem__(self, key):
- return self.filectx(key)
-
- def p1(self): return self._parents[0]
- def p2(self): return self._parents[1]
-
- def user(self): return self._user or self._repo.ui.username()
- def date(self): return self._date
- def description(self): return self._text
- def files(self): return self.modified()
- def modified(self): return self._status[0]
- def added(self): return self._status[1]
- def removed(self): return self._status[2]
- def deleted(self): return self._status[3]
- def unknown(self): return self._status[4]
- def clean(self): return self._status[5]
- def branch(self): return self._extra['branch']
- def extra(self): return self._extra
- def flags(self, f): return self[f].flags()
-
- def parents(self):
- """return contexts for each parent changeset"""
- return self._parents
-
- def filectx(self, path, filelog=None):
- """get a file context from the working directory"""
- return self._filectxfn(self._repo, self, path)
-
-class memfilectx(object):
- """memfilectx represents an in-memory file to commit.
-
- See memctx for more details.
- """
- def __init__(self, path, data, islink, isexec, copied):
- """
- path is the normalized file path relative to repository root.
- data is the file content as a string.
- islink is True if the file is a symbolic link.
- isexec is True if the file is executable.
- copied is the source file path if current file was copied in the
- revision being committed, or None."""
- self._path = path
- self._data = data
- self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
- self._copied = None
- if copied:
- self._copied = (copied, nullid)
-
- def __nonzero__(self): return True
- def __str__(self): return "%s@%s" % (self.path(), self._changectx)
- def path(self): return self._path
- def data(self): return self._data
- def flags(self): return self._flags
- def isexec(self): return 'x' in self._flags
- def islink(self): return 'l' in self._flags
- def renamed(self): return self._copied
diff --git a/sys/src/cmd/hg/mercurial/copies.py b/sys/src/cmd/hg/mercurial/copies.py
deleted file mode 100644
index 63c80a3f6..000000000
--- a/sys/src/cmd/hg/mercurial/copies.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# copies.py - copy detection for Mercurial
-#
-# Copyright 2008 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import util
-import heapq
-
-def _nonoverlap(d1, d2, d3):
- "Return list of elements in d1 not in d2 or d3"
- return sorted([d for d in d1 if d not in d3 and d not in d2])
-
-def _dirname(f):
- s = f.rfind("/")
- if s == -1:
- return ""
- return f[:s]
-
-def _dirs(files):
- d = set()
- for f in files:
- f = _dirname(f)
- while f not in d:
- d.add(f)
- f = _dirname(f)
- return d
-
-def _findoldnames(fctx, limit):
- "find files that path was copied from, back to linkrev limit"
- old = {}
- seen = set()
- orig = fctx.path()
- visit = [(fctx, 0)]
- while visit:
- fc, depth = visit.pop()
- s = str(fc)
- if s in seen:
- continue
- seen.add(s)
- if fc.path() != orig and fc.path() not in old:
- old[fc.path()] = (depth, fc.path()) # remember depth
- if fc.rev() is not None and fc.rev() < limit:
- continue
- visit += [(p, depth - 1) for p in fc.parents()]
-
- # return old names sorted by depth
- return [o[1] for o in sorted(old.values())]
-
-def _findlimit(repo, a, b):
- "find the earliest revision that's an ancestor of a or b but not both"
- # basic idea:
- # - mark a and b with different sides
- # - if a parent's children are all on the same side, the parent is
- # on that side, otherwise it is on no side
- # - walk the graph in topological order with the help of a heap;
- # - add unseen parents to side map
- # - clear side of any parent that has children on different sides
- # - track number of interesting revs that might still be on a side
- # - track the lowest interesting rev seen
- # - quit when interesting revs is zero
-
- cl = repo.changelog
- working = len(cl) # pseudo rev for the working directory
- if a is None:
- a = working
- if b is None:
- b = working
-
- side = {a: -1, b: 1}
- visit = [-a, -b]
- heapq.heapify(visit)
- interesting = len(visit)
- limit = working
-
- while interesting:
- r = -heapq.heappop(visit)
- if r == working:
- parents = [cl.rev(p) for p in repo.dirstate.parents()]
- else:
- parents = cl.parentrevs(r)
- for p in parents:
- if p not in side:
- # first time we see p; add it to visit
- side[p] = side[r]
- if side[p]:
- interesting += 1
- heapq.heappush(visit, -p)
- elif side[p] and side[p] != side[r]:
- # p was interesting but now we know better
- side[p] = 0
- interesting -= 1
- if side[r]:
- limit = r # lowest rev visited
- interesting -= 1
- return limit
-
-def copies(repo, c1, c2, ca, checkdirs=False):
- """
- Find moves and copies between context c1 and c2
- """
- # avoid silly behavior for update from empty dir
- if not c1 or not c2 or c1 == c2:
- return {}, {}
-
- # avoid silly behavior for parent -> working dir
- if c2.node() is None and c1.node() == repo.dirstate.parents()[0]:
- return repo.dirstate.copies(), {}
-
- limit = _findlimit(repo, c1.rev(), c2.rev())
- m1 = c1.manifest()
- m2 = c2.manifest()
- ma = ca.manifest()
-
- def makectx(f, n):
- if len(n) != 20: # in a working context?
- if c1.rev() is None:
- return c1.filectx(f)
- return c2.filectx(f)
- return repo.filectx(f, fileid=n)
-
- ctx = util.lrucachefunc(makectx)
- copy = {}
- fullcopy = {}
- diverge = {}
-
- def checkcopies(f, m1, m2):
- '''check possible copies of f from m1 to m2'''
- c1 = ctx(f, m1[f])
- for of in _findoldnames(c1, limit):
- fullcopy[f] = of # remember for dir rename detection
- if of in m2: # original file not in other manifest?
- # if the original file is unchanged on the other branch,
- # no merge needed
- if m2[of] != ma.get(of):
- c2 = ctx(of, m2[of])
- ca = c1.ancestor(c2)
- # related and named changed on only one side?
- if ca and (ca.path() == f or ca.path() == c2.path()):
- if c1 != ca or c2 != ca: # merge needed?
- copy[f] = of
- elif of in ma:
- diverge.setdefault(of, []).append(f)
-
- repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
-
- u1 = _nonoverlap(m1, m2, ma)
- u2 = _nonoverlap(m2, m1, ma)
-
- if u1:
- repo.ui.debug(_(" unmatched files in local:\n %s\n")
- % "\n ".join(u1))
- if u2:
- repo.ui.debug(_(" unmatched files in other:\n %s\n")
- % "\n ".join(u2))
-
- for f in u1:
- checkcopies(f, m1, m2)
- for f in u2:
- checkcopies(f, m2, m1)
-
- diverge2 = set()
- for of, fl in diverge.items():
- if len(fl) == 1:
- del diverge[of] # not actually divergent
- else:
- diverge2.update(fl) # reverse map for below
-
- if fullcopy:
- repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n"))
- for f in fullcopy:
- note = ""
- if f in copy: note += "*"
- if f in diverge2: note += "!"
- repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
- del diverge2
-
- if not fullcopy or not checkdirs:
- return copy, diverge
-
- repo.ui.debug(_(" checking for directory renames\n"))
-
- # generate a directory move map
- d1, d2 = _dirs(m1), _dirs(m2)
- invalid = set()
- dirmove = {}
-
- # examine each file copy for a potential directory move, which is
- # when all the files in a directory are moved to a new directory
- for dst, src in fullcopy.iteritems():
- dsrc, ddst = _dirname(src), _dirname(dst)
- if dsrc in invalid:
- # already seen to be uninteresting
- continue
- elif dsrc in d1 and ddst in d1:
- # directory wasn't entirely moved locally
- invalid.add(dsrc)
- elif dsrc in d2 and ddst in d2:
- # directory wasn't entirely moved remotely
- invalid.add(dsrc)
- elif dsrc in dirmove and dirmove[dsrc] != ddst:
- # files from the same directory moved to two different places
- invalid.add(dsrc)
- else:
- # looks good so far
- dirmove[dsrc + "/"] = ddst + "/"
-
- for i in invalid:
- if i in dirmove:
- del dirmove[i]
- del d1, d2, invalid
-
- if not dirmove:
- return copy, diverge
-
- for d in dirmove:
- repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d]))
-
- # check unaccounted nonoverlapping files against directory moves
- for f in u1 + u2:
- if f not in fullcopy:
- for d in dirmove:
- if f.startswith(d):
- # new file added in a directory that was moved, move it
- df = dirmove[d] + f[len(d):]
- if df not in copy:
- copy[f] = df
- repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f]))
- break
-
- return copy, diverge
diff --git a/sys/src/cmd/hg/mercurial/demandimport.py b/sys/src/cmd/hg/mercurial/demandimport.py
deleted file mode 100644
index a620bb243..000000000
--- a/sys/src/cmd/hg/mercurial/demandimport.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# demandimport.py - global demand-loading of modules for Mercurial
-#
-# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-'''
-demandimport - automatic demandloading of modules
-
-To enable this module, do:
-
- import demandimport; demandimport.enable()
-
-Imports of the following forms will be demand-loaded:
-
- import a, b.c
- import a.b as c
- from a import b,c # a will be loaded immediately
-
-These imports will not be delayed:
-
- from a import *
- b = __import__(a)
-'''
-
-import __builtin__
-_origimport = __import__
-
-class _demandmod(object):
- """module demand-loader and proxy"""
- def __init__(self, name, globals, locals):
- if '.' in name:
- head, rest = name.split('.', 1)
- after = [rest]
- else:
- head = name
- after = []
- object.__setattr__(self, "_data", (head, globals, locals, after))
- object.__setattr__(self, "_module", None)
- def _extend(self, name):
- """add to the list of submodules to load"""
- self._data[3].append(name)
- def _load(self):
- if not self._module:
- head, globals, locals, after = self._data
- mod = _origimport(head, globals, locals)
- # load submodules
- def subload(mod, p):
- h, t = p, None
- if '.' in p:
- h, t = p.split('.', 1)
- if not hasattr(mod, h):
- setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__))
- elif t:
- subload(getattr(mod, h), t)
-
- for x in after:
- subload(mod, x)
-
- # are we in the locals dictionary still?
- if locals and locals.get(head) == self:
- locals[head] = mod
- object.__setattr__(self, "_module", mod)
-
- def __repr__(self):
- if self._module:
- return "<proxied module '%s'>" % self._data[0]
- return "<unloaded module '%s'>" % self._data[0]
- def __call__(self, *args, **kwargs):
- raise TypeError("%s object is not callable" % repr(self))
- def __getattribute__(self, attr):
- if attr in ('_data', '_extend', '_load', '_module'):
- return object.__getattribute__(self, attr)
- self._load()
- return getattr(self._module, attr)
- def __setattr__(self, attr, val):
- self._load()
- setattr(self._module, attr, val)
-
-def _demandimport(name, globals=None, locals=None, fromlist=None, level=None):
- if not locals or name in ignore or fromlist == ('*',):
- # these cases we can't really delay
- if level is None:
- return _origimport(name, globals, locals, fromlist)
- else:
- return _origimport(name, globals, locals, fromlist, level)
- elif not fromlist:
- # import a [as b]
- if '.' in name: # a.b
- base, rest = name.split('.', 1)
- # email.__init__ loading email.mime
- if globals and globals.get('__name__', None) == base:
- return _origimport(name, globals, locals, fromlist)
- # if a is already demand-loaded, add b to its submodule list
- if base in locals:
- if isinstance(locals[base], _demandmod):
- locals[base]._extend(rest)
- return locals[base]
- return _demandmod(name, globals, locals)
- else:
- if level is not None:
- # from . import b,c,d or from .a import b,c,d
- return _origimport(name, globals, locals, fromlist, level)
- # from a import b,c,d
- mod = _origimport(name, globals, locals)
- # recurse down the module chain
- for comp in name.split('.')[1:]:
- if not hasattr(mod, comp):
- setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__))
- mod = getattr(mod, comp)
- for x in fromlist:
- # set requested submodules for demand load
- if not(hasattr(mod, x)):
- setattr(mod, x, _demandmod(x, mod.__dict__, locals))
- return mod
-
-ignore = [
- '_hashlib',
- '_xmlplus',
- 'fcntl',
- 'win32com.gen_py',
- 'pythoncom',
- # imported by tarfile, not available under Windows
- 'pwd',
- 'grp',
- # imported by profile, itself imported by hotshot.stats,
- # not available under Windows
- 'resource',
- ]
-
-def enable():
- "enable global demand-loading of modules"
- __builtin__.__import__ = _demandimport
-
-def disable():
- "disable global demand-loading of modules"
- __builtin__.__import__ = _origimport
-
diff --git a/sys/src/cmd/hg/mercurial/diffhelpers.c b/sys/src/cmd/hg/mercurial/diffhelpers.c
deleted file mode 100644
index d9316ea4b..000000000
--- a/sys/src/cmd/hg/mercurial/diffhelpers.c
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * diffhelpers.c - helper routines for mpatch
- *
- * Copyright 2007 Chris Mason <chris.mason@oracle.com>
- *
- * This software may be used and distributed according to the terms
- * of the GNU General Public License v2, incorporated herein by reference.
- */
-
-#include <Python.h>
-#include <stdlib.h>
-#include <string.h>
-
-static char diffhelpers_doc[] = "Efficient diff parsing";
-static PyObject *diffhelpers_Error;
-
-
-/* fixup the last lines of a and b when the patch has no newline at eof */
-static void _fix_newline(PyObject *hunk, PyObject *a, PyObject *b)
-{
- int hunksz = PyList_Size(hunk);
- PyObject *s = PyList_GET_ITEM(hunk, hunksz-1);
- char *l = PyString_AS_STRING(s);
- int sz = PyString_GET_SIZE(s);
- int alen = PyList_Size(a);
- int blen = PyList_Size(b);
- char c = l[0];
-
- PyObject *hline = PyString_FromStringAndSize(l, sz-1);
- if (c == ' ' || c == '+') {
- PyObject *rline = PyString_FromStringAndSize(l+1, sz-2);
- PyList_SetItem(b, blen-1, rline);
- }
- if (c == ' ' || c == '-') {
- Py_INCREF(hline);
- PyList_SetItem(a, alen-1, hline);
- }
- PyList_SetItem(hunk, hunksz-1, hline);
-}
-
-/* python callable form of _fix_newline */
-static PyObject *
-fix_newline(PyObject *self, PyObject *args)
-{
- PyObject *hunk, *a, *b;
- if (!PyArg_ParseTuple(args, "OOO", &hunk, &a, &b))
- return NULL;
- _fix_newline(hunk, a, b);
- return Py_BuildValue("l", 0);
-}
-
-/*
- * read lines from fp into the hunk. The hunk is parsed into two arrays
- * a and b. a gets the old state of the text, b gets the new state
- * The control char from the hunk is saved when inserting into a, but not b
- * (for performance while deleting files)
- */
-static PyObject *
-addlines(PyObject *self, PyObject *args)
-{
-
- PyObject *fp, *hunk, *a, *b, *x;
- int i;
- int lena, lenb;
- int num;
- int todoa, todob;
- char *s, c;
- PyObject *l;
- if (!PyArg_ParseTuple(args, "OOiiOO", &fp, &hunk, &lena, &lenb, &a, &b))
- return NULL;
-
- while(1) {
- todoa = lena - PyList_Size(a);
- todob = lenb - PyList_Size(b);
- num = todoa > todob ? todoa : todob;
- if (num == 0)
- break;
- for (i = 0 ; i < num ; i++) {
- x = PyFile_GetLine(fp, 0);
- s = PyString_AS_STRING(x);
- c = *s;
- if (strcmp(s, "\\ No newline at end of file\n") == 0) {
- _fix_newline(hunk, a, b);
- continue;
- }
- if (c == '\n') {
- /* Some patches may be missing the control char
- * on empty lines. Supply a leading space. */
- Py_DECREF(x);
- x = PyString_FromString(" \n");
- }
- PyList_Append(hunk, x);
- if (c == '+') {
- l = PyString_FromString(s + 1);
- PyList_Append(b, l);
- Py_DECREF(l);
- } else if (c == '-') {
- PyList_Append(a, x);
- } else {
- l = PyString_FromString(s + 1);
- PyList_Append(b, l);
- Py_DECREF(l);
- PyList_Append(a, x);
- }
- Py_DECREF(x);
- }
- }
- return Py_BuildValue("l", 0);
-}
-
-/*
- * compare the lines in a with the lines in b. a is assumed to have
- * a control char at the start of each line, this char is ignored in the
- * compare
- */
-static PyObject *
-testhunk(PyObject *self, PyObject *args)
-{
-
- PyObject *a, *b;
- long bstart;
- int alen, blen;
- int i;
- char *sa, *sb;
-
- if (!PyArg_ParseTuple(args, "OOl", &a, &b, &bstart))
- return NULL;
- alen = PyList_Size(a);
- blen = PyList_Size(b);
- if (alen > blen - bstart) {
- return Py_BuildValue("l", -1);
- }
- for (i = 0 ; i < alen ; i++) {
- sa = PyString_AS_STRING(PyList_GET_ITEM(a, i));
- sb = PyString_AS_STRING(PyList_GET_ITEM(b, i + bstart));
- if (strcmp(sa+1, sb) != 0)
- return Py_BuildValue("l", -1);
- }
- return Py_BuildValue("l", 0);
-}
-
-static PyMethodDef methods[] = {
- {"addlines", addlines, METH_VARARGS, "add lines to a hunk\n"},
- {"fix_newline", fix_newline, METH_VARARGS, "fixup newline counters\n"},
- {"testhunk", testhunk, METH_VARARGS, "test lines in a hunk\n"},
- {NULL, NULL}
-};
-
-PyMODINIT_FUNC
-initdiffhelpers(void)
-{
- Py_InitModule3("diffhelpers", methods, diffhelpers_doc);
- diffhelpers_Error = PyErr_NewException("diffhelpers.diffhelpersError",
- NULL, NULL);
-}
-
diff --git a/sys/src/cmd/hg/mercurial/dirstate.py b/sys/src/cmd/hg/mercurial/dirstate.py
deleted file mode 100644
index c10e3a6c7..000000000
--- a/sys/src/cmd/hg/mercurial/dirstate.py
+++ /dev/null
@@ -1,601 +0,0 @@
-# dirstate.py - working directory tracking for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import nullid
-from i18n import _
-import util, ignore, osutil, parsers
-import struct, os, stat, errno
-import cStringIO, sys
-
-_unknown = ('?', 0, 0, 0)
-_format = ">cllll"
-propertycache = util.propertycache
-
-def _finddirs(path):
- pos = path.rfind('/')
- while pos != -1:
- yield path[:pos]
- pos = path.rfind('/', 0, pos)
-
-def _incdirs(dirs, path):
- for base in _finddirs(path):
- if base in dirs:
- dirs[base] += 1
- return
- dirs[base] = 1
-
-def _decdirs(dirs, path):
- for base in _finddirs(path):
- if dirs[base] > 1:
- dirs[base] -= 1
- return
- del dirs[base]
-
-class dirstate(object):
-
- def __init__(self, opener, ui, root):
- self._opener = opener
- self._root = root
- self._rootdir = os.path.join(root, '')
- self._dirty = False
- self._dirtypl = False
- self._ui = ui
-
- @propertycache
- def _map(self):
- self._read()
- return self._map
-
- @propertycache
- def _copymap(self):
- self._read()
- return self._copymap
-
- @propertycache
- def _foldmap(self):
- f = {}
- for name in self._map:
- f[os.path.normcase(name)] = name
- return f
-
- @propertycache
- def _branch(self):
- try:
- return self._opener("branch").read().strip() or "default"
- except IOError:
- return "default"
-
- @propertycache
- def _pl(self):
- try:
- st = self._opener("dirstate").read(40)
- l = len(st)
- if l == 40:
- return st[:20], st[20:40]
- elif l > 0 and l < 40:
- raise util.Abort(_('working directory state appears damaged!'))
- except IOError, err:
- if err.errno != errno.ENOENT: raise
- return [nullid, nullid]
-
- @propertycache
- def _dirs(self):
- dirs = {}
- for f,s in self._map.iteritems():
- if s[0] != 'r':
- _incdirs(dirs, f)
- return dirs
-
- @propertycache
- def _ignore(self):
- files = [self._join('.hgignore')]
- for name, path in self._ui.configitems("ui"):
- if name == 'ignore' or name.startswith('ignore.'):
- files.append(os.path.expanduser(path))
- return ignore.ignore(self._root, files, self._ui.warn)
-
- @propertycache
- def _slash(self):
- return self._ui.configbool('ui', 'slash') and os.sep != '/'
-
- @propertycache
- def _checklink(self):
- return util.checklink(self._root)
-
- @propertycache
- def _checkexec(self):
- return util.checkexec(self._root)
-
- @propertycache
- def _checkcase(self):
- return not util.checkcase(self._join('.hg'))
-
- def _join(self, f):
- # much faster than os.path.join()
- # it's safe because f is always a relative path
- return self._rootdir + f
-
- def flagfunc(self, fallback):
- if self._checklink:
- if self._checkexec:
- def f(x):
- p = self._join(x)
- if os.path.islink(p):
- return 'l'
- if util.is_exec(p):
- return 'x'
- return ''
- return f
- def f(x):
- if os.path.islink(self._join(x)):
- return 'l'
- if 'x' in fallback(x):
- return 'x'
- return ''
- return f
- if self._checkexec:
- def f(x):
- if 'l' in fallback(x):
- return 'l'
- if util.is_exec(self._join(x)):
- return 'x'
- return ''
- return f
- return fallback
-
- def getcwd(self):
- cwd = os.getcwd()
- if cwd == self._root: return ''
- # self._root ends with a path separator if self._root is '/' or 'C:\'
- rootsep = self._root
- if not util.endswithsep(rootsep):
- rootsep += os.sep
- if cwd.startswith(rootsep):
- return cwd[len(rootsep):]
- else:
- # we're outside the repo. return an absolute path.
- return cwd
-
- def pathto(self, f, cwd=None):
- if cwd is None:
- cwd = self.getcwd()
- path = util.pathto(self._root, cwd, f)
- if self._slash:
- return util.normpath(path)
- return path
-
- def __getitem__(self, key):
- ''' current states:
- n normal
- m needs merging
- r marked for removal
- a marked for addition
- ? not tracked'''
- return self._map.get(key, ("?",))[0]
-
- def __contains__(self, key):
- return key in self._map
-
- def __iter__(self):
- for x in sorted(self._map):
- yield x
-
- def parents(self):
- return self._pl
-
- def branch(self):
- return self._branch
-
- def setparents(self, p1, p2=nullid):
- self._dirty = self._dirtypl = True
- self._pl = p1, p2
-
- def setbranch(self, branch):
- self._branch = branch
- self._opener("branch", "w").write(branch + '\n')
-
- def _read(self):
- self._map = {}
- self._copymap = {}
- try:
- st = self._opener("dirstate").read()
- except IOError, err:
- if err.errno != errno.ENOENT: raise
- return
- if not st:
- return
-
- p = parsers.parse_dirstate(self._map, self._copymap, st)
- if not self._dirtypl:
- self._pl = p
-
- def invalidate(self):
- for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
- if a in self.__dict__:
- delattr(self, a)
- self._dirty = False
-
- def copy(self, source, dest):
- """Mark dest as a copy of source. Unmark dest if source is None.
- """
- if source == dest:
- return
- self._dirty = True
- if source is not None:
- self._copymap[dest] = source
- elif dest in self._copymap:
- del self._copymap[dest]
-
- def copied(self, file):
- return self._copymap.get(file, None)
-
- def copies(self):
- return self._copymap
-
- def _droppath(self, f):
- if self[f] not in "?r" and "_dirs" in self.__dict__:
- _decdirs(self._dirs, f)
-
- def _addpath(self, f, check=False):
- oldstate = self[f]
- if check or oldstate == "r":
- if '\r' in f or '\n' in f:
- raise util.Abort(
- _("'\\n' and '\\r' disallowed in filenames: %r") % f)
- if f in self._dirs:
- raise util.Abort(_('directory %r already in dirstate') % f)
- # shadows
- for d in _finddirs(f):
- if d in self._dirs:
- break
- if d in self._map and self[d] != 'r':
- raise util.Abort(
- _('file %r in dirstate clashes with %r') % (d, f))
- if oldstate in "?r" and "_dirs" in self.__dict__:
- _incdirs(self._dirs, f)
-
- def normal(self, f):
- 'mark a file normal and clean'
- self._dirty = True
- self._addpath(f)
- s = os.lstat(self._join(f))
- self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
- if f in self._copymap:
- del self._copymap[f]
-
- def normallookup(self, f):
- 'mark a file normal, but possibly dirty'
- if self._pl[1] != nullid and f in self._map:
- # if there is a merge going on and the file was either
- # in state 'm' or dirty before being removed, restore that state.
- entry = self._map[f]
- if entry[0] == 'r' and entry[2] in (-1, -2):
- source = self._copymap.get(f)
- if entry[2] == -1:
- self.merge(f)
- elif entry[2] == -2:
- self.normaldirty(f)
- if source:
- self.copy(source, f)
- return
- if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
- return
- self._dirty = True
- self._addpath(f)
- self._map[f] = ('n', 0, -1, -1)
- if f in self._copymap:
- del self._copymap[f]
-
- def normaldirty(self, f):
- 'mark a file normal, but dirty'
- self._dirty = True
- self._addpath(f)
- self._map[f] = ('n', 0, -2, -1)
- if f in self._copymap:
- del self._copymap[f]
-
- def add(self, f):
- 'mark a file added'
- self._dirty = True
- self._addpath(f, True)
- self._map[f] = ('a', 0, -1, -1)
- if f in self._copymap:
- del self._copymap[f]
-
- def remove(self, f):
- 'mark a file removed'
- self._dirty = True
- self._droppath(f)
- size = 0
- if self._pl[1] != nullid and f in self._map:
- entry = self._map[f]
- if entry[0] == 'm':
- size = -1
- elif entry[0] == 'n' and entry[2] == -2:
- size = -2
- self._map[f] = ('r', 0, size, 0)
- if size == 0 and f in self._copymap:
- del self._copymap[f]
-
- def merge(self, f):
- 'mark a file merged'
- self._dirty = True
- s = os.lstat(self._join(f))
- self._addpath(f)
- self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
- if f in self._copymap:
- del self._copymap[f]
-
- def forget(self, f):
- 'forget a file'
- self._dirty = True
- try:
- self._droppath(f)
- del self._map[f]
- except KeyError:
- self._ui.warn(_("not in dirstate: %s\n") % f)
-
- def _normalize(self, path, knownpath):
- norm_path = os.path.normcase(path)
- fold_path = self._foldmap.get(norm_path, None)
- if fold_path is None:
- if knownpath or not os.path.exists(os.path.join(self._root, path)):
- fold_path = path
- else:
- fold_path = self._foldmap.setdefault(norm_path,
- util.fspath(path, self._root))
- return fold_path
-
- def clear(self):
- self._map = {}
- if "_dirs" in self.__dict__:
- delattr(self, "_dirs");
- self._copymap = {}
- self._pl = [nullid, nullid]
- self._dirty = True
-
- def rebuild(self, parent, files):
- self.clear()
- for f in files:
- if 'x' in files.flags(f):
- self._map[f] = ('n', 0777, -1, 0)
- else:
- self._map[f] = ('n', 0666, -1, 0)
- self._pl = (parent, nullid)
- self._dirty = True
-
- def write(self):
- if not self._dirty:
- return
- st = self._opener("dirstate", "w", atomictemp=True)
-
- try:
- gran = int(self._ui.config('dirstate', 'granularity', 1))
- except ValueError:
- gran = 1
- limit = sys.maxint
- if gran > 0:
- limit = util.fstat(st).st_mtime - gran
-
- cs = cStringIO.StringIO()
- copymap = self._copymap
- pack = struct.pack
- write = cs.write
- write("".join(self._pl))
- for f, e in self._map.iteritems():
- if f in copymap:
- f = "%s\0%s" % (f, copymap[f])
- if e[3] > limit and e[0] == 'n':
- e = (e[0], 0, -1, -1)
- e = pack(_format, e[0], e[1], e[2], e[3], len(f))
- write(e)
- write(f)
- st.write(cs.getvalue())
- st.rename()
- self._dirty = self._dirtypl = False
-
- def _dirignore(self, f):
- if f == '.':
- return False
- if self._ignore(f):
- return True
- for p in _finddirs(f):
- if self._ignore(p):
- return True
- return False
-
- def walk(self, match, unknown, ignored):
- '''
- walk recursively through the directory tree, finding all files
- matched by the match function
-
- results are yielded in a tuple (filename, stat), where stat
- and st is the stat result if the file was found in the directory.
- '''
-
- def fwarn(f, msg):
- self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
- return False
-
- def badtype(mode):
- kind = _('unknown')
- if stat.S_ISCHR(mode): kind = _('character device')
- elif stat.S_ISBLK(mode): kind = _('block device')
- elif stat.S_ISFIFO(mode): kind = _('fifo')
- elif stat.S_ISSOCK(mode): kind = _('socket')
- elif stat.S_ISDIR(mode): kind = _('directory')
- return _('unsupported file type (type is %s)') % kind
-
- ignore = self._ignore
- dirignore = self._dirignore
- if ignored:
- ignore = util.never
- dirignore = util.never
- elif not unknown:
- # if unknown and ignored are False, skip step 2
- ignore = util.always
- dirignore = util.always
-
- matchfn = match.matchfn
- badfn = match.bad
- dmap = self._map
- normpath = util.normpath
- listdir = osutil.listdir
- lstat = os.lstat
- getkind = stat.S_IFMT
- dirkind = stat.S_IFDIR
- regkind = stat.S_IFREG
- lnkkind = stat.S_IFLNK
- join = self._join
- work = []
- wadd = work.append
-
- if self._checkcase:
- normalize = self._normalize
- else:
- normalize = lambda x, y: x
-
- exact = skipstep3 = False
- if matchfn == match.exact: # match.exact
- exact = True
- dirignore = util.always # skip step 2
- elif match.files() and not match.anypats(): # match.match, no patterns
- skipstep3 = True
-
- files = set(match.files())
- if not files or '.' in files:
- files = ['']
- results = {'.hg': None}
-
- # step 1: find all explicit files
- for ff in sorted(files):
- nf = normalize(normpath(ff), False)
- if nf in results:
- continue
-
- try:
- st = lstat(join(nf))
- kind = getkind(st.st_mode)
- if kind == dirkind:
- skipstep3 = False
- if nf in dmap:
- #file deleted on disk but still in dirstate
- results[nf] = None
- match.dir(nf)
- if not dirignore(nf):
- wadd(nf)
- elif kind == regkind or kind == lnkkind:
- results[nf] = st
- else:
- badfn(ff, badtype(kind))
- if nf in dmap:
- results[nf] = None
- except OSError, inst:
- if nf in dmap: # does it exactly match a file?
- results[nf] = None
- else: # does it match a directory?
- prefix = nf + "/"
- for fn in dmap:
- if fn.startswith(prefix):
- match.dir(nf)
- skipstep3 = False
- break
- else:
- badfn(ff, inst.strerror)
-
- # step 2: visit subdirectories
- while work:
- nd = work.pop()
- skip = None
- if nd == '.':
- nd = ''
- else:
- skip = '.hg'
- try:
- entries = listdir(join(nd), stat=True, skip=skip)
- except OSError, inst:
- if inst.errno == errno.EACCES:
- fwarn(nd, inst.strerror)
- continue
- raise
- for f, kind, st in entries:
- nf = normalize(nd and (nd + "/" + f) or f, True)
- if nf not in results:
- if kind == dirkind:
- if not ignore(nf):
- match.dir(nf)
- wadd(nf)
- if nf in dmap and matchfn(nf):
- results[nf] = None
- elif kind == regkind or kind == lnkkind:
- if nf in dmap:
- if matchfn(nf):
- results[nf] = st
- elif matchfn(nf) and not ignore(nf):
- results[nf] = st
- elif nf in dmap and matchfn(nf):
- results[nf] = None
-
- # step 3: report unseen items in the dmap hash
- if not skipstep3 and not exact:
- visit = sorted([f for f in dmap if f not in results and matchfn(f)])
- for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
- if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
- st = None
- results[nf] = st
-
- del results['.hg']
- return results
-
- def status(self, match, ignored, clean, unknown):
- listignored, listclean, listunknown = ignored, clean, unknown
- lookup, modified, added, unknown, ignored = [], [], [], [], []
- removed, deleted, clean = [], [], []
-
- dmap = self._map
- ladd = lookup.append
- madd = modified.append
- aadd = added.append
- uadd = unknown.append
- iadd = ignored.append
- radd = removed.append
- dadd = deleted.append
- cadd = clean.append
-
- for fn, st in self.walk(match, listunknown, listignored).iteritems():
- if fn not in dmap:
- if (listignored or match.exact(fn)) and self._dirignore(fn):
- if listignored:
- iadd(fn)
- elif listunknown:
- uadd(fn)
- continue
-
- state, mode, size, time = dmap[fn]
-
- if not st and state in "nma":
- dadd(fn)
- elif state == 'n':
- if (size >= 0 and
- (size != st.st_size
- or ((mode ^ st.st_mode) & 0100 and self._checkexec))
- or size == -2
- or fn in self._copymap):
- madd(fn)
- elif time != int(st.st_mtime):
- ladd(fn)
- elif listclean:
- cadd(fn)
- elif state == 'm':
- madd(fn)
- elif state == 'a':
- aadd(fn)
- elif state == 'r':
- radd(fn)
-
- return (lookup, modified, added, removed, deleted, unknown, ignored,
- clean)
diff --git a/sys/src/cmd/hg/mercurial/dispatch.py b/sys/src/cmd/hg/mercurial/dispatch.py
deleted file mode 100644
index a01cf8204..000000000
--- a/sys/src/cmd/hg/mercurial/dispatch.py
+++ /dev/null
@@ -1,501 +0,0 @@
-# dispatch.py - command dispatching for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import os, sys, atexit, signal, pdb, socket, errno, shlex, time
-import util, commands, hg, fancyopts, extensions, hook, error
-import cmdutil, encoding
-import ui as _ui
-
-def run():
- "run the command in sys.argv"
- sys.exit(dispatch(sys.argv[1:]))
-
-def dispatch(args):
- "run the command specified in args"
- try:
- u = _ui.ui()
- if '--traceback' in args:
- u.setconfig('ui', 'traceback', 'on')
- except util.Abort, inst:
- sys.stderr.write(_("abort: %s\n") % inst)
- return -1
- return _runcatch(u, args)
-
-def _runcatch(ui, args):
- def catchterm(*args):
- raise error.SignalInterrupt
-
- for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
- num = getattr(signal, name, None)
- if num: signal.signal(num, catchterm)
-
- try:
- try:
- # enter the debugger before command execution
- if '--debugger' in args:
- pdb.set_trace()
- try:
- return _dispatch(ui, args)
- finally:
- ui.flush()
- except:
- # enter the debugger when we hit an exception
- if '--debugger' in args:
- pdb.post_mortem(sys.exc_info()[2])
- ui.traceback()
- raise
-
- # Global exception handling, alphabetically
- # Mercurial-specific first, followed by built-in and library exceptions
- except error.AmbiguousCommand, inst:
- ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
- (inst.args[0], " ".join(inst.args[1])))
- except error.ConfigError, inst:
- ui.warn(_("hg: %s\n") % inst.args[0])
- except error.LockHeld, inst:
- if inst.errno == errno.ETIMEDOUT:
- reason = _('timed out waiting for lock held by %s') % inst.locker
- else:
- reason = _('lock held by %s') % inst.locker
- ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
- except error.LockUnavailable, inst:
- ui.warn(_("abort: could not lock %s: %s\n") %
- (inst.desc or inst.filename, inst.strerror))
- except error.ParseError, inst:
- if inst.args[0]:
- ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
- commands.help_(ui, inst.args[0])
- else:
- ui.warn(_("hg: %s\n") % inst.args[1])
- commands.help_(ui, 'shortlist')
- except error.RepoError, inst:
- ui.warn(_("abort: %s!\n") % inst)
- except error.ResponseError, inst:
- ui.warn(_("abort: %s") % inst.args[0])
- if not isinstance(inst.args[1], basestring):
- ui.warn(" %r\n" % (inst.args[1],))
- elif not inst.args[1]:
- ui.warn(_(" empty string\n"))
- else:
- ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
- except error.RevlogError, inst:
- ui.warn(_("abort: %s!\n") % inst)
- except error.SignalInterrupt:
- ui.warn(_("killed!\n"))
- except error.UnknownCommand, inst:
- ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
- commands.help_(ui, 'shortlist')
- except util.Abort, inst:
- ui.warn(_("abort: %s\n") % inst)
- except ImportError, inst:
- m = str(inst).split()[-1]
- ui.warn(_("abort: could not import module %s!\n") % m)
- if m in "mpatch bdiff".split():
- ui.warn(_("(did you forget to compile extensions?)\n"))
- elif m in "zlib".split():
- ui.warn(_("(is your Python install correct?)\n"))
- except IOError, inst:
- if hasattr(inst, "code"):
- ui.warn(_("abort: %s\n") % inst)
- elif hasattr(inst, "reason"):
- try: # usually it is in the form (errno, strerror)
- reason = inst.reason.args[1]
- except: # it might be anything, for example a string
- reason = inst.reason
- ui.warn(_("abort: error: %s\n") % reason)
- elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
- if ui.debugflag:
- ui.warn(_("broken pipe\n"))
- elif getattr(inst, "strerror", None):
- if getattr(inst, "filename", None):
- ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
- else:
- ui.warn(_("abort: %s\n") % inst.strerror)
- else:
- raise
- except OSError, inst:
- if getattr(inst, "filename", None):
- ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
- else:
- ui.warn(_("abort: %s\n") % inst.strerror)
- except KeyboardInterrupt:
- try:
- ui.warn(_("interrupted!\n"))
- except IOError, inst:
- if inst.errno == errno.EPIPE:
- if ui.debugflag:
- ui.warn(_("\nbroken pipe\n"))
- else:
- raise
- except MemoryError:
- ui.warn(_("abort: out of memory\n"))
- except SystemExit, inst:
- # Commands shouldn't sys.exit directly, but give a return code.
- # Just in case catch this and and pass exit code to caller.
- return inst.code
- except socket.error, inst:
- ui.warn(_("abort: %s\n") % inst.args[-1])
- except:
- ui.warn(_("** unknown exception encountered, details follow\n"))
- ui.warn(_("** report bug details to "
- "http://mercurial.selenic.com/bts/\n"))
- ui.warn(_("** or mercurial@selenic.com\n"))
- ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
- % util.version())
- ui.warn(_("** Extensions loaded: %s\n")
- % ", ".join([x[0] for x in extensions.extensions()]))
- raise
-
- return -1
-
-def _findrepo(p):
- while not os.path.isdir(os.path.join(p, ".hg")):
- oldp, p = p, os.path.dirname(p)
- if p == oldp:
- return None
-
- return p
-
-def aliasargs(fn):
- if hasattr(fn, 'args'):
- return fn.args
- return []
-
-class cmdalias(object):
- def __init__(self, name, definition, cmdtable):
- self.name = name
- self.definition = definition
- self.args = []
- self.opts = []
- self.help = ''
- self.norepo = True
-
- try:
- cmdutil.findcmd(self.name, cmdtable, True)
- self.shadows = True
- except error.UnknownCommand:
- self.shadows = False
-
- if not self.definition:
- def fn(ui, *args):
- ui.warn(_("no definition for alias '%s'\n") % self.name)
- return 1
- self.fn = fn
-
- return
-
- args = shlex.split(self.definition)
- cmd = args.pop(0)
- opts = []
- help = ''
-
- try:
- self.fn, self.opts, self.help = cmdutil.findcmd(cmd, cmdtable, False)[1]
- self.args = aliasargs(self.fn) + args
- if cmd not in commands.norepo.split(' '):
- self.norepo = False
- except error.UnknownCommand:
- def fn(ui, *args):
- ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
- % (self.name, cmd))
- return 1
- self.fn = fn
- except error.AmbiguousCommand:
- def fn(ui, *args):
- ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
- % (self.name, cmd))
- return 1
- self.fn = fn
-
- def __call__(self, ui, *args, **opts):
- if self.shadows:
- ui.debug(_("alias '%s' shadows command\n") % self.name)
-
- return self.fn(ui, *args, **opts)
-
-def addaliases(ui, cmdtable):
- # aliases are processed after extensions have been loaded, so they
- # may use extension commands. Aliases can also use other alias definitions,
- # but only if they have been defined prior to the current definition.
- for alias, definition in ui.configitems('alias'):
- aliasdef = cmdalias(alias, definition, cmdtable)
- cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
- if aliasdef.norepo:
- commands.norepo += ' %s' % alias
-
-def _parse(ui, args):
- options = {}
- cmdoptions = {}
-
- try:
- args = fancyopts.fancyopts(args, commands.globalopts, options)
- except fancyopts.getopt.GetoptError, inst:
- raise error.ParseError(None, inst)
-
- if args:
- cmd, args = args[0], args[1:]
- aliases, i = cmdutil.findcmd(cmd, commands.table,
- ui.config("ui", "strict"))
- cmd = aliases[0]
- args = aliasargs(i[0]) + args
- defaults = ui.config("defaults", cmd)
- if defaults:
- args = shlex.split(defaults) + args
- c = list(i[1])
- else:
- cmd = None
- c = []
-
- # combine global options into local
- for o in commands.globalopts:
- c.append((o[0], o[1], options[o[1]], o[3]))
-
- try:
- args = fancyopts.fancyopts(args, c, cmdoptions, True)
- except fancyopts.getopt.GetoptError, inst:
- raise error.ParseError(cmd, inst)
-
- # separate global options back out
- for o in commands.globalopts:
- n = o[1]
- options[n] = cmdoptions[n]
- del cmdoptions[n]
-
- return (cmd, cmd and i[0] or None, args, options, cmdoptions)
-
-def _parseconfig(ui, config):
- """parse the --config options from the command line"""
- for cfg in config:
- try:
- name, value = cfg.split('=', 1)
- section, name = name.split('.', 1)
- if not section or not name:
- raise IndexError
- ui.setconfig(section, name, value)
- except (IndexError, ValueError):
- raise util.Abort(_('malformed --config option: %s') % cfg)
-
-def _earlygetopt(aliases, args):
- """Return list of values for an option (or aliases).
-
- The values are listed in the order they appear in args.
- The options and values are removed from args.
- """
- try:
- argcount = args.index("--")
- except ValueError:
- argcount = len(args)
- shortopts = [opt for opt in aliases if len(opt) == 2]
- values = []
- pos = 0
- while pos < argcount:
- if args[pos] in aliases:
- if pos + 1 >= argcount:
- # ignore and let getopt report an error if there is no value
- break
- del args[pos]
- values.append(args.pop(pos))
- argcount -= 2
- elif args[pos][:2] in shortopts:
- # short option can have no following space, e.g. hg log -Rfoo
- values.append(args.pop(pos)[2:])
- argcount -= 1
- else:
- pos += 1
- return values
-
-def runcommand(lui, repo, cmd, fullargs, ui, options, d):
- # run pre-hook, and abort if it fails
- ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
- if ret:
- return ret
- ret = _runcommand(ui, options, cmd, d)
- # run post-hook, passing command result
- hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
- result = ret)
- return ret
-
-_loaded = set()
-def _dispatch(ui, args):
- # read --config before doing anything else
- # (e.g. to change trust settings for reading .hg/hgrc)
- _parseconfig(ui, _earlygetopt(['--config'], args))
-
- # check for cwd
- cwd = _earlygetopt(['--cwd'], args)
- if cwd:
- os.chdir(cwd[-1])
-
- # read the local repository .hgrc into a local ui object
- path = _findrepo(os.getcwd()) or ""
- if not path:
- lui = ui
- if path:
- try:
- lui = ui.copy()
- lui.readconfig(os.path.join(path, ".hg", "hgrc"))
- except IOError:
- pass
-
- # now we can expand paths, even ones in .hg/hgrc
- rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
- if rpath:
- path = lui.expandpath(rpath[-1])
- lui = ui.copy()
- lui.readconfig(os.path.join(path, ".hg", "hgrc"))
-
- extensions.loadall(lui)
- for name, module in extensions.extensions():
- if name in _loaded:
- continue
-
- # setup extensions
- # TODO this should be generalized to scheme, where extensions can
- # redepend on other extensions. then we should toposort them, and
- # do initialization in correct order
- extsetup = getattr(module, 'extsetup', None)
- if extsetup:
- extsetup()
-
- cmdtable = getattr(module, 'cmdtable', {})
- overrides = [cmd for cmd in cmdtable if cmd in commands.table]
- if overrides:
- ui.warn(_("extension '%s' overrides commands: %s\n")
- % (name, " ".join(overrides)))
- commands.table.update(cmdtable)
- _loaded.add(name)
-
- addaliases(lui, commands.table)
-
- # check for fallback encoding
- fallback = lui.config('ui', 'fallbackencoding')
- if fallback:
- encoding.fallbackencoding = fallback
-
- fullargs = args
- cmd, func, args, options, cmdoptions = _parse(lui, args)
-
- if options["config"]:
- raise util.Abort(_("Option --config may not be abbreviated!"))
- if options["cwd"]:
- raise util.Abort(_("Option --cwd may not be abbreviated!"))
- if options["repository"]:
- raise util.Abort(_(
- "Option -R has to be separated from other options (e.g. not -qR) "
- "and --repository may only be abbreviated as --repo!"))
-
- if options["encoding"]:
- encoding.encoding = options["encoding"]
- if options["encodingmode"]:
- encoding.encodingmode = options["encodingmode"]
- if options["time"]:
- def get_times():
- t = os.times()
- if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
- t = (t[0], t[1], t[2], t[3], time.clock())
- return t
- s = get_times()
- def print_time():
- t = get_times()
- ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
- (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
- atexit.register(print_time)
-
- if options['verbose'] or options['debug'] or options['quiet']:
- ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
- ui.setconfig('ui', 'debug', str(bool(options['debug'])))
- ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
- if options['traceback']:
- ui.setconfig('ui', 'traceback', 'on')
- if options['noninteractive']:
- ui.setconfig('ui', 'interactive', 'off')
-
- if options['help']:
- return commands.help_(ui, cmd, options['version'])
- elif options['version']:
- return commands.version_(ui)
- elif not cmd:
- return commands.help_(ui, 'shortlist')
-
- repo = None
- if cmd not in commands.norepo.split():
- try:
- repo = hg.repository(ui, path=path)
- ui = repo.ui
- if not repo.local():
- raise util.Abort(_("repository '%s' is not local") % path)
- ui.setconfig("bundle", "mainreporoot", repo.root)
- except error.RepoError:
- if cmd not in commands.optionalrepo.split():
- if args and not path: # try to infer -R from command args
- repos = map(_findrepo, args)
- guess = repos[0]
- if guess and repos.count(guess) == len(repos):
- return _dispatch(ui, ['--repository', guess] + fullargs)
- if not path:
- raise error.RepoError(_("There is no Mercurial repository"
- " here (.hg not found)"))
- raise
- args.insert(0, repo)
- elif rpath:
- ui.warn("warning: --repository ignored\n")
-
- d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
- return runcommand(lui, repo, cmd, fullargs, ui, options, d)
-
-def _runcommand(ui, options, cmd, cmdfunc):
- def checkargs():
- try:
- return cmdfunc()
- except error.SignatureError:
- raise error.ParseError(cmd, _("invalid arguments"))
-
- if options['profile']:
- format = ui.config('profiling', 'format', default='text')
-
- if not format in ['text', 'kcachegrind']:
- ui.warn(_("unrecognized profiling format '%s'"
- " - Ignored\n") % format)
- format = 'text'
-
- output = ui.config('profiling', 'output')
-
- if output:
- path = os.path.expanduser(output)
- path = ui.expandpath(path)
- ostream = open(path, 'wb')
- else:
- ostream = sys.stderr
-
- try:
- from mercurial import lsprof
- except ImportError:
- raise util.Abort(_(
- 'lsprof not available - install from '
- 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
- p = lsprof.Profiler()
- p.enable(subcalls=True)
- try:
- return checkargs()
- finally:
- p.disable()
-
- if format == 'kcachegrind':
- import lsprofcalltree
- calltree = lsprofcalltree.KCacheGrind(p)
- calltree.output(ostream)
- else:
- # format == 'text'
- stats = lsprof.Stats(p.getstats())
- stats.sort()
- stats.pprint(top=10, file=ostream, climit=5)
-
- if output:
- ostream.close()
- else:
- return checkargs()
diff --git a/sys/src/cmd/hg/mercurial/encoding.py b/sys/src/cmd/hg/mercurial/encoding.py
deleted file mode 100644
index b286cc865..000000000
--- a/sys/src/cmd/hg/mercurial/encoding.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# encoding.py - character transcoding support for Mercurial
-#
-# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import error
-import sys, unicodedata, locale, os
-
-_encodingfixup = {'646': 'ascii', 'ANSI_X3.4-1968': 'ascii'}
-
-try:
- encoding = os.environ.get("HGENCODING")
- if sys.platform == 'darwin' and not encoding:
- # On darwin, getpreferredencoding ignores the locale environment and
- # always returns mac-roman. We override this if the environment is
- # not C (has been customized by the user).
- locale.setlocale(locale.LC_CTYPE, '')
- encoding = locale.getlocale()[1]
- if not encoding:
- encoding = locale.getpreferredencoding() or 'ascii'
- encoding = _encodingfixup.get(encoding, encoding)
-except locale.Error:
- encoding = 'ascii'
-encodingmode = os.environ.get("HGENCODINGMODE", "strict")
-fallbackencoding = 'ISO-8859-1'
-
-def tolocal(s):
- """
- Convert a string from internal UTF-8 to local encoding
-
- All internal strings should be UTF-8 but some repos before the
- implementation of locale support may contain latin1 or possibly
- other character sets. We attempt to decode everything strictly
- using UTF-8, then Latin-1, and failing that, we use UTF-8 and
- replace unknown characters.
- """
- for e in ('UTF-8', fallbackencoding):
- try:
- u = s.decode(e) # attempt strict decoding
- return u.encode(encoding, "replace")
- except LookupError, k:
- raise error.Abort("%s, please check your locale settings" % k)
- except UnicodeDecodeError:
- pass
- u = s.decode("utf-8", "replace") # last ditch
- return u.encode(encoding, "replace")
-
-def fromlocal(s):
- """
- Convert a string from the local character encoding to UTF-8
-
- We attempt to decode strings using the encoding mode set by
- HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
- characters will cause an error message. Other modes include
- 'replace', which replaces unknown characters with a special
- Unicode character, and 'ignore', which drops the character.
- """
- try:
- return s.decode(encoding, encodingmode).encode("utf-8")
- except UnicodeDecodeError, inst:
- sub = s[max(0, inst.start-10):inst.start+10]
- raise error.Abort("decoding near '%s': %s!" % (sub, inst))
- except LookupError, k:
- raise error.Abort("%s, please check your locale settings" % k)
-
-def colwidth(s):
- "Find the column width of a UTF-8 string for display"
- d = s.decode(encoding, 'replace')
- if hasattr(unicodedata, 'east_asian_width'):
- w = unicodedata.east_asian_width
- return sum([w(c) in 'WF' and 2 or 1 for c in d])
- return len(d)
-
diff --git a/sys/src/cmd/hg/mercurial/error.py b/sys/src/cmd/hg/mercurial/error.py
deleted file mode 100644
index f4ed42e33..000000000
--- a/sys/src/cmd/hg/mercurial/error.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# error.py - Mercurial exceptions
-#
-# Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-"""Mercurial exceptions.
-
-This allows us to catch exceptions at higher levels without forcing
-imports.
-"""
-
-# Do not import anything here, please
-
-class RevlogError(Exception):
- pass
-
-class LookupError(RevlogError, KeyError):
- def __init__(self, name, index, message):
- self.name = name
- if isinstance(name, str) and len(name) == 20:
- from node import short
- name = short(name)
- RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
-
- def __str__(self):
- return RevlogError.__str__(self)
-
-class ParseError(Exception):
- """Exception raised on errors in parsing the command line."""
-
-class ConfigError(Exception):
- 'Exception raised when parsing config files'
-
-class RepoError(Exception):
- pass
-
-class CapabilityError(RepoError):
- pass
-
-class LockError(IOError):
- def __init__(self, errno, strerror, filename, desc):
- IOError.__init__(self, errno, strerror, filename)
- self.desc = desc
-
-class LockHeld(LockError):
- def __init__(self, errno, filename, desc, locker):
- LockError.__init__(self, errno, 'Lock held', filename, desc)
- self.locker = locker
-
-class LockUnavailable(LockError):
- pass
-
-class ResponseError(Exception):
- """Raised to print an error with part of output and exit."""
-
-class UnknownCommand(Exception):
- """Exception raised if command is not in the command table."""
-
-class AmbiguousCommand(Exception):
- """Exception raised if command shortcut matches more than one command."""
-
-# derived from KeyboardInterrupt to simplify some breakout code
-class SignalInterrupt(KeyboardInterrupt):
- """Exception raised on SIGTERM and SIGHUP."""
-
-class SignatureError(Exception):
- pass
-
-class Abort(Exception):
- """Raised if a command needs to print an error and exit."""
diff --git a/sys/src/cmd/hg/mercurial/extensions.py b/sys/src/cmd/hg/mercurial/extensions.py
deleted file mode 100644
index 30da1ebd1..000000000
--- a/sys/src/cmd/hg/mercurial/extensions.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# extensions.py - extension handling for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import imp, os
-import util, cmdutil, help
-from i18n import _, gettext
-
-_extensions = {}
-_order = []
-
-def extensions():
- for name in _order:
- module = _extensions[name]
- if module:
- yield name, module
-
-def find(name):
- '''return module with given extension name'''
- try:
- return _extensions[name]
- except KeyError:
- for k, v in _extensions.iteritems():
- if k.endswith('.' + name) or k.endswith('/' + name):
- return v
- raise KeyError(name)
-
-def loadpath(path, module_name):
- module_name = module_name.replace('.', '_')
- path = os.path.expanduser(path)
- if os.path.isdir(path):
- # module/__init__.py style
- d, f = os.path.split(path.rstrip('/'))
- fd, fpath, desc = imp.find_module(f, [d])
- return imp.load_module(module_name, fd, fpath, desc)
- else:
- return imp.load_source(module_name, path)
-
-def load(ui, name, path):
- if name.startswith('hgext.') or name.startswith('hgext/'):
- shortname = name[6:]
- else:
- shortname = name
- if shortname in _extensions:
- return
- _extensions[shortname] = None
- if path:
- # the module will be loaded in sys.modules
- # choose an unique name so that it doesn't
- # conflicts with other modules
- mod = loadpath(path, 'hgext.%s' % name)
- else:
- def importh(name):
- mod = __import__(name)
- components = name.split('.')
- for comp in components[1:]:
- mod = getattr(mod, comp)
- return mod
- try:
- mod = importh("hgext.%s" % name)
- except ImportError:
- mod = importh(name)
- _extensions[shortname] = mod
- _order.append(shortname)
-
- uisetup = getattr(mod, 'uisetup', None)
- if uisetup:
- uisetup(ui)
-
-def loadall(ui):
- result = ui.configitems("extensions")
- for (name, path) in result:
- if path:
- if path[0] == '!':
- continue
- try:
- load(ui, name, path)
- except KeyboardInterrupt:
- raise
- except Exception, inst:
- if path:
- ui.warn(_("*** failed to import extension %s from %s: %s\n")
- % (name, path, inst))
- else:
- ui.warn(_("*** failed to import extension %s: %s\n")
- % (name, inst))
- if ui.traceback():
- return 1
-
-def wrapcommand(table, command, wrapper):
- aliases, entry = cmdutil.findcmd(command, table)
- for alias, e in table.iteritems():
- if e is entry:
- key = alias
- break
-
- origfn = entry[0]
- def wrap(*args, **kwargs):
- return util.checksignature(wrapper)(
- util.checksignature(origfn), *args, **kwargs)
-
- wrap.__doc__ = getattr(origfn, '__doc__')
- wrap.__module__ = getattr(origfn, '__module__')
-
- newentry = list(entry)
- newentry[0] = wrap
- table[key] = tuple(newentry)
- return entry
-
-def wrapfunction(container, funcname, wrapper):
- def wrap(*args, **kwargs):
- return wrapper(origfn, *args, **kwargs)
-
- origfn = getattr(container, funcname)
- setattr(container, funcname, wrap)
- return origfn
-
-def disabled():
- '''find disabled extensions from hgext
- returns a dict of {name: desc}, and the max name length'''
-
- import hgext
- extpath = os.path.dirname(os.path.abspath(hgext.__file__))
-
- try: # might not be a filesystem path
- files = os.listdir(extpath)
- except OSError:
- return None, 0
-
- exts = {}
- maxlength = 0
- for e in files:
-
- if e.endswith('.py'):
- name = e.rsplit('.', 1)[0]
- path = os.path.join(extpath, e)
- else:
- name = e
- path = os.path.join(extpath, e, '__init__.py')
- if not os.path.exists(path):
- continue
-
- if name in exts or name in _order or name == '__init__':
- continue
-
- try:
- file = open(path)
- except IOError:
- continue
- else:
- doc = help.moduledoc(file)
- file.close()
-
- if doc: # extracting localized synopsis
- exts[name] = gettext(doc).splitlines()[0]
- else:
- exts[name] = _('(no help text available)')
-
- if len(name) > maxlength:
- maxlength = len(name)
-
- return exts, maxlength
-
-def enabled():
- '''return a dict of {name: desc} of extensions, and the max name length'''
- exts = {}
- maxlength = 0
- exthelps = []
- for ename, ext in extensions():
- doc = (gettext(ext.__doc__) or _('(no help text available)'))
- ename = ename.split('.')[-1]
- maxlength = max(len(ename), maxlength)
- exts[ename] = doc.splitlines()[0].strip()
-
- return exts, maxlength
diff --git a/sys/src/cmd/hg/mercurial/fancyopts.py b/sys/src/cmd/hg/mercurial/fancyopts.py
deleted file mode 100644
index 5acf0143d..000000000
--- a/sys/src/cmd/hg/mercurial/fancyopts.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# fancyopts.py - better command line parsing
-#
-# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import getopt
-
-def gnugetopt(args, options, longoptions):
- """Parse options mostly like getopt.gnu_getopt.
-
- This is different from getopt.gnu_getopt in that an argument of - will
- become an argument of - instead of vanishing completely.
- """
- extraargs = []
- if '--' in args:
- stopindex = args.index('--')
- extraargs = args[stopindex+1:]
- args = args[:stopindex]
- opts, parseargs = getopt.getopt(args, options, longoptions)
- args = []
- while parseargs:
- arg = parseargs.pop(0)
- if arg and arg[0] == '-' and len(arg) > 1:
- parseargs.insert(0, arg)
- topts, newparseargs = getopt.getopt(parseargs, options, longoptions)
- opts = opts + topts
- parseargs = newparseargs
- else:
- args.append(arg)
- args.extend(extraargs)
- return opts, args
-
-
-def fancyopts(args, options, state, gnu=False):
- """
- read args, parse options, and store options in state
-
- each option is a tuple of:
-
- short option or ''
- long option
- default value
- description
-
- option types include:
-
- boolean or none - option sets variable in state to true
- string - parameter string is stored in state
- list - parameter string is added to a list
- integer - parameter strings is stored as int
- function - call function with parameter
-
- non-option args are returned
- """
- namelist = []
- shortlist = ''
- argmap = {}
- defmap = {}
-
- for short, name, default, comment in options:
- # convert opts to getopt format
- oname = name
- name = name.replace('-', '_')
-
- argmap['-' + short] = argmap['--' + oname] = name
- defmap[name] = default
-
- # copy defaults to state
- if isinstance(default, list):
- state[name] = default[:]
- elif hasattr(default, '__call__'):
- state[name] = None
- else:
- state[name] = default
-
- # does it take a parameter?
- if not (default is None or default is True or default is False):
- if short: short += ':'
- if oname: oname += '='
- if short:
- shortlist += short
- if name:
- namelist.append(oname)
-
- # parse arguments
- if gnu:
- parse = gnugetopt
- else:
- parse = getopt.getopt
- opts, args = parse(args, shortlist, namelist)
-
- # transfer result to state
- for opt, val in opts:
- name = argmap[opt]
- t = type(defmap[name])
- if t is type(fancyopts):
- state[name] = defmap[name](val)
- elif t is type(1):
- state[name] = int(val)
- elif t is type(''):
- state[name] = val
- elif t is type([]):
- state[name].append(val)
- elif t is type(None) or t is type(False):
- state[name] = True
-
- # return unparsed args
- return args
diff --git a/sys/src/cmd/hg/mercurial/filelog.py b/sys/src/cmd/hg/mercurial/filelog.py
deleted file mode 100644
index 01ca4f1b4..000000000
--- a/sys/src/cmd/hg/mercurial/filelog.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# filelog.py - file history class for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import revlog
-
-class filelog(revlog.revlog):
- def __init__(self, opener, path):
- revlog.revlog.__init__(self, opener,
- "/".join(("data", path + ".i")))
-
- def read(self, node):
- t = self.revision(node)
- if not t.startswith('\1\n'):
- return t
- s = t.index('\1\n', 2)
- return t[s+2:]
-
- def _readmeta(self, node):
- t = self.revision(node)
- if not t.startswith('\1\n'):
- return {}
- s = t.index('\1\n', 2)
- mt = t[2:s]
- m = {}
- for l in mt.splitlines():
- k, v = l.split(": ", 1)
- m[k] = v
- return m
-
- def add(self, text, meta, transaction, link, p1=None, p2=None):
- if meta or text.startswith('\1\n'):
- mt = ""
- if meta:
- mt = ["%s: %s\n" % (k, v) for k, v in meta.iteritems()]
- text = "\1\n%s\1\n%s" % ("".join(mt), text)
- return self.addrevision(text, transaction, link, p1, p2)
-
- def renamed(self, node):
- if self.parents(node)[0] != revlog.nullid:
- return False
- m = self._readmeta(node)
- if m and "copy" in m:
- return (m["copy"], revlog.bin(m["copyrev"]))
- return False
-
- def size(self, rev):
- """return the size of a given revision"""
-
- # for revisions with renames, we have to go the slow way
- node = self.node(rev)
- if self.renamed(node):
- return len(self.read(node))
-
- return revlog.revlog.size(self, rev)
-
- def cmp(self, node, text):
- """compare text with a given file revision"""
-
- # for renames, we have to go the slow way
- if self.renamed(node):
- t2 = self.read(node)
- return t2 != text
-
- return revlog.revlog.cmp(self, node, text)
diff --git a/sys/src/cmd/hg/mercurial/filemerge.py b/sys/src/cmd/hg/mercurial/filemerge.py
deleted file mode 100644
index 5431f8a96..000000000
--- a/sys/src/cmd/hg/mercurial/filemerge.py
+++ /dev/null
@@ -1,231 +0,0 @@
-# filemerge.py - file-level merge handling for Mercurial
-#
-# Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import short
-from i18n import _
-import util, simplemerge, match
-import os, tempfile, re, filecmp
-
-def _toolstr(ui, tool, part, default=""):
- return ui.config("merge-tools", tool + "." + part, default)
-
-def _toolbool(ui, tool, part, default=False):
- return ui.configbool("merge-tools", tool + "." + part, default)
-
-_internal = ['internal:' + s
- for s in 'fail local other merge prompt dump'.split()]
-
-def _findtool(ui, tool):
- if tool in _internal:
- return tool
- k = _toolstr(ui, tool, "regkey")
- if k:
- p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
- if p:
- p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
- if p:
- return p
- return util.find_exe(_toolstr(ui, tool, "executable", tool))
-
-def _picktool(repo, ui, path, binary, symlink):
- def check(tool, pat, symlink, binary):
- tmsg = tool
- if pat:
- tmsg += " specified for " + pat
- if not _findtool(ui, tool):
- if pat: # explicitly requested tool deserves a warning
- ui.warn(_("couldn't find merge tool %s\n") % tmsg)
- else: # configured but non-existing tools are more silent
- ui.note(_("couldn't find merge tool %s\n") % tmsg)
- elif symlink and not _toolbool(ui, tool, "symlink"):
- ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
- elif binary and not _toolbool(ui, tool, "binary"):
- ui.warn(_("tool %s can't handle binary\n") % tmsg)
- elif not util.gui() and _toolbool(ui, tool, "gui"):
- ui.warn(_("tool %s requires a GUI\n") % tmsg)
- else:
- return True
- return False
-
- # HGMERGE takes precedence
- hgmerge = os.environ.get("HGMERGE")
- if hgmerge:
- return (hgmerge, hgmerge)
-
- # then patterns
- for pat, tool in ui.configitems("merge-patterns"):
- mf = match.match(repo.root, '', [pat])
- if mf(path) and check(tool, pat, symlink, False):
- toolpath = _findtool(ui, tool)
- return (tool, '"' + toolpath + '"')
-
- # then merge tools
- tools = {}
- for k,v in ui.configitems("merge-tools"):
- t = k.split('.')[0]
- if t not in tools:
- tools[t] = int(_toolstr(ui, t, "priority", "0"))
- names = tools.keys()
- tools = sorted([(-p,t) for t,p in tools.items()])
- uimerge = ui.config("ui", "merge")
- if uimerge:
- if uimerge not in names:
- return (uimerge, uimerge)
- tools.insert(0, (None, uimerge)) # highest priority
- tools.append((None, "hgmerge")) # the old default, if found
- for p,t in tools:
- if check(t, None, symlink, binary):
- toolpath = _findtool(ui, t)
- return (t, '"' + toolpath + '"')
- # internal merge as last resort
- return (not (symlink or binary) and "internal:merge" or None, None)
-
-def _eoltype(data):
- "Guess the EOL type of a file"
- if '\0' in data: # binary
- return None
- if '\r\n' in data: # Windows
- return '\r\n'
- if '\r' in data: # Old Mac
- return '\r'
- if '\n' in data: # UNIX
- return '\n'
- return None # unknown
-
-def _matcheol(file, origfile):
- "Convert EOL markers in a file to match origfile"
- tostyle = _eoltype(open(origfile, "rb").read())
- if tostyle:
- data = open(file, "rb").read()
- style = _eoltype(data)
- if style:
- newdata = data.replace(style, tostyle)
- if newdata != data:
- open(file, "wb").write(newdata)
-
-def filemerge(repo, mynode, orig, fcd, fco, fca):
- """perform a 3-way merge in the working directory
-
- mynode = parent node before merge
- orig = original local filename before merge
- fco = other file context
- fca = ancestor file context
- fcd = local file context for current/destination file
- """
-
- def temp(prefix, ctx):
- pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
- (fd, name) = tempfile.mkstemp(prefix=pre)
- data = repo.wwritedata(ctx.path(), ctx.data())
- f = os.fdopen(fd, "wb")
- f.write(data)
- f.close()
- return name
-
- def isbin(ctx):
- try:
- return util.binary(ctx.data())
- except IOError:
- return False
-
- if not fco.cmp(fcd.data()): # files identical?
- return None
-
- ui = repo.ui
- fd = fcd.path()
- binary = isbin(fcd) or isbin(fco) or isbin(fca)
- symlink = 'l' in fcd.flags() + fco.flags()
- tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
- ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
- (tool, fd, binary, symlink))
-
- if not tool or tool == 'internal:prompt':
- tool = "internal:local"
- if ui.promptchoice(_(" no tool found to merge %s\n"
- "keep (l)ocal or take (o)ther?") % fd,
- (_("&Local"), _("&Other")), 0):
- tool = "internal:other"
- if tool == "internal:local":
- return 0
- if tool == "internal:other":
- repo.wwrite(fd, fco.data(), fco.flags())
- return 0
- if tool == "internal:fail":
- return 1
-
- # do the actual merge
- a = repo.wjoin(fd)
- b = temp("base", fca)
- c = temp("other", fco)
- out = ""
- back = a + ".orig"
- util.copyfile(a, back)
-
- if orig != fco.path():
- ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
- else:
- ui.status(_("merging %s\n") % fd)
-
- ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
-
- # do we attempt to simplemerge first?
- if _toolbool(ui, tool, "premerge", not (binary or symlink)):
- r = simplemerge.simplemerge(ui, a, b, c, quiet=True)
- if not r:
- ui.debug(_(" premerge successful\n"))
- os.unlink(back)
- os.unlink(b)
- os.unlink(c)
- return 0
- util.copyfile(back, a) # restore from backup and try again
-
- env = dict(HG_FILE=fd,
- HG_MY_NODE=short(mynode),
- HG_OTHER_NODE=str(fco.changectx()),
- HG_MY_ISLINK='l' in fcd.flags(),
- HG_OTHER_ISLINK='l' in fco.flags(),
- HG_BASE_ISLINK='l' in fca.flags())
-
- if tool == "internal:merge":
- r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other'])
- elif tool == 'internal:dump':
- a = repo.wjoin(fd)
- util.copyfile(a, a + ".local")
- repo.wwrite(fd + ".other", fco.data(), fco.flags())
- repo.wwrite(fd + ".base", fca.data(), fca.flags())
- return 1 # unresolved
- else:
- args = _toolstr(ui, tool, "args", '$local $base $other')
- if "$output" in args:
- out, a = a, back # read input from backup, write to original
- replace = dict(local=a, base=b, other=c, output=out)
- args = re.sub("\$(local|base|other|output)",
- lambda x: '"%s"' % replace[x.group()[1:]], args)
- r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
-
- if not r and _toolbool(ui, tool, "checkconflicts"):
- if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
- r = 1
-
- if not r and _toolbool(ui, tool, "checkchanged"):
- if filecmp.cmp(repo.wjoin(fd), back):
- if ui.promptchoice(_(" output file %s appears unchanged\n"
- "was merge successful (yn)?") % fd,
- (_("&Yes"), _("&No")), 1):
- r = 1
-
- if _toolbool(ui, tool, "fixeol"):
- _matcheol(repo.wjoin(fd), back)
-
- if r:
- ui.warn(_("merging %s failed!\n") % fd)
- else:
- os.unlink(back)
-
- os.unlink(b)
- os.unlink(c)
- return r
diff --git a/sys/src/cmd/hg/mercurial/graphmod.py b/sys/src/cmd/hg/mercurial/graphmod.py
deleted file mode 100644
index e8f9573c0..000000000
--- a/sys/src/cmd/hg/mercurial/graphmod.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# Revision graph generator for Mercurial
-#
-# Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
-# Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-"""supports walking the history as DAGs suitable for graphical output
-
-The most basic format we use is that of::
-
- (id, type, data, [parentids])
-
-The node and parent ids are arbitrary integers which identify a node in the
-context of the graph returned. Type is a constant specifying the node type.
-Data depends on type.
-"""
-
-from mercurial.node import nullrev
-
-CHANGESET = 'C'
-
-def revisions(repo, start, stop):
- """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
-
- This generator function walks through the revision history from revision
- start to revision stop (which must be less than or equal to start). It
- returns a tuple for each node. The node and parent ids are arbitrary
- integers which identify a node in the context of the graph returned.
- """
- cur = start
- while cur >= stop:
- ctx = repo[cur]
- parents = [p.rev() for p in ctx.parents() if p.rev() != nullrev]
- yield (cur, CHANGESET, ctx, sorted(parents))
- cur -= 1
-
-def filerevs(repo, path, start, stop):
- """file cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
-
- This generator function walks through the revision history of a single
- file from revision start down to revision stop.
- """
- filerev = len(repo.file(path)) - 1
- while filerev >= 0:
- fctx = repo.filectx(path, fileid=filerev)
- parents = [f.linkrev() for f in fctx.parents() if f.path() == path]
- rev = fctx.rev()
- if rev <= start:
- yield (rev, CHANGESET, fctx, sorted(parents))
- if rev <= stop:
- break
- filerev -= 1
-
-def nodes(repo, nodes):
- """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
-
- This generator function walks the given nodes. It only returns parents
- that are in nodes, too.
- """
- include = set(nodes)
- for node in nodes:
- ctx = repo[node]
- parents = [p.rev() for p in ctx.parents() if p.node() in include]
- yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
-
-def colored(dag):
- """annotates a DAG with colored edge information
-
- For each DAG node this function emits tuples::
-
- (id, type, data, (col, color), [(col, nextcol, color)])
-
- with the following new elements:
-
- - Tuple (col, color) with column and color index for the current node
- - A list of tuples indicating the edges between the current node and its
- parents.
- """
- seen = []
- colors = {}
- newcolor = 1
- for (cur, type, data, parents) in dag:
-
- # Compute seen and next
- if cur not in seen:
- seen.append(cur) # new head
- colors[cur] = newcolor
- newcolor += 1
-
- col = seen.index(cur)
- color = colors.pop(cur)
- next = seen[:]
-
- # Add parents to next
- addparents = [p for p in parents if p not in next]
- next[col:col + 1] = addparents
-
- # Set colors for the parents
- for i, p in enumerate(addparents):
- if not i:
- colors[p] = color
- else:
- colors[p] = newcolor
- newcolor += 1
-
- # Add edges to the graph
- edges = []
- for ecol, eid in enumerate(seen):
- if eid in next:
- edges.append((ecol, next.index(eid), colors[eid]))
- elif eid == cur:
- for p in parents:
- edges.append((ecol, next.index(p), colors[p]))
-
- # Yield and move on
- yield (cur, type, data, (col, color), edges)
- seen = next
diff --git a/sys/src/cmd/hg/mercurial/hbisect.py b/sys/src/cmd/hg/mercurial/hbisect.py
deleted file mode 100644
index 5f6389a20..000000000
--- a/sys/src/cmd/hg/mercurial/hbisect.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# changelog bisection for mercurial
-#
-# Copyright 2007 Matt Mackall
-# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
-#
-# Inspired by git bisect, extension skeleton taken from mq.py.
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import os
-from i18n import _
-from node import short, hex
-import util
-
-def bisect(changelog, state):
- """find the next node (if any) for testing during a bisect search.
- returns a (nodes, number, good) tuple.
-
- 'nodes' is the final result of the bisect if 'number' is 0.
- Otherwise 'number' indicates the remaining possible candidates for
- the search and 'nodes' contains the next bisect target.
- 'good' is True if bisect is searching for a first good changeset, False
- if searching for a first bad one.
- """
-
- clparents = changelog.parentrevs
- skip = set([changelog.rev(n) for n in state['skip']])
-
- def buildancestors(bad, good):
- # only the earliest bad revision matters
- badrev = min([changelog.rev(n) for n in bad])
- goodrevs = [changelog.rev(n) for n in good]
- # build ancestors array
- ancestors = [[]] * (len(changelog) + 1) # an extra for [-1]
-
- # clear good revs from array
- for node in goodrevs:
- ancestors[node] = None
- for rev in xrange(len(changelog), -1, -1):
- if ancestors[rev] is None:
- for prev in clparents(rev):
- ancestors[prev] = None
-
- if ancestors[badrev] is None:
- return badrev, None
- return badrev, ancestors
-
- good = 0
- badrev, ancestors = buildancestors(state['bad'], state['good'])
- if not ancestors: # looking for bad to good transition?
- good = 1
- badrev, ancestors = buildancestors(state['good'], state['bad'])
- bad = changelog.node(badrev)
- if not ancestors: # now we're confused
- raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
- % (badrev, short(bad)))
-
- # build children dict
- children = {}
- visit = [badrev]
- candidates = []
- while visit:
- rev = visit.pop(0)
- if ancestors[rev] == []:
- candidates.append(rev)
- for prev in clparents(rev):
- if prev != -1:
- if prev in children:
- children[prev].append(rev)
- else:
- children[prev] = [rev]
- visit.append(prev)
-
- candidates.sort()
- # have we narrowed it down to one entry?
- # or have all other possible candidates besides 'bad' have been skipped?
- tot = len(candidates)
- unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
- if tot == 1 or not unskipped:
- return ([changelog.node(rev) for rev in candidates], 0, good)
- perfect = tot // 2
-
- # find the best node to test
- best_rev = None
- best_len = -1
- poison = set()
- for rev in candidates:
- if rev in poison:
- # poison children
- poison.update(children.get(rev, []))
- continue
-
- a = ancestors[rev] or [rev]
- ancestors[rev] = None
-
- x = len(a) # number of ancestors
- y = tot - x # number of non-ancestors
- value = min(x, y) # how good is this test?
- if value > best_len and rev not in skip:
- best_len = value
- best_rev = rev
- if value == perfect: # found a perfect candidate? quit early
- break
-
- if y < perfect and rev not in skip: # all downhill from here?
- # poison children
- poison.update(children.get(rev, []))
- continue
-
- for c in children.get(rev, []):
- if ancestors[c]:
- ancestors[c] = list(set(ancestors[c] + a))
- else:
- ancestors[c] = a + [c]
-
- assert best_rev is not None
- best_node = changelog.node(best_rev)
-
- return ([best_node], tot, good)
-
-
-def load_state(repo):
- state = {'good': [], 'bad': [], 'skip': []}
- if os.path.exists(repo.join("bisect.state")):
- for l in repo.opener("bisect.state"):
- kind, node = l[:-1].split()
- node = repo.lookup(node)
- if kind not in state:
- raise util.Abort(_("unknown bisect kind %s") % kind)
- state[kind].append(node)
- return state
-
-
-def save_state(repo, state):
- f = repo.opener("bisect.state", "w", atomictemp=True)
- wlock = repo.wlock()
- try:
- for kind in state:
- for node in state[kind]:
- f.write("%s %s\n" % (kind, hex(node)))
- f.rename()
- finally:
- wlock.release()
-
diff --git a/sys/src/cmd/hg/mercurial/help.py b/sys/src/cmd/hg/mercurial/help.py
deleted file mode 100644
index a553ac15e..000000000
--- a/sys/src/cmd/hg/mercurial/help.py
+++ /dev/null
@@ -1,527 +0,0 @@
-# help.py - help data for mercurial
-#
-# Copyright 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import extensions, util
-
-
-def moduledoc(file):
- '''return the top-level python documentation for the given file
-
- Loosely inspired by pydoc.source_synopsis(), but rewritten to handle \'''
- as well as """ and to return the whole text instead of just the synopsis'''
- result = []
-
- line = file.readline()
- while line[:1] == '#' or not line.strip():
- line = file.readline()
- if not line: break
-
- start = line[:3]
- if start == '"""' or start == "'''":
- line = line[3:]
- while line:
- if line.rstrip().endswith(start):
- line = line.split(start)[0]
- if line:
- result.append(line)
- break
- elif not line:
- return None # unmatched delimiter
- result.append(line)
- line = file.readline()
- else:
- return None
-
- return ''.join(result)
-
-def listexts(header, exts, maxlength):
- '''return a text listing of the given extensions'''
- if not exts:
- return ''
- result = '\n%s\n\n' % header
- for name, desc in sorted(exts.iteritems()):
- result += ' %-*s %s\n' % (maxlength + 2, ':%s:' % name, desc)
- return result
-
-def extshelp():
- doc = _(r'''
- Mercurial has the ability to add new features through the use of
- extensions. Extensions may add new commands, add options to
- existing commands, change the default behavior of commands, or
- implement hooks.
-
- Extensions are not loaded by default for a variety of reasons:
- they can increase startup overhead; they may be meant for advanced
- usage only; they may provide potentially dangerous abilities (such
- as letting you destroy or modify history); they might not be ready
- for prime time; or they may alter some usual behaviors of stock
- Mercurial. It is thus up to the user to activate extensions as
- needed.
-
- To enable the "foo" extension, either shipped with Mercurial or in
- the Python search path, create an entry for it in your hgrc, like
- this::
-
- [extensions]
- foo =
-
- You may also specify the full path to an extension::
-
- [extensions]
- myfeature = ~/.hgext/myfeature.py
-
- To explicitly disable an extension enabled in an hgrc of broader
- scope, prepend its path with !::
-
- [extensions]
- # disabling extension bar residing in /path/to/extension/bar.py
- hgext.bar = !/path/to/extension/bar.py
- # ditto, but no path was supplied for extension baz
- hgext.baz = !
- ''')
-
- exts, maxlength = extensions.enabled()
- doc += listexts(_('enabled extensions:'), exts, maxlength)
-
- exts, maxlength = extensions.disabled()
- doc += listexts(_('disabled extensions:'), exts, maxlength)
-
- return doc
-
-helptable = (
- (["dates"], _("Date Formats"),
- _(r'''
- Some commands allow the user to specify a date, e.g.:
-
- - backout, commit, import, tag: Specify the commit date.
- - log, revert, update: Select revision(s) by date.
-
- Many date formats are valid. Here are some examples::
-
- "Wed Dec 6 13:18:29 2006" (local timezone assumed)
- "Dec 6 13:18 -0600" (year assumed, time offset provided)
- "Dec 6 13:18 UTC" (UTC and GMT are aliases for +0000)
- "Dec 6" (midnight)
- "13:18" (today assumed)
- "3:39" (3:39AM assumed)
- "3:39pm" (15:39)
- "2006-12-06 13:18:29" (ISO 8601 format)
- "2006-12-6 13:18"
- "2006-12-6"
- "12-6"
- "12/6"
- "12/6/6" (Dec 6 2006)
-
- Lastly, there is Mercurial's internal format::
-
- "1165432709 0" (Wed Dec 6 13:18:29 2006 UTC)
-
- This is the internal representation format for dates. unixtime is
- the number of seconds since the epoch (1970-01-01 00:00 UTC).
- offset is the offset of the local timezone, in seconds west of UTC
- (negative if the timezone is east of UTC).
-
- The log command also accepts date ranges::
-
- "<{datetime}" - at or before a given date/time
- ">{datetime}" - on or after a given date/time
- "{datetime} to {datetime}" - a date range, inclusive
- "-{days}" - within a given number of days of today
- ''')),
-
- (["patterns"], _("File Name Patterns"),
- _(r'''
- Mercurial accepts several notations for identifying one or more
- files at a time.
-
- By default, Mercurial treats filenames as shell-style extended
- glob patterns.
-
- Alternate pattern notations must be specified explicitly.
-
- To use a plain path name without any pattern matching, start it
- with "path:". These path names must completely match starting at
- the current repository root.
-
- To use an extended glob, start a name with "glob:". Globs are
- rooted at the current directory; a glob such as "``*.c``" will
- only match files in the current directory ending with ".c".
-
- The supported glob syntax extensions are "``**``" to match any
- string across path separators and "{a,b}" to mean "a or b".
-
- To use a Perl/Python regular expression, start a name with "re:".
- Regexp pattern matching is anchored at the root of the repository.
-
- Plain examples::
-
- path:foo/bar a name bar in a directory named foo in the root
- of the repository
- path:path:name a file or directory named "path:name"
-
- Glob examples::
-
- glob:*.c any name ending in ".c" in the current directory
- *.c any name ending in ".c" in the current directory
- **.c any name ending in ".c" in any subdirectory of the
- current directory including itself.
- foo/*.c any name ending in ".c" in the directory foo
- foo/**.c any name ending in ".c" in any subdirectory of foo
- including itself.
-
- Regexp examples::
-
- re:.*\.c$ any name ending in ".c", anywhere in the repository
-
- ''')),
-
- (['environment', 'env'], _('Environment Variables'),
- _(r'''
-HG
- Path to the 'hg' executable, automatically passed when running
- hooks, extensions or external tools. If unset or empty, this is
- the hg executable's name if it's frozen, or an executable named
- 'hg' (with %PATHEXT% [defaulting to COM/EXE/BAT/CMD] extensions on
- Windows) is searched.
-
-HGEDITOR
- This is the name of the editor to run when committing. See EDITOR.
-
- (deprecated, use .hgrc)
-
-HGENCODING
- This overrides the default locale setting detected by Mercurial.
- This setting is used to convert data including usernames,
- changeset descriptions, tag names, and branches. This setting can
- be overridden with the --encoding command-line option.
-
-HGENCODINGMODE
- This sets Mercurial's behavior for handling unknown characters
- while transcoding user input. The default is "strict", which
- causes Mercurial to abort if it can't map a character. Other
- settings include "replace", which replaces unknown characters, and
- "ignore", which drops them. This setting can be overridden with
- the --encodingmode command-line option.
-
-HGMERGE
- An executable to use for resolving merge conflicts. The program
- will be executed with three arguments: local file, remote file,
- ancestor file.
-
- (deprecated, use .hgrc)
-
-HGRCPATH
- A list of files or directories to search for hgrc files. Item
- separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
- platform default search path is used. If empty, only the .hg/hgrc
- from the current repository is read.
-
- For each element in HGRCPATH:
-
- - if it's a directory, all files ending with .rc are added
- - otherwise, the file itself will be added
-
-HGUSER
- This is the string used as the author of a commit. If not set,
- available values will be considered in this order:
-
- - HGUSER (deprecated)
- - hgrc files from the HGRCPATH
- - EMAIL
- - interactive prompt
- - LOGNAME (with '@hostname' appended)
-
- (deprecated, use .hgrc)
-
-EMAIL
- May be used as the author of a commit; see HGUSER.
-
-LOGNAME
- May be used as the author of a commit; see HGUSER.
-
-VISUAL
- This is the name of the editor to use when committing. See EDITOR.
-
-EDITOR
- Sometimes Mercurial needs to open a text file in an editor for a
- user to modify, for example when writing commit messages. The
- editor it uses is determined by looking at the environment
- variables HGEDITOR, VISUAL and EDITOR, in that order. The first
- non-empty one is chosen. If all of them are empty, the editor
- defaults to 'vi'.
-
-PYTHONPATH
- This is used by Python to find imported modules and may need to be
- set appropriately if this Mercurial is not installed system-wide.
- ''')),
-
- (['revs', 'revisions'], _('Specifying Single Revisions'),
- _(r'''
- Mercurial supports several ways to specify individual revisions.
-
- A plain integer is treated as a revision number. Negative integers
- are treated as sequential offsets from the tip, with -1 denoting
- the tip, -2 denoting the revision prior to the tip, and so forth.
-
- A 40-digit hexadecimal string is treated as a unique revision
- identifier.
-
- A hexadecimal string less than 40 characters long is treated as a
- unique revision identifier and is referred to as a short-form
- identifier. A short-form identifier is only valid if it is the
- prefix of exactly one full-length identifier.
-
- Any other string is treated as a tag or branch name. A tag name is
- a symbolic name associated with a revision identifier. A branch
- name denotes the tipmost revision of that branch. Tag and branch
- names must not contain the ":" character.
-
- The reserved name "tip" is a special tag that always identifies
- the most recent revision.
-
- The reserved name "null" indicates the null revision. This is the
- revision of an empty repository, and the parent of revision 0.
-
- The reserved name "." indicates the working directory parent. If
- no working directory is checked out, it is equivalent to null. If
- an uncommitted merge is in progress, "." is the revision of the
- first parent.
- ''')),
-
- (['mrevs', 'multirevs'], _('Specifying Multiple Revisions'),
- _(r'''
- When Mercurial accepts more than one revision, they may be
- specified individually, or provided as a topologically continuous
- range, separated by the ":" character.
-
- The syntax of range notation is [BEGIN]:[END], where BEGIN and END
- are revision identifiers. Both BEGIN and END are optional. If
- BEGIN is not specified, it defaults to revision number 0. If END
- is not specified, it defaults to the tip. The range ":" thus means
- "all revisions".
-
- If BEGIN is greater than END, revisions are treated in reverse
- order.
-
- A range acts as a closed interval. This means that a range of 3:5
- gives 3, 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
- ''')),
-
- (['diffs'], _('Diff Formats'),
- _(r'''
- Mercurial's default format for showing changes between two
- versions of a file is compatible with the unified format of GNU
- diff, which can be used by GNU patch and many other standard
- tools.
-
- While this standard format is often enough, it does not encode the
- following information:
-
- - executable status and other permission bits
- - copy or rename information
- - changes in binary files
- - creation or deletion of empty files
-
- Mercurial also supports the extended diff format from the git VCS
- which addresses these limitations. The git diff format is not
- produced by default because a few widespread tools still do not
- understand this format.
-
- This means that when generating diffs from a Mercurial repository
- (e.g. with "hg export"), you should be careful about things like
- file copies and renames or other things mentioned above, because
- when applying a standard diff to a different repository, this
- extra information is lost. Mercurial's internal operations (like
- push and pull) are not affected by this, because they use an
- internal binary format for communicating changes.
-
- To make Mercurial produce the git extended diff format, use the
- --git option available for many commands, or set 'git = True' in
- the [diff] section of your hgrc. You do not need to set this
- option when importing diffs in this format or using them in the mq
- extension.
- ''')),
- (['templating', 'templates'], _('Template Usage'),
- _(r'''
- Mercurial allows you to customize output of commands through
- templates. You can either pass in a template from the command
- line, via the --template option, or select an existing
- template-style (--style).
-
- You can customize output for any "log-like" command: log,
- outgoing, incoming, tip, parents, heads and glog.
-
- Three styles are packaged with Mercurial: default (the style used
- when no explicit preference is passed), compact and changelog.
- Usage::
-
- $ hg log -r1 --style changelog
-
- A template is a piece of text, with markup to invoke variable
- expansion::
-
- $ hg log -r1 --template "{node}\n"
- b56ce7b07c52de7d5fd79fb89701ea538af65746
-
- Strings in curly braces are called keywords. The availability of
- keywords depends on the exact context of the templater. These
- keywords are usually available for templating a log-like command:
-
- :author: String. The unmodified author of the changeset.
- :branches: String. The name of the branch on which the changeset
- was committed. Will be empty if the branch name was
- default.
- :date: Date information. The date when the changeset was
- committed.
- :desc: String. The text of the changeset description.
- :diffstat: String. Statistics of changes with the following
- format: "modified files: +added/-removed lines"
- :files: List of strings. All files modified, added, or removed
- by this changeset.
- :file_adds: List of strings. Files added by this changeset.
- :file_mods: List of strings. Files modified by this changeset.
- :file_dels: List of strings. Files removed by this changeset.
- :node: String. The changeset identification hash, as a
- 40-character hexadecimal string.
- :parents: List of strings. The parents of the changeset.
- :rev: Integer. The repository-local changeset revision
- number.
- :tags: List of strings. Any tags associated with the
- changeset.
-
- The "date" keyword does not produce human-readable output. If you
- want to use a date in your output, you can use a filter to process
- it. Filters are functions which return a string based on the input
- variable. You can also use a chain of filters to get the desired
- output::
-
- $ hg tip --template "{date|isodate}\n"
- 2008-08-21 18:22 +0000
-
- List of filters:
-
- :addbreaks: Any text. Add an XHTML "<br />" tag before the end of
- every line except the last.
- :age: Date. Returns a human-readable date/time difference
- between the given date/time and the current
- date/time.
- :basename: Any text. Treats the text as a path, and returns the
- last component of the path after splitting by the
- path separator (ignoring trailing separators). For
- example, "foo/bar/baz" becomes "baz" and "foo/bar//"
- becomes "bar".
- :stripdir: Treat the text as path and strip a directory level,
- if possible. For example, "foo" and "foo/bar" becomes
- "foo".
- :date: Date. Returns a date in a Unix date format, including
- the timezone: "Mon Sep 04 15:13:13 2006 0700".
- :domain: Any text. Finds the first string that looks like an
- email address, and extracts just the domain
- component. Example: 'User <user@example.com>' becomes
- 'example.com'.
- :email: Any text. Extracts the first string that looks like
- an email address. Example: 'User <user@example.com>'
- becomes 'user@example.com'.
- :escape: Any text. Replaces the special XML/XHTML characters
- "&", "<" and ">" with XML entities.
- :fill68: Any text. Wraps the text to fit in 68 columns.
- :fill76: Any text. Wraps the text to fit in 76 columns.
- :firstline: Any text. Returns the first line of text.
- :nonempty: Any text. Returns '(none)' if the string is empty.
- :hgdate: Date. Returns the date as a pair of numbers:
- "1157407993 25200" (Unix timestamp, timezone offset).
- :isodate: Date. Returns the date in ISO 8601 format.
- :localdate: Date. Converts a date to local date.
- :obfuscate: Any text. Returns the input text rendered as a
- sequence of XML entities.
- :person: Any text. Returns the text before an email address.
- :rfc822date: Date. Returns a date using the same format used in
- email headers.
- :short: Changeset hash. Returns the short form of a changeset
- hash, i.e. a 12-byte hexadecimal string.
- :shortdate: Date. Returns a date like "2006-09-18".
- :strip: Any text. Strips all leading and trailing whitespace.
- :tabindent: Any text. Returns the text, with every line except
- the first starting with a tab character.
- :urlescape: Any text. Escapes all "special" characters. For
- example, "foo bar" becomes "foo%20bar".
- :user: Any text. Returns the user portion of an email
- address.
- ''')),
-
- (['urls'], _('URL Paths'),
- _(r'''
- Valid URLs are of the form::
-
- local/filesystem/path[#revision]
- file://local/filesystem/path[#revision]
- http://[user[:pass]@]host[:port]/[path][#revision]
- https://[user[:pass]@]host[:port]/[path][#revision]
- ssh://[user[:pass]@]host[:port]/[path][#revision]
-
- Paths in the local filesystem can either point to Mercurial
- repositories or to bundle files (as created by 'hg bundle' or 'hg
- incoming --bundle').
-
- An optional identifier after # indicates a particular branch, tag,
- or changeset to use from the remote repository. See also 'hg help
- revisions'.
-
- Some features, such as pushing to http:// and https:// URLs are
- only possible if the feature is explicitly enabled on the remote
- Mercurial server.
-
- Some notes about using SSH with Mercurial:
-
- - SSH requires an accessible shell account on the destination
- machine and a copy of hg in the remote path or specified with as
- remotecmd.
- - path is relative to the remote user's home directory by default.
- Use an extra slash at the start of a path to specify an absolute
- path::
-
- ssh://example.com//tmp/repository
-
- - Mercurial doesn't use its own compression via SSH; the right
- thing to do is to configure it in your ~/.ssh/config, e.g.::
-
- Host *.mylocalnetwork.example.com
- Compression no
- Host *
- Compression yes
-
- Alternatively specify "ssh -C" as your ssh command in your hgrc
- or with the --ssh command line option.
-
- These URLs can all be stored in your hgrc with path aliases under
- the [paths] section like so::
-
- [paths]
- alias1 = URL1
- alias2 = URL2
- ...
-
- You can then use the alias for any command that uses a URL (for
- example 'hg pull alias1' would pull from the 'alias1' path).
-
- Two path aliases are special because they are used as defaults
- when you do not provide the URL to a command:
-
- default:
- When you create a repository with hg clone, the clone command
- saves the location of the source repository as the new
- repository's 'default' path. This is then used when you omit
- path from push- and pull-like commands (including incoming and
- outgoing).
-
- default-push:
- The push command will look for a path named 'default-push', and
- prefer it over 'default' if both are defined.
- ''')),
- (["extensions"], _("Using additional features"), extshelp),
-)
diff --git a/sys/src/cmd/hg/mercurial/hg.py b/sys/src/cmd/hg/mercurial/hg.py
deleted file mode 100644
index 504bc1256..000000000
--- a/sys/src/cmd/hg/mercurial/hg.py
+++ /dev/null
@@ -1,367 +0,0 @@
-# hg.py - repository classes for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-from lock import release
-import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
-import lock, util, extensions, error
-import merge as _merge
-import verify as _verify
-import errno, os, shutil
-
-def _local(path):
- return (os.path.isfile(util.drop_scheme('file', path)) and
- bundlerepo or localrepo)
-
-def parseurl(url, revs=[]):
- '''parse url#branch, returning url, branch + revs'''
-
- if '#' not in url:
- return url, (revs or None), revs and revs[-1] or None
-
- url, branch = url.split('#', 1)
- checkout = revs and revs[-1] or branch
- return url, (revs or []) + [branch], checkout
-
-schemes = {
- 'bundle': bundlerepo,
- 'file': _local,
- 'http': httprepo,
- 'https': httprepo,
- 'ssh': sshrepo,
- 'static-http': statichttprepo,
-}
-
-def _lookup(path):
- scheme = 'file'
- if path:
- c = path.find(':')
- if c > 0:
- scheme = path[:c]
- thing = schemes.get(scheme) or schemes['file']
- try:
- return thing(path)
- except TypeError:
- return thing
-
-def islocal(repo):
- '''return true if repo or path is local'''
- if isinstance(repo, str):
- try:
- return _lookup(repo).islocal(repo)
- except AttributeError:
- return False
- return repo.local()
-
-def repository(ui, path='', create=False):
- """return a repository object for the specified path"""
- repo = _lookup(path).instance(ui, path, create)
- ui = getattr(repo, "ui", ui)
- for name, module in extensions.extensions():
- hook = getattr(module, 'reposetup', None)
- if hook:
- hook(ui, repo)
- return repo
-
-def defaultdest(source):
- '''return default destination of clone if none is given'''
- return os.path.basename(os.path.normpath(source))
-
-def localpath(path):
- if path.startswith('file://localhost/'):
- return path[16:]
- if path.startswith('file://'):
- return path[7:]
- if path.startswith('file:'):
- return path[5:]
- return path
-
-def share(ui, source, dest=None, update=True):
- '''create a shared repository'''
-
- if not islocal(source):
- raise util.Abort(_('can only share local repositories'))
-
- if not dest:
- dest = os.path.basename(source)
- else:
- dest = ui.expandpath(dest)
-
- if isinstance(source, str):
- origsource = ui.expandpath(source)
- source, rev, checkout = parseurl(origsource, '')
- srcrepo = repository(ui, source)
- else:
- srcrepo = source
- origsource = source = srcrepo.url()
- checkout = None
-
- sharedpath = srcrepo.sharedpath # if our source is already sharing
-
- root = os.path.realpath(dest)
- roothg = os.path.join(root, '.hg')
-
- if os.path.exists(roothg):
- raise util.Abort(_('destination already exists'))
-
- if not os.path.isdir(root):
- os.mkdir(root)
- os.mkdir(roothg)
-
- requirements = ''
- try:
- requirements = srcrepo.opener('requires').read()
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
-
- requirements += 'shared\n'
- file(os.path.join(roothg, 'requires'), 'w').write(requirements)
- file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
-
- default = srcrepo.ui.config('paths', 'default')
- if default:
- f = file(os.path.join(roothg, 'hgrc'), 'w')
- f.write('[paths]\ndefault = %s\n' % default)
- f.close()
-
- r = repository(ui, root)
-
- if update:
- r.ui.status(_("updating working directory\n"))
- if update is not True:
- checkout = update
- for test in (checkout, 'default', 'tip'):
- try:
- uprev = r.lookup(test)
- break
- except LookupError:
- continue
- _update(r, uprev)
-
-def clone(ui, source, dest=None, pull=False, rev=None, update=True,
- stream=False):
- """Make a copy of an existing repository.
-
- Create a copy of an existing repository in a new directory. The
- source and destination are URLs, as passed to the repository
- function. Returns a pair of repository objects, the source and
- newly created destination.
-
- The location of the source is added to the new repository's
- .hg/hgrc file, as the default to be used for future pulls and
- pushes.
-
- If an exception is raised, the partly cloned/updated destination
- repository will be deleted.
-
- Arguments:
-
- source: repository object or URL
-
- dest: URL of destination repository to create (defaults to base
- name of source repository)
-
- pull: always pull from source repository, even in local case
-
- stream: stream raw data uncompressed from repository (fast over
- LAN, slow over WAN)
-
- rev: revision to clone up to (implies pull=True)
-
- update: update working directory after clone completes, if
- destination is local repository (True means update to default rev,
- anything else is treated as a revision)
- """
-
- if isinstance(source, str):
- origsource = ui.expandpath(source)
- source, rev, checkout = parseurl(origsource, rev)
- src_repo = repository(ui, source)
- else:
- src_repo = source
- origsource = source = src_repo.url()
- checkout = rev and rev[-1] or None
-
- if dest is None:
- dest = defaultdest(source)
- ui.status(_("destination directory: %s\n") % dest)
- else:
- dest = ui.expandpath(dest)
-
- dest = localpath(dest)
- source = localpath(source)
-
- if os.path.exists(dest):
- if not os.path.isdir(dest):
- raise util.Abort(_("destination '%s' already exists") % dest)
- elif os.listdir(dest):
- raise util.Abort(_("destination '%s' is not empty") % dest)
-
- class DirCleanup(object):
- def __init__(self, dir_):
- self.rmtree = shutil.rmtree
- self.dir_ = dir_
- def close(self):
- self.dir_ = None
- def cleanup(self):
- if self.dir_:
- self.rmtree(self.dir_, True)
-
- src_lock = dest_lock = dir_cleanup = None
- try:
- if islocal(dest):
- dir_cleanup = DirCleanup(dest)
-
- abspath = origsource
- copy = False
- if src_repo.cancopy() and islocal(dest):
- abspath = os.path.abspath(util.drop_scheme('file', origsource))
- copy = not pull and not rev
-
- if copy:
- try:
- # we use a lock here because if we race with commit, we
- # can end up with extra data in the cloned revlogs that's
- # not pointed to by changesets, thus causing verify to
- # fail
- src_lock = src_repo.lock(wait=False)
- except error.LockError:
- copy = False
-
- if copy:
- src_repo.hook('preoutgoing', throw=True, source='clone')
- hgdir = os.path.realpath(os.path.join(dest, ".hg"))
- if not os.path.exists(dest):
- os.mkdir(dest)
- else:
- # only clean up directories we create ourselves
- dir_cleanup.dir_ = hgdir
- try:
- dest_path = hgdir
- os.mkdir(dest_path)
- except OSError, inst:
- if inst.errno == errno.EEXIST:
- dir_cleanup.close()
- raise util.Abort(_("destination '%s' already exists")
- % dest)
- raise
-
- for f in src_repo.store.copylist():
- src = os.path.join(src_repo.path, f)
- dst = os.path.join(dest_path, f)
- dstbase = os.path.dirname(dst)
- if dstbase and not os.path.exists(dstbase):
- os.mkdir(dstbase)
- if os.path.exists(src):
- if dst.endswith('data'):
- # lock to avoid premature writing to the target
- dest_lock = lock.lock(os.path.join(dstbase, "lock"))
- util.copyfiles(src, dst)
-
- # we need to re-init the repo after manually copying the data
- # into it
- dest_repo = repository(ui, dest)
- src_repo.hook('outgoing', source='clone', node='0'*40)
- else:
- try:
- dest_repo = repository(ui, dest, create=True)
- except OSError, inst:
- if inst.errno == errno.EEXIST:
- dir_cleanup.close()
- raise util.Abort(_("destination '%s' already exists")
- % dest)
- raise
-
- revs = None
- if rev:
- if 'lookup' not in src_repo.capabilities:
- raise util.Abort(_("src repository does not support "
- "revision lookup and so doesn't "
- "support clone by revision"))
- revs = [src_repo.lookup(r) for r in rev]
- checkout = revs[0]
- if dest_repo.local():
- dest_repo.clone(src_repo, heads=revs, stream=stream)
- elif src_repo.local():
- src_repo.push(dest_repo, revs=revs)
- else:
- raise util.Abort(_("clone from remote to remote not supported"))
-
- if dir_cleanup:
- dir_cleanup.close()
-
- if dest_repo.local():
- fp = dest_repo.opener("hgrc", "w", text=True)
- fp.write("[paths]\n")
- fp.write("default = %s\n" % abspath)
- fp.close()
-
- dest_repo.ui.setconfig('paths', 'default', abspath)
-
- if update:
- dest_repo.ui.status(_("updating working directory\n"))
- if update is not True:
- checkout = update
- for test in (checkout, 'default', 'tip'):
- try:
- uprev = dest_repo.lookup(test)
- break
- except:
- continue
- _update(dest_repo, uprev)
-
- return src_repo, dest_repo
- finally:
- release(src_lock, dest_lock)
- if dir_cleanup is not None:
- dir_cleanup.cleanup()
-
-def _showstats(repo, stats):
- stats = ((stats[0], _("updated")),
- (stats[1], _("merged")),
- (stats[2], _("removed")),
- (stats[3], _("unresolved")))
- note = ", ".join([_("%d files %s") % s for s in stats])
- repo.ui.status("%s\n" % note)
-
-def update(repo, node):
- """update the working directory to node, merging linear changes"""
- stats = _merge.update(repo, node, False, False, None)
- _showstats(repo, stats)
- if stats[3]:
- repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
- return stats[3] > 0
-
-# naming conflict in clone()
-_update = update
-
-def clean(repo, node, show_stats=True):
- """forcibly switch the working directory to node, clobbering changes"""
- stats = _merge.update(repo, node, False, True, None)
- if show_stats: _showstats(repo, stats)
- return stats[3] > 0
-
-def merge(repo, node, force=None, remind=True):
- """branch merge with node, resolving changes"""
- stats = _merge.update(repo, node, True, force, False)
- _showstats(repo, stats)
- if stats[3]:
- repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
- "or 'hg up --clean' to abandon\n"))
- elif remind:
- repo.ui.status(_("(branch merge, don't forget to commit)\n"))
- return stats[3] > 0
-
-def revert(repo, node, choose):
- """revert changes to revision in node without updating dirstate"""
- return _merge.update(repo, node, False, True, choose)[3] > 0
-
-def verify(repo):
- """verify the consistency of a repository"""
- return _verify.verify(repo)
diff --git a/sys/src/cmd/hg/mercurial/hook.py b/sys/src/cmd/hg/mercurial/hook.py
deleted file mode 100644
index c5b536df9..000000000
--- a/sys/src/cmd/hg/mercurial/hook.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# hook.py - hook support for mercurial
-#
-# Copyright 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import os, sys
-import extensions, util
-
-def _pythonhook(ui, repo, name, hname, funcname, args, throw):
- '''call python hook. hook is callable object, looked up as
- name in python module. if callable returns "true", hook
- 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.'''
-
- ui.note(_("calling hook %s: %s\n") % (hname, funcname))
- obj = funcname
- if not hasattr(obj, '__call__'):
- d = funcname.rfind('.')
- if d == -1:
- raise util.Abort(_('%s hook is invalid ("%s" not in '
- 'a module)') % (hname, funcname))
- modname = funcname[:d]
- oldpaths = sys.path[:]
- if hasattr(sys, "frozen"):
- # binary installs require sys.path manipulation
- path, name = os.path.split(modname)
- if path and name:
- sys.path.append(path)
- modname = name
- try:
- obj = __import__(modname)
- except ImportError:
- try:
- # extensions are loaded with hgext_ prefix
- obj = __import__("hgext_%s" % modname)
- except ImportError:
- raise util.Abort(_('%s hook is invalid '
- '(import of "%s" failed)') %
- (hname, modname))
- sys.path = oldpaths
- try:
- for p in funcname.split('.')[1:]:
- obj = getattr(obj, p)
- except AttributeError:
- raise util.Abort(_('%s hook is invalid '
- '("%s" is not defined)') %
- (hname, funcname))
- if not hasattr(obj, '__call__'):
- raise util.Abort(_('%s hook is invalid '
- '("%s" is not callable)') %
- (hname, funcname))
- try:
- r = obj(ui=ui, repo=repo, hooktype=name, **args)
- except KeyboardInterrupt:
- raise
- except Exception, exc:
- if isinstance(exc, util.Abort):
- ui.warn(_('error: %s hook failed: %s\n') %
- (hname, exc.args[0]))
- else:
- ui.warn(_('error: %s hook raised an exception: '
- '%s\n') % (hname, exc))
- if throw:
- raise
- ui.traceback()
- return True
- if r:
- if throw:
- raise util.Abort(_('%s hook failed') % hname)
- ui.warn(_('warning: %s hook failed\n') % hname)
- return r
-
-def _exthook(ui, repo, name, cmd, args, throw):
- ui.note(_("running hook %s: %s\n") % (name, cmd))
-
- env = {}
- for k, v in args.iteritems():
- if hasattr(v, '__call__'):
- v = v()
- env['HG_' + k.upper()] = v
-
- if repo:
- cwd = repo.root
- else:
- cwd = os.getcwd()
- r = util.system(cmd, environ=env, cwd=cwd)
- if r:
- desc, r = util.explain_exit(r)
- if throw:
- raise util.Abort(_('%s hook %s') % (name, desc))
- ui.warn(_('warning: %s hook %s\n') % (name, desc))
- return r
-
-_redirect = False
-def redirect(state):
- global _redirect
- _redirect = state
-
-def hook(ui, repo, name, throw=False, **args):
- r = False
-
- if _redirect:
- # temporarily redirect stdout to stderr
- oldstdout = os.dup(sys.__stdout__.fileno())
- os.dup2(sys.__stderr__.fileno(), sys.__stdout__.fileno())
-
- try:
- for hname, cmd in ui.configitems('hooks'):
- if hname.split('.')[0] != name or not cmd:
- continue
- if hasattr(cmd, '__call__'):
- r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
- elif cmd.startswith('python:'):
- if cmd.count(':') >= 2:
- path, cmd = cmd[7:].rsplit(':', 1)
- mod = extensions.loadpath(path, 'hghook.%s' % hname)
- hookfn = getattr(mod, cmd)
- else:
- hookfn = cmd[7:].strip()
- r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
- else:
- r = _exthook(ui, repo, hname, cmd, args, throw) or r
- finally:
- if _redirect:
- os.dup2(oldstdout, sys.__stdout__.fileno())
- os.close(oldstdout)
-
- return r
diff --git a/sys/src/cmd/hg/mercurial/httprepo.py b/sys/src/cmd/hg/mercurial/httprepo.py
deleted file mode 100644
index a766bf07a..000000000
--- a/sys/src/cmd/hg/mercurial/httprepo.py
+++ /dev/null
@@ -1,258 +0,0 @@
-# httprepo.py - HTTP repository proxy classes for mercurial
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import bin, hex, nullid
-from i18n import _
-import repo, changegroup, statichttprepo, error, url, util
-import os, urllib, urllib2, urlparse, zlib, httplib
-import errno, socket
-
-def zgenerator(f):
- zd = zlib.decompressobj()
- try:
- for chunk in util.filechunkiter(f):
- yield zd.decompress(chunk)
- except httplib.HTTPException:
- raise IOError(None, _('connection ended unexpectedly'))
- yield zd.flush()
-
-class httprepository(repo.repository):
- def __init__(self, ui, path):
- self.path = path
- self.caps = None
- self.handler = None
- scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
- if query or frag:
- raise util.Abort(_('unsupported URL component: "%s"') %
- (query or frag))
-
- # urllib cannot handle URLs with embedded user or passwd
- self._url, authinfo = url.getauthinfo(path)
-
- self.ui = ui
- self.ui.debug(_('using %s\n') % self._url)
-
- self.urlopener = url.opener(ui, authinfo)
-
- def __del__(self):
- for h in self.urlopener.handlers:
- h.close()
- if hasattr(h, "close_all"):
- h.close_all()
-
- def url(self):
- return self.path
-
- # look up capabilities only when needed
-
- def get_caps(self):
- if self.caps is None:
- try:
- self.caps = set(self.do_read('capabilities').split())
- except error.RepoError:
- self.caps = set()
- self.ui.debug(_('capabilities: %s\n') %
- (' '.join(self.caps or ['none'])))
- return self.caps
-
- capabilities = property(get_caps)
-
- def lock(self):
- raise util.Abort(_('operation not supported over http'))
-
- def do_cmd(self, cmd, **args):
- data = args.pop('data', None)
- headers = args.pop('headers', {})
- self.ui.debug(_("sending %s command\n") % cmd)
- q = {"cmd": cmd}
- q.update(args)
- qs = '?%s' % urllib.urlencode(q)
- cu = "%s%s" % (self._url, qs)
- try:
- if data:
- self.ui.debug(_("sending %s bytes\n") % len(data))
- resp = self.urlopener.open(urllib2.Request(cu, data, headers))
- except urllib2.HTTPError, inst:
- if inst.code == 401:
- raise util.Abort(_('authorization failed'))
- raise
- except httplib.HTTPException, inst:
- self.ui.debug(_('http error while sending %s command\n') % cmd)
- self.ui.traceback()
- raise IOError(None, inst)
- except IndexError:
- # this only happens with Python 2.3, later versions raise URLError
- raise util.Abort(_('http error, possibly caused by proxy setting'))
- # record the url we got redirected to
- resp_url = resp.geturl()
- if resp_url.endswith(qs):
- resp_url = resp_url[:-len(qs)]
- if self._url != resp_url:
- self.ui.status(_('real URL is %s\n') % resp_url)
- self._url = resp_url
- try:
- proto = resp.getheader('content-type')
- except AttributeError:
- proto = resp.headers['content-type']
-
- safeurl = url.hidepassword(self._url)
- # accept old "text/plain" and "application/hg-changegroup" for now
- if not (proto.startswith('application/mercurial-') or
- proto.startswith('text/plain') or
- proto.startswith('application/hg-changegroup')):
- self.ui.debug(_("requested URL: '%s'\n") % url.hidepassword(cu))
- raise error.RepoError(_("'%s' does not appear to be an hg repository")
- % safeurl)
-
- if proto.startswith('application/mercurial-'):
- try:
- version = proto.split('-', 1)[1]
- version_info = tuple([int(n) for n in version.split('.')])
- except ValueError:
- raise error.RepoError(_("'%s' sent a broken Content-Type "
- "header (%s)") % (safeurl, proto))
- if version_info > (0, 1):
- raise error.RepoError(_("'%s' uses newer protocol %s") %
- (safeurl, version))
-
- return resp
-
- def do_read(self, cmd, **args):
- fp = self.do_cmd(cmd, **args)
- try:
- return fp.read()
- finally:
- # if using keepalive, allow connection to be reused
- fp.close()
-
- def lookup(self, key):
- self.requirecap('lookup', _('look up remote revision'))
- d = self.do_cmd("lookup", key = key).read()
- success, data = d[:-1].split(' ', 1)
- if int(success):
- return bin(data)
- raise error.RepoError(data)
-
- def heads(self):
- d = self.do_read("heads")
- try:
- return map(bin, d[:-1].split(" "))
- except:
- raise error.ResponseError(_("unexpected response:"), d)
-
- def branchmap(self):
- d = self.do_read("branchmap")
- try:
- branchmap = {}
- for branchpart in d.splitlines():
- branchheads = branchpart.split(' ')
- branchname = urllib.unquote(branchheads[0])
- branchheads = [bin(x) for x in branchheads[1:]]
- branchmap[branchname] = branchheads
- return branchmap
- except:
- raise error.ResponseError(_("unexpected response:"), d)
-
- def branches(self, nodes):
- n = " ".join(map(hex, nodes))
- d = self.do_read("branches", nodes=n)
- try:
- br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
- return br
- except:
- raise error.ResponseError(_("unexpected response:"), d)
-
- def between(self, pairs):
- batch = 8 # avoid giant requests
- r = []
- for i in xrange(0, len(pairs), batch):
- n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
- d = self.do_read("between", pairs=n)
- try:
- r += [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
- except:
- raise error.ResponseError(_("unexpected response:"), d)
- return r
-
- def changegroup(self, nodes, kind):
- n = " ".join(map(hex, nodes))
- f = self.do_cmd("changegroup", roots=n)
- return util.chunkbuffer(zgenerator(f))
-
- def changegroupsubset(self, bases, heads, source):
- self.requirecap('changegroupsubset', _('look up remote changes'))
- baselst = " ".join([hex(n) for n in bases])
- headlst = " ".join([hex(n) for n in heads])
- f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
- return util.chunkbuffer(zgenerator(f))
-
- def unbundle(self, cg, heads, source):
- # have to stream bundle to a temp file because we do not have
- # http 1.1 chunked transfer.
-
- type = ""
- types = self.capable('unbundle')
- # servers older than d1b16a746db6 will send 'unbundle' as a
- # boolean capability
- try:
- types = types.split(',')
- except AttributeError:
- types = [""]
- if types:
- for x in types:
- if x in changegroup.bundletypes:
- type = x
- break
-
- tempname = changegroup.writebundle(cg, None, type)
- fp = url.httpsendfile(tempname, "rb")
- try:
- try:
- resp = self.do_read(
- 'unbundle', data=fp,
- headers={'Content-Type': 'application/octet-stream'},
- heads=' '.join(map(hex, heads)))
- resp_code, output = resp.split('\n', 1)
- try:
- ret = int(resp_code)
- except ValueError, err:
- raise error.ResponseError(
- _('push failed (unexpected response):'), resp)
- self.ui.write(output)
- return ret
- except socket.error, err:
- if err[0] in (errno.ECONNRESET, errno.EPIPE):
- raise util.Abort(_('push failed: %s') % err[1])
- raise util.Abort(err[1])
- finally:
- fp.close()
- os.unlink(tempname)
-
- def stream_out(self):
- return self.do_cmd('stream_out')
-
-class httpsrepository(httprepository):
- def __init__(self, ui, path):
- if not url.has_https:
- raise util.Abort(_('Python support for SSL and HTTPS '
- 'is not installed'))
- httprepository.__init__(self, ui, path)
-
-def instance(ui, path, create):
- if create:
- raise util.Abort(_('cannot create new http repository'))
- try:
- if path.startswith('https:'):
- inst = httpsrepository(ui, path)
- else:
- inst = httprepository(ui, path)
- inst.between([(nullid, nullid)])
- return inst
- except error.RepoError:
- ui.note('(falling back to static-http)\n')
- return statichttprepo.instance(ui, "static-" + path, create)
diff --git a/sys/src/cmd/hg/mercurial/i18n.py b/sys/src/cmd/hg/mercurial/i18n.py
deleted file mode 100644
index c8ef2e9a5..000000000
--- a/sys/src/cmd/hg/mercurial/i18n.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# i18n.py - internationalization support for mercurial
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import encoding
-import gettext, sys, os
-
-# modelled after templater.templatepath:
-if hasattr(sys, 'frozen'):
- module = sys.executable
-else:
- module = __file__
-
-base = os.path.dirname(module)
-for dir in ('.', '..'):
- localedir = os.path.normpath(os.path.join(base, dir, 'locale'))
- if os.path.isdir(localedir):
- break
-
-t = gettext.translation('hg', localedir, fallback=True)
-
-def gettext(message):
- """Translate message.
-
- The message is looked up in the catalog to get a Unicode string,
- which is encoded in the local encoding before being returned.
-
- Important: message is restricted to characters in the encoding
- given by sys.getdefaultencoding() which is most likely 'ascii'.
- """
- # If message is None, t.ugettext will return u'None' as the
- # translation whereas our callers expect us to return None.
- if message is None:
- return message
-
- u = t.ugettext(message)
- try:
- # encoding.tolocal cannot be used since it will first try to
- # decode the Unicode string. Calling u.decode(enc) really
- # means u.encode(sys.getdefaultencoding()).decode(enc). Since
- # the Python encoding defaults to 'ascii', this fails if the
- # translated string use non-ASCII characters.
- return u.encode(encoding.encoding, "replace")
- except LookupError:
- # An unknown encoding results in a LookupError.
- return message
-
-_ = gettext
-
diff --git a/sys/src/cmd/hg/mercurial/ignore.py b/sys/src/cmd/hg/mercurial/ignore.py
deleted file mode 100644
index 72532ea5d..000000000
--- a/sys/src/cmd/hg/mercurial/ignore.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# ignore.py - ignored file handling for mercurial
-#
-# Copyright 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import util, match
-import re
-
-_commentre = None
-
-def ignorepats(lines):
- '''parse lines (iterable) of .hgignore text, returning a tuple of
- (patterns, parse errors). These patterns should be given to compile()
- to be validated and converted into a match function.'''
- syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
- syntax = 'relre:'
- patterns = []
- warnings = []
-
- for line in lines:
- if "#" in line:
- global _commentre
- if not _commentre:
- _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
- # remove comments prefixed by an even number of escapes
- line = _commentre.sub(r'\1', line)
- # fixup properly escaped comments that survived the above
- line = line.replace("\\#", "#")
- line = line.rstrip()
- if not line:
- continue
-
- if line.startswith('syntax:'):
- s = line[7:].strip()
- try:
- syntax = syntaxes[s]
- except KeyError:
- warnings.append(_("ignoring invalid syntax '%s'") % s)
- continue
- pat = syntax + line
- for s, rels in syntaxes.iteritems():
- if line.startswith(rels):
- pat = line
- break
- elif line.startswith(s+':'):
- pat = rels + line[len(s)+1:]
- break
- patterns.append(pat)
-
- return patterns, warnings
-
-def ignore(root, files, warn):
- '''return matcher covering patterns in 'files'.
-
- the files parsed for patterns include:
- .hgignore in the repository root
- any additional files specified in the [ui] section of ~/.hgrc
-
- trailing white space is dropped.
- the escape character is backslash.
- comments start with #.
- empty lines are skipped.
-
- lines can be of the following formats:
-
- syntax: regexp # defaults following lines to non-rooted regexps
- syntax: glob # defaults following lines to non-rooted globs
- re:pattern # non-rooted regular expression
- glob:pattern # non-rooted glob
- pattern # pattern of the current default type'''
-
- pats = {}
- for f in files:
- try:
- pats[f] = []
- fp = open(f)
- pats[f], warnings = ignorepats(fp)
- for warning in warnings:
- warn("%s: %s\n" % (f, warning))
- except IOError, inst:
- if f != files[0]:
- warn(_("skipping unreadable ignore file '%s': %s\n") %
- (f, inst.strerror))
-
- allpats = []
- [allpats.extend(patlist) for patlist in pats.values()]
- if not allpats:
- return util.never
-
- try:
- ignorefunc = match.match(root, '', [], allpats)
- except util.Abort:
- # Re-raise an exception where the src is the right file
- for f, patlist in pats.iteritems():
- try:
- match.match(root, '', [], patlist)
- except util.Abort, inst:
- raise util.Abort('%s: %s' % (f, inst[0]))
-
- return ignorefunc
diff --git a/sys/src/cmd/hg/mercurial/keepalive.py b/sys/src/cmd/hg/mercurial/keepalive.py
deleted file mode 100644
index aa19ffbed..000000000
--- a/sys/src/cmd/hg/mercurial/keepalive.py
+++ /dev/null
@@ -1,671 +0,0 @@
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc.,
-# 59 Temple Place, Suite 330,
-# Boston, MA 02111-1307 USA
-
-# This file is part of urlgrabber, a high-level cross-protocol url-grabber
-# Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
-
-# Modified by Benoit Boissinot:
-# - fix for digest auth (inspired from urllib2.py @ Python v2.4)
-# Modified by Dirkjan Ochtman:
-# - import md5 function from a local util module
-# Modified by Martin Geisler:
-# - moved md5 function from local util module to this module
-
-"""An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive.
-
->>> import urllib2
->>> from keepalive import HTTPHandler
->>> keepalive_handler = HTTPHandler()
->>> opener = urllib2.build_opener(keepalive_handler)
->>> urllib2.install_opener(opener)
->>>
->>> fo = urllib2.urlopen('http://www.python.org')
-
-If a connection to a given host is requested, and all of the existing
-connections are still in use, another connection will be opened. If
-the handler tries to use an existing connection but it fails in some
-way, it will be closed and removed from the pool.
-
-To remove the handler, simply re-run build_opener with no arguments, and
-install that opener.
-
-You can explicitly close connections by using the close_connection()
-method of the returned file-like object (described below) or you can
-use the handler methods:
-
- close_connection(host)
- close_all()
- open_connections()
-
-NOTE: using the close_connection and close_all methods of the handler
-should be done with care when using multiple threads.
- * there is nothing that prevents another thread from creating new
- connections immediately after connections are closed
- * no checks are done to prevent in-use connections from being closed
-
->>> keepalive_handler.close_all()
-
-EXTRA ATTRIBUTES AND METHODS
-
- Upon a status of 200, the object returned has a few additional
- attributes and methods, which should not be used if you want to
- remain consistent with the normal urllib2-returned objects:
-
- close_connection() - close the connection to the host
- readlines() - you know, readlines()
- status - the return status (ie 404)
- reason - english translation of status (ie 'File not found')
-
- If you want the best of both worlds, use this inside an
- AttributeError-catching try:
-
- >>> try: status = fo.status
- >>> except AttributeError: status = None
-
- Unfortunately, these are ONLY there if status == 200, so it's not
- easy to distinguish between non-200 responses. The reason is that
- urllib2 tries to do clever things with error codes 301, 302, 401,
- and 407, and it wraps the object upon return.
-
- For python versions earlier than 2.4, you can avoid this fancy error
- handling by setting the module-level global HANDLE_ERRORS to zero.
- You see, prior to 2.4, it's the HTTP Handler's job to determine what
- to handle specially, and what to just pass up. HANDLE_ERRORS == 0
- means "pass everything up". In python 2.4, however, this job no
- longer belongs to the HTTP Handler and is now done by a NEW handler,
- HTTPErrorProcessor. Here's the bottom line:
-
- python version < 2.4
- HANDLE_ERRORS == 1 (default) pass up 200, treat the rest as
- errors
- HANDLE_ERRORS == 0 pass everything up, error processing is
- left to the calling code
- python version >= 2.4
- HANDLE_ERRORS == 1 pass up 200, treat the rest as errors
- HANDLE_ERRORS == 0 (default) pass everything up, let the
- other handlers (specifically,
- HTTPErrorProcessor) decide what to do
-
- In practice, setting the variable either way makes little difference
- in python 2.4, so for the most consistent behavior across versions,
- you probably just want to use the defaults, which will give you
- exceptions on errors.
-
-"""
-
-# $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $
-
-import urllib2
-import httplib
-import socket
-import thread
-
-DEBUG = None
-
-import sys
-if sys.version_info < (2, 4): HANDLE_ERRORS = 1
-else: HANDLE_ERRORS = 0
-
-class ConnectionManager:
- """
- The connection manager must be able to:
- * keep track of all existing
- """
- def __init__(self):
- self._lock = thread.allocate_lock()
- self._hostmap = {} # map hosts to a list of connections
- self._connmap = {} # map connections to host
- self._readymap = {} # map connection to ready state
-
- def add(self, host, connection, ready):
- self._lock.acquire()
- try:
- if not host in self._hostmap: self._hostmap[host] = []
- self._hostmap[host].append(connection)
- self._connmap[connection] = host
- self._readymap[connection] = ready
- finally:
- self._lock.release()
-
- def remove(self, connection):
- self._lock.acquire()
- try:
- try:
- host = self._connmap[connection]
- except KeyError:
- pass
- else:
- del self._connmap[connection]
- del self._readymap[connection]
- self._hostmap[host].remove(connection)
- if not self._hostmap[host]: del self._hostmap[host]
- finally:
- self._lock.release()
-
- def set_ready(self, connection, ready):
- try: self._readymap[connection] = ready
- except KeyError: pass
-
- def get_ready_conn(self, host):
- conn = None
- self._lock.acquire()
- try:
- if host in self._hostmap:
- for c in self._hostmap[host]:
- if self._readymap[c]:
- self._readymap[c] = 0
- conn = c
- break
- finally:
- self._lock.release()
- return conn
-
- def get_all(self, host=None):
- if host:
- return list(self._hostmap.get(host, []))
- else:
- return dict(self._hostmap)
-
-class KeepAliveHandler:
- def __init__(self):
- self._cm = ConnectionManager()
-
- #### Connection Management
- def open_connections(self):
- """return a list of connected hosts and the number of connections
- to each. [('foo.com:80', 2), ('bar.org', 1)]"""
- return [(host, len(li)) for (host, li) in self._cm.get_all().items()]
-
- def close_connection(self, host):
- """close connection(s) to <host>
- host is the host:port spec, as in 'www.cnn.com:8080' as passed in.
- no error occurs if there is no connection to that host."""
- for h in self._cm.get_all(host):
- self._cm.remove(h)
- h.close()
-
- def close_all(self):
- """close all open connections"""
- for host, conns in self._cm.get_all().iteritems():
- for h in conns:
- self._cm.remove(h)
- h.close()
-
- def _request_closed(self, request, host, connection):
- """tells us that this request is now closed and the the
- connection is ready for another request"""
- self._cm.set_ready(connection, 1)
-
- def _remove_connection(self, host, connection, close=0):
- if close: connection.close()
- self._cm.remove(connection)
-
- #### Transaction Execution
- def http_open(self, req):
- return self.do_open(HTTPConnection, req)
-
- def do_open(self, http_class, req):
- host = req.get_host()
- if not host:
- raise urllib2.URLError('no host given')
-
- try:
- h = self._cm.get_ready_conn(host)
- while h:
- r = self._reuse_connection(h, req, host)
-
- # if this response is non-None, then it worked and we're
- # done. Break out, skipping the else block.
- if r: break
-
- # connection is bad - possibly closed by server
- # discard it and ask for the next free connection
- h.close()
- self._cm.remove(h)
- h = self._cm.get_ready_conn(host)
- else:
- # no (working) free connections were found. Create a new one.
- h = http_class(host)
- if DEBUG: DEBUG.info("creating new connection to %s (%d)",
- host, id(h))
- self._cm.add(host, h, 0)
- self._start_transaction(h, req)
- r = h.getresponse()
- except (socket.error, httplib.HTTPException), err:
- raise urllib2.URLError(err)
-
- # if not a persistent connection, don't try to reuse it
- if r.will_close: self._cm.remove(h)
-
- if DEBUG: DEBUG.info("STATUS: %s, %s", r.status, r.reason)
- r._handler = self
- r._host = host
- r._url = req.get_full_url()
- r._connection = h
- r.code = r.status
- r.headers = r.msg
- r.msg = r.reason
-
- if r.status == 200 or not HANDLE_ERRORS:
- return r
- else:
- return self.parent.error('http', req, r,
- r.status, r.msg, r.headers)
-
- def _reuse_connection(self, h, req, host):
- """start the transaction with a re-used connection
- return a response object (r) upon success or None on failure.
- This DOES not close or remove bad connections in cases where
- it returns. However, if an unexpected exception occurs, it
- will close and remove the connection before re-raising.
- """
- try:
- self._start_transaction(h, req)
- r = h.getresponse()
- # note: just because we got something back doesn't mean it
- # worked. We'll check the version below, too.
- except (socket.error, httplib.HTTPException):
- r = None
- except:
- # adding this block just in case we've missed
- # something we will still raise the exception, but
- # lets try and close the connection and remove it
- # first. We previously got into a nasty loop
- # where an exception was uncaught, and so the
- # connection stayed open. On the next try, the
- # same exception was raised, etc. The tradeoff is
- # that it's now possible this call will raise
- # a DIFFERENT exception
- if DEBUG: DEBUG.error("unexpected exception - closing " + \
- "connection to %s (%d)", host, id(h))
- self._cm.remove(h)
- h.close()
- raise
-
- if r is None or r.version == 9:
- # httplib falls back to assuming HTTP 0.9 if it gets a
- # bad header back. This is most likely to happen if
- # the socket has been closed by the server since we
- # last used the connection.
- if DEBUG: DEBUG.info("failed to re-use connection to %s (%d)",
- host, id(h))
- r = None
- else:
- if DEBUG: DEBUG.info("re-using connection to %s (%d)", host, id(h))
-
- return r
-
- def _start_transaction(self, h, req):
- # What follows mostly reimplements HTTPConnection.request()
- # except it adds self.parent.addheaders in the mix.
- headers = req.headers.copy()
- if sys.version_info >= (2, 4):
- headers.update(req.unredirected_hdrs)
- headers.update(self.parent.addheaders)
- headers = dict((n.lower(), v) for n,v in headers.items())
- skipheaders = {}
- for n in ('host', 'accept-encoding'):
- if n in headers:
- skipheaders['skip_' + n.replace('-', '_')] = 1
- try:
- if req.has_data():
- data = req.get_data()
- h.putrequest('POST', req.get_selector(), **skipheaders)
- if 'content-type' not in headers:
- h.putheader('Content-type',
- 'application/x-www-form-urlencoded')
- if 'content-length' not in headers:
- h.putheader('Content-length', '%d' % len(data))
- else:
- h.putrequest('GET', req.get_selector(), **skipheaders)
- except (socket.error), err:
- raise urllib2.URLError(err)
- for k, v in headers.items():
- h.putheader(k, v)
- h.endheaders()
- if req.has_data():
- h.send(data)
-
-class HTTPHandler(KeepAliveHandler, urllib2.HTTPHandler):
- pass
-
-class HTTPResponse(httplib.HTTPResponse):
- # we need to subclass HTTPResponse in order to
- # 1) add readline() and readlines() methods
- # 2) add close_connection() methods
- # 3) add info() and geturl() methods
-
- # in order to add readline(), read must be modified to deal with a
- # buffer. example: readline must read a buffer and then spit back
- # one line at a time. The only real alternative is to read one
- # BYTE at a time (ick). Once something has been read, it can't be
- # put back (ok, maybe it can, but that's even uglier than this),
- # so if you THEN do a normal read, you must first take stuff from
- # the buffer.
-
- # the read method wraps the original to accomodate buffering,
- # although read() never adds to the buffer.
- # Both readline and readlines have been stolen with almost no
- # modification from socket.py
-
-
- def __init__(self, sock, debuglevel=0, strict=0, method=None):
- if method: # the httplib in python 2.3 uses the method arg
- httplib.HTTPResponse.__init__(self, sock, debuglevel, method)
- else: # 2.2 doesn't
- httplib.HTTPResponse.__init__(self, sock, debuglevel)
- self.fileno = sock.fileno
- self.code = None
- self._rbuf = ''
- self._rbufsize = 8096
- self._handler = None # inserted by the handler later
- self._host = None # (same)
- self._url = None # (same)
- self._connection = None # (same)
-
- _raw_read = httplib.HTTPResponse.read
-
- def close(self):
- if self.fp:
- self.fp.close()
- self.fp = None
- if self._handler:
- self._handler._request_closed(self, self._host,
- self._connection)
-
- def close_connection(self):
- self._handler._remove_connection(self._host, self._connection, close=1)
- self.close()
-
- def info(self):
- return self.headers
-
- def geturl(self):
- return self._url
-
- def read(self, amt=None):
- # the _rbuf test is only in this first if for speed. It's not
- # logically necessary
- if self._rbuf and not amt is None:
- L = len(self._rbuf)
- if amt > L:
- amt -= L
- else:
- s = self._rbuf[:amt]
- self._rbuf = self._rbuf[amt:]
- return s
-
- s = self._rbuf + self._raw_read(amt)
- self._rbuf = ''
- return s
-
- # stolen from Python SVN #68532 to fix issue1088
- def _read_chunked(self, amt):
- chunk_left = self.chunk_left
- value = ''
-
- # XXX This accumulates chunks by repeated string concatenation,
- # which is not efficient as the number or size of chunks gets big.
- while True:
- if chunk_left is None:
- line = self.fp.readline()
- i = line.find(';')
- if i >= 0:
- line = line[:i] # strip chunk-extensions
- try:
- chunk_left = int(line, 16)
- except ValueError:
- # close the connection as protocol synchronisation is
- # probably lost
- self.close()
- raise httplib.IncompleteRead(value)
- if chunk_left == 0:
- break
- if amt is None:
- value += self._safe_read(chunk_left)
- elif amt < chunk_left:
- value += self._safe_read(amt)
- self.chunk_left = chunk_left - amt
- return value
- elif amt == chunk_left:
- value += self._safe_read(amt)
- self._safe_read(2) # toss the CRLF at the end of the chunk
- self.chunk_left = None
- return value
- else:
- value += self._safe_read(chunk_left)
- amt -= chunk_left
-
- # we read the whole chunk, get another
- self._safe_read(2) # toss the CRLF at the end of the chunk
- chunk_left = None
-
- # read and discard trailer up to the CRLF terminator
- ### note: we shouldn't have any trailers!
- while True:
- line = self.fp.readline()
- if not line:
- # a vanishingly small number of sites EOF without
- # sending the trailer
- break
- if line == '\r\n':
- break
-
- # we read everything; close the "file"
- self.close()
-
- return value
-
- def readline(self, limit=-1):
- i = self._rbuf.find('\n')
- while i < 0 and not (0 < limit <= len(self._rbuf)):
- new = self._raw_read(self._rbufsize)
- if not new: break
- i = new.find('\n')
- if i >= 0: i = i + len(self._rbuf)
- self._rbuf = self._rbuf + new
- if i < 0: i = len(self._rbuf)
- else: i = i+1
- if 0 <= limit < len(self._rbuf): i = limit
- data, self._rbuf = self._rbuf[:i], self._rbuf[i:]
- return data
-
- def readlines(self, sizehint = 0):
- total = 0
- list = []
- while 1:
- line = self.readline()
- if not line: break
- list.append(line)
- total += len(line)
- if sizehint and total >= sizehint:
- break
- return list
-
-
-class HTTPConnection(httplib.HTTPConnection):
- # use the modified response class
- response_class = HTTPResponse
-
-#########################################################################
-##### TEST FUNCTIONS
-#########################################################################
-
-def error_handler(url):
- global HANDLE_ERRORS
- orig = HANDLE_ERRORS
- keepalive_handler = HTTPHandler()
- opener = urllib2.build_opener(keepalive_handler)
- urllib2.install_opener(opener)
- pos = {0: 'off', 1: 'on'}
- for i in (0, 1):
- print " fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i)
- HANDLE_ERRORS = i
- try:
- fo = urllib2.urlopen(url)
- fo.read()
- fo.close()
- try: status, reason = fo.status, fo.reason
- except AttributeError: status, reason = None, None
- except IOError, e:
- print " EXCEPTION: %s" % e
- raise
- else:
- print " status = %s, reason = %s" % (status, reason)
- HANDLE_ERRORS = orig
- hosts = keepalive_handler.open_connections()
- print "open connections:", hosts
- keepalive_handler.close_all()
-
-def md5(s):
- try:
- from hashlib import md5 as _md5
- except ImportError:
- from md5 import md5 as _md5
- global md5
- md5 = _md5
- return _md5(s)
-
-def continuity(url):
- format = '%25s: %s'
-
- # first fetch the file with the normal http handler
- opener = urllib2.build_opener()
- urllib2.install_opener(opener)
- fo = urllib2.urlopen(url)
- foo = fo.read()
- fo.close()
- m = md5.new(foo)
- print format % ('normal urllib', m.hexdigest())
-
- # now install the keepalive handler and try again
- opener = urllib2.build_opener(HTTPHandler())
- urllib2.install_opener(opener)
-
- fo = urllib2.urlopen(url)
- foo = fo.read()
- fo.close()
- m = md5.new(foo)
- print format % ('keepalive read', m.hexdigest())
-
- fo = urllib2.urlopen(url)
- foo = ''
- while 1:
- f = fo.readline()
- if f: foo = foo + f
- else: break
- fo.close()
- m = md5.new(foo)
- print format % ('keepalive readline', m.hexdigest())
-
-def comp(N, url):
- print ' making %i connections to:\n %s' % (N, url)
-
- sys.stdout.write(' first using the normal urllib handlers')
- # first use normal opener
- opener = urllib2.build_opener()
- urllib2.install_opener(opener)
- t1 = fetch(N, url)
- print ' TIME: %.3f s' % t1
-
- sys.stdout.write(' now using the keepalive handler ')
- # now install the keepalive handler and try again
- opener = urllib2.build_opener(HTTPHandler())
- urllib2.install_opener(opener)
- t2 = fetch(N, url)
- print ' TIME: %.3f s' % t2
- print ' improvement factor: %.2f' % (t1/t2, )
-
-def fetch(N, url, delay=0):
- import time
- lens = []
- starttime = time.time()
- for i in range(N):
- if delay and i > 0: time.sleep(delay)
- fo = urllib2.urlopen(url)
- foo = fo.read()
- fo.close()
- lens.append(len(foo))
- diff = time.time() - starttime
-
- j = 0
- for i in lens[1:]:
- j = j + 1
- if not i == lens[0]:
- print "WARNING: inconsistent length on read %i: %i" % (j, i)
-
- return diff
-
-def test_timeout(url):
- global DEBUG
- dbbackup = DEBUG
- class FakeLogger:
- def debug(self, msg, *args): print msg % args
- info = warning = error = debug
- DEBUG = FakeLogger()
- print " fetching the file to establish a connection"
- fo = urllib2.urlopen(url)
- data1 = fo.read()
- fo.close()
-
- i = 20
- print " waiting %i seconds for the server to close the connection" % i
- while i > 0:
- sys.stdout.write('\r %2i' % i)
- sys.stdout.flush()
- time.sleep(1)
- i -= 1
- sys.stderr.write('\r')
-
- print " fetching the file a second time"
- fo = urllib2.urlopen(url)
- data2 = fo.read()
- fo.close()
-
- if data1 == data2:
- print ' data are identical'
- else:
- print ' ERROR: DATA DIFFER'
-
- DEBUG = dbbackup
-
-
-def test(url, N=10):
- print "checking error hander (do this on a non-200)"
- try: error_handler(url)
- except IOError:
- print "exiting - exception will prevent further tests"
- sys.exit()
- print
- print "performing continuity test (making sure stuff isn't corrupted)"
- continuity(url)
- print
- print "performing speed comparison"
- comp(N, url)
- print
- print "performing dropped-connection check"
- test_timeout(url)
-
-if __name__ == '__main__':
- import time
- import sys
- try:
- N = int(sys.argv[1])
- url = sys.argv[2]
- except:
- print "%s <integer> <url>" % sys.argv[0]
- else:
- test(url, N)
diff --git a/sys/src/cmd/hg/mercurial/localrepo.py b/sys/src/cmd/hg/mercurial/localrepo.py
deleted file mode 100644
index f6000b502..000000000
--- a/sys/src/cmd/hg/mercurial/localrepo.py
+++ /dev/null
@@ -1,2156 +0,0 @@
-# localrepo.py - read/write repository class for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import bin, hex, nullid, nullrev, short
-from i18n import _
-import repo, changegroup, subrepo
-import changelog, dirstate, filelog, manifest, context
-import lock, transaction, store, encoding
-import util, extensions, hook, error
-import match as match_
-import merge as merge_
-import tags as tags_
-from lock import release
-import weakref, stat, errno, os, time, inspect
-propertycache = util.propertycache
-
-class localrepository(repo.repository):
- capabilities = set(('lookup', 'changegroupsubset', 'branchmap'))
- supported = set('revlogv1 store fncache shared'.split())
-
- def __init__(self, baseui, path=None, create=0):
- repo.repository.__init__(self)
- self.root = os.path.realpath(path)
- self.path = os.path.join(self.root, ".hg")
- self.origroot = path
- self.opener = util.opener(self.path)
- self.wopener = util.opener(self.root)
- self.baseui = baseui
- self.ui = baseui.copy()
-
- try:
- self.ui.readconfig(self.join("hgrc"), self.root)
- extensions.loadall(self.ui)
- except IOError:
- pass
-
- if not os.path.isdir(self.path):
- if create:
- if not os.path.exists(path):
- os.makedirs(path)
- os.mkdir(self.path)
- requirements = ["revlogv1"]
- if self.ui.configbool('format', 'usestore', True):
- os.mkdir(os.path.join(self.path, "store"))
- requirements.append("store")
- if self.ui.configbool('format', 'usefncache', True):
- requirements.append("fncache")
- # create an invalid changelog
- self.opener("00changelog.i", "a").write(
- '\0\0\0\2' # represents revlogv2
- ' dummy changelog to prevent using the old repo layout'
- )
- reqfile = self.opener("requires", "w")
- for r in requirements:
- reqfile.write("%s\n" % r)
- reqfile.close()
- else:
- raise error.RepoError(_("repository %s not found") % path)
- elif create:
- raise error.RepoError(_("repository %s already exists") % path)
- else:
- # find requirements
- requirements = set()
- try:
- requirements = set(self.opener("requires").read().splitlines())
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
- for r in requirements - self.supported:
- raise error.RepoError(_("requirement '%s' not supported") % r)
-
- self.sharedpath = self.path
- try:
- s = os.path.realpath(self.opener("sharedpath").read())
- if not os.path.exists(s):
- raise error.RepoError(
- _('.hg/sharedpath points to nonexistent directory %s') % s)
- self.sharedpath = s
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
-
- self.store = store.store(requirements, self.sharedpath, util.opener)
- self.spath = self.store.path
- self.sopener = self.store.opener
- self.sjoin = self.store.join
- self.opener.createmode = self.store.createmode
-
- # These two define the set of tags for this repository. _tags
- # maps tag name to node; _tagtypes maps tag name to 'global' or
- # 'local'. (Global tags are defined by .hgtags across all
- # heads, and local tags are defined in .hg/localtags.) They
- # constitute the in-memory cache of tags.
- self._tags = None
- self._tagtypes = None
-
- self.branchcache = None
- self._ubranchcache = None # UTF-8 version of branchcache
- self._branchcachetip = None
- self.nodetagscache = None
- self.filterpats = {}
- self._datafilters = {}
- self._transref = self._lockref = self._wlockref = None
-
- @propertycache
- def changelog(self):
- c = changelog.changelog(self.sopener)
- if 'HG_PENDING' in os.environ:
- p = os.environ['HG_PENDING']
- if p.startswith(self.root):
- c.readpending('00changelog.i.a')
- self.sopener.defversion = c.version
- return c
-
- @propertycache
- def manifest(self):
- return manifest.manifest(self.sopener)
-
- @propertycache
- def dirstate(self):
- return dirstate.dirstate(self.opener, self.ui, self.root)
-
- def __getitem__(self, changeid):
- if changeid is None:
- return context.workingctx(self)
- return context.changectx(self, changeid)
-
- def __nonzero__(self):
- return True
-
- def __len__(self):
- return len(self.changelog)
-
- def __iter__(self):
- for i in xrange(len(self)):
- yield i
-
- def url(self):
- return 'file:' + self.root
-
- def hook(self, name, throw=False, **args):
- return hook.hook(self.ui, self, name, throw, **args)
-
- tag_disallowed = ':\r\n'
-
- def _tag(self, names, node, message, local, user, date, extra={}):
- if isinstance(names, str):
- allchars = names
- names = (names,)
- else:
- allchars = ''.join(names)
- for c in self.tag_disallowed:
- if c in allchars:
- raise util.Abort(_('%r cannot be used in a tag name') % c)
-
- for name in names:
- self.hook('pretag', throw=True, node=hex(node), tag=name,
- local=local)
-
- def writetags(fp, names, munge, prevtags):
- fp.seek(0, 2)
- if prevtags and prevtags[-1] != '\n':
- fp.write('\n')
- for name in names:
- m = munge and munge(name) or name
- if self._tagtypes and name in self._tagtypes:
- old = self._tags.get(name, nullid)
- fp.write('%s %s\n' % (hex(old), m))
- fp.write('%s %s\n' % (hex(node), m))
- fp.close()
-
- prevtags = ''
- if local:
- try:
- fp = self.opener('localtags', 'r+')
- except IOError:
- fp = self.opener('localtags', 'a')
- else:
- prevtags = fp.read()
-
- # local tags are stored in the current charset
- writetags(fp, names, None, prevtags)
- for name in names:
- self.hook('tag', node=hex(node), tag=name, local=local)
- return
-
- try:
- fp = self.wfile('.hgtags', 'rb+')
- except IOError:
- fp = self.wfile('.hgtags', 'ab')
- else:
- prevtags = fp.read()
-
- # committed tags are stored in UTF-8
- writetags(fp, names, encoding.fromlocal, prevtags)
-
- if '.hgtags' not in self.dirstate:
- self.add(['.hgtags'])
-
- m = match_.exact(self.root, '', ['.hgtags'])
- tagnode = self.commit(message, user, date, extra=extra, match=m)
-
- for name in names:
- self.hook('tag', node=hex(node), tag=name, local=local)
-
- return tagnode
-
- def tag(self, names, node, message, local, user, date):
- '''tag a revision with one or more symbolic names.
-
- names is a list of strings or, when adding a single tag, names may be a
- string.
-
- if local is True, the tags are stored in a per-repository file.
- otherwise, they are stored in the .hgtags file, and a new
- changeset is committed with the change.
-
- keyword arguments:
-
- local: whether to store tags in non-version-controlled file
- (default False)
-
- message: commit message to use if committing
-
- user: name of user to use if committing
-
- date: date tuple to use if committing'''
-
- for x in self.status()[:5]:
- if '.hgtags' in x:
- raise util.Abort(_('working copy of .hgtags is changed '
- '(please commit .hgtags manually)'))
-
- self.tags() # instantiate the cache
- self._tag(names, node, message, local, user, date)
-
- def tags(self):
- '''return a mapping of tag to node'''
- if self._tags is None:
- (self._tags, self._tagtypes) = self._findtags()
-
- return self._tags
-
- def _findtags(self):
- '''Do the hard work of finding tags. Return a pair of dicts
- (tags, tagtypes) where tags maps tag name to node, and tagtypes
- maps tag name to a string like \'global\' or \'local\'.
- Subclasses or extensions are free to add their own tags, but
- should be aware that the returned dicts will be retained for the
- duration of the localrepo object.'''
-
- # XXX what tagtype should subclasses/extensions use? Currently
- # mq and bookmarks add tags, but do not set the tagtype at all.
- # Should each extension invent its own tag type? Should there
- # be one tagtype for all such "virtual" tags? Or is the status
- # quo fine?
-
- alltags = {} # map tag name to (node, hist)
- tagtypes = {}
-
- tags_.findglobaltags(self.ui, self, alltags, tagtypes)
- tags_.readlocaltags(self.ui, self, alltags, tagtypes)
-
- # Build the return dicts. Have to re-encode tag names because
- # the tags module always uses UTF-8 (in order not to lose info
- # writing to the cache), but the rest of Mercurial wants them in
- # local encoding.
- tags = {}
- for (name, (node, hist)) in alltags.iteritems():
- if node != nullid:
- tags[encoding.tolocal(name)] = node
- tags['tip'] = self.changelog.tip()
- tagtypes = dict([(encoding.tolocal(name), value)
- for (name, value) in tagtypes.iteritems()])
- return (tags, tagtypes)
-
- def tagtype(self, tagname):
- '''
- return the type of the given tag. result can be:
-
- 'local' : a local tag
- 'global' : a global tag
- None : tag does not exist
- '''
-
- self.tags()
-
- return self._tagtypes.get(tagname)
-
- def tagslist(self):
- '''return a list of tags ordered by revision'''
- l = []
- for t, n in self.tags().iteritems():
- try:
- r = self.changelog.rev(n)
- except:
- r = -2 # sort to the beginning of the list if unknown
- l.append((r, t, n))
- return [(t, n) for r, t, n in sorted(l)]
-
- def nodetags(self, node):
- '''return the tags associated with a node'''
- if not self.nodetagscache:
- self.nodetagscache = {}
- for t, n in self.tags().iteritems():
- self.nodetagscache.setdefault(n, []).append(t)
- return self.nodetagscache.get(node, [])
-
- def _branchtags(self, partial, lrev):
- # TODO: rename this function?
- tiprev = len(self) - 1
- if lrev != tiprev:
- self._updatebranchcache(partial, lrev+1, tiprev+1)
- self._writebranchcache(partial, self.changelog.tip(), tiprev)
-
- return partial
-
- def branchmap(self):
- tip = self.changelog.tip()
- if self.branchcache is not None and self._branchcachetip == tip:
- return self.branchcache
-
- oldtip = self._branchcachetip
- self._branchcachetip = tip
- if self.branchcache is None:
- self.branchcache = {} # avoid recursion in changectx
- else:
- self.branchcache.clear() # keep using the same dict
- if oldtip is None or oldtip not in self.changelog.nodemap:
- partial, last, lrev = self._readbranchcache()
- else:
- lrev = self.changelog.rev(oldtip)
- partial = self._ubranchcache
-
- self._branchtags(partial, lrev)
- # this private cache holds all heads (not just tips)
- self._ubranchcache = partial
-
- # the branch cache is stored on disk as UTF-8, but in the local
- # charset internally
- for k, v in partial.iteritems():
- self.branchcache[encoding.tolocal(k)] = v
- return self.branchcache
-
-
- def branchtags(self):
- '''return a dict where branch names map to the tipmost head of
- the branch, open heads come before closed'''
- bt = {}
- for bn, heads in self.branchmap().iteritems():
- head = None
- for i in range(len(heads)-1, -1, -1):
- h = heads[i]
- if 'close' not in self.changelog.read(h)[5]:
- head = h
- break
- # no open heads were found
- if head is None:
- head = heads[-1]
- bt[bn] = head
- return bt
-
-
- def _readbranchcache(self):
- partial = {}
- try:
- f = self.opener("branchheads.cache")
- lines = f.read().split('\n')
- f.close()
- except (IOError, OSError):
- return {}, nullid, nullrev
-
- try:
- last, lrev = lines.pop(0).split(" ", 1)
- last, lrev = bin(last), int(lrev)
- if lrev >= len(self) or self[lrev].node() != last:
- # invalidate the cache
- raise ValueError('invalidating branch cache (tip differs)')
- for l in lines:
- if not l: continue
- node, label = l.split(" ", 1)
- partial.setdefault(label.strip(), []).append(bin(node))
- except KeyboardInterrupt:
- raise
- except Exception, inst:
- if self.ui.debugflag:
- self.ui.warn(str(inst), '\n')
- partial, last, lrev = {}, nullid, nullrev
- return partial, last, lrev
-
- def _writebranchcache(self, branches, tip, tiprev):
- try:
- f = self.opener("branchheads.cache", "w", atomictemp=True)
- f.write("%s %s\n" % (hex(tip), tiprev))
- for label, nodes in branches.iteritems():
- for node in nodes:
- f.write("%s %s\n" % (hex(node), label))
- f.rename()
- except (IOError, OSError):
- pass
-
- def _updatebranchcache(self, partial, start, end):
- # collect new branch entries
- newbranches = {}
- for r in xrange(start, end):
- c = self[r]
- newbranches.setdefault(c.branch(), []).append(c.node())
- # if older branchheads are reachable from new ones, they aren't
- # really branchheads. Note checking parents is insufficient:
- # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
- for branch, newnodes in newbranches.iteritems():
- bheads = partial.setdefault(branch, [])
- bheads.extend(newnodes)
- if len(bheads) < 2:
- continue
- newbheads = []
- # starting from tip means fewer passes over reachable
- while newnodes:
- latest = newnodes.pop()
- if latest not in bheads:
- continue
- minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
- reachable = self.changelog.reachable(latest, minbhrev)
- bheads = [b for b in bheads if b not in reachable]
- newbheads.insert(0, latest)
- bheads.extend(newbheads)
- partial[branch] = bheads
-
- def lookup(self, key):
- if isinstance(key, int):
- return self.changelog.node(key)
- elif key == '.':
- return self.dirstate.parents()[0]
- elif key == 'null':
- return nullid
- elif key == 'tip':
- return self.changelog.tip()
- n = self.changelog._match(key)
- if n:
- return n
- if key in self.tags():
- return self.tags()[key]
- if key in self.branchtags():
- return self.branchtags()[key]
- n = self.changelog._partialmatch(key)
- if n:
- return n
-
- # can't find key, check if it might have come from damaged dirstate
- if key in self.dirstate.parents():
- raise error.Abort(_("working directory has unknown parent '%s'!")
- % short(key))
- try:
- if len(key) == 20:
- key = hex(key)
- except:
- pass
- raise error.RepoError(_("unknown revision '%s'") % key)
-
- def local(self):
- return True
-
- def join(self, f):
- return os.path.join(self.path, f)
-
- def wjoin(self, f):
- return os.path.join(self.root, f)
-
- def rjoin(self, f):
- return os.path.join(self.root, util.pconvert(f))
-
- def file(self, f):
- if f[0] == '/':
- f = f[1:]
- return filelog.filelog(self.sopener, f)
-
- def changectx(self, changeid):
- return self[changeid]
-
- def parents(self, changeid=None):
- '''get list of changectxs for parents of changeid'''
- return self[changeid].parents()
-
- def filectx(self, path, changeid=None, fileid=None):
- """changeid can be a changeset revision, node, or tag.
- fileid can be a file revision or node."""
- return context.filectx(self, path, changeid, fileid)
-
- def getcwd(self):
- return self.dirstate.getcwd()
-
- def pathto(self, f, cwd=None):
- return self.dirstate.pathto(f, cwd)
-
- def wfile(self, f, mode='r'):
- return self.wopener(f, mode)
-
- def _link(self, f):
- return os.path.islink(self.wjoin(f))
-
- def _filter(self, filter, filename, data):
- if filter not in self.filterpats:
- l = []
- for pat, cmd in self.ui.configitems(filter):
- if cmd == '!':
- continue
- mf = match_.match(self.root, '', [pat])
- fn = None
- params = cmd
- for name, filterfn in self._datafilters.iteritems():
- if cmd.startswith(name):
- fn = filterfn
- params = cmd[len(name):].lstrip()
- break
- if not fn:
- fn = lambda s, c, **kwargs: util.filter(s, c)
- # Wrap old filters not supporting keyword arguments
- if not inspect.getargspec(fn)[2]:
- oldfn = fn
- fn = lambda s, c, **kwargs: oldfn(s, c)
- l.append((mf, fn, params))
- self.filterpats[filter] = l
-
- for mf, fn, cmd in self.filterpats[filter]:
- if mf(filename):
- self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
- data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
- break
-
- return data
-
- def adddatafilter(self, name, filter):
- self._datafilters[name] = filter
-
- def wread(self, filename):
- if self._link(filename):
- data = os.readlink(self.wjoin(filename))
- else:
- data = self.wopener(filename, 'r').read()
- return self._filter("encode", filename, data)
-
- def wwrite(self, filename, data, flags):
- data = self._filter("decode", filename, data)
- try:
- os.unlink(self.wjoin(filename))
- except OSError:
- pass
- if 'l' in flags:
- self.wopener.symlink(data, filename)
- else:
- self.wopener(filename, 'w').write(data)
- if 'x' in flags:
- util.set_flags(self.wjoin(filename), False, True)
-
- def wwritedata(self, filename, data):
- return self._filter("decode", filename, data)
-
- def transaction(self):
- tr = self._transref and self._transref() or None
- if tr and tr.running():
- return tr.nest()
-
- # abort here if the journal already exists
- if os.path.exists(self.sjoin("journal")):
- raise error.RepoError(_("journal already exists - run hg recover"))
-
- # save dirstate for rollback
- try:
- ds = self.opener("dirstate").read()
- except IOError:
- ds = ""
- self.opener("journal.dirstate", "w").write(ds)
- self.opener("journal.branch", "w").write(self.dirstate.branch())
-
- renames = [(self.sjoin("journal"), self.sjoin("undo")),
- (self.join("journal.dirstate"), self.join("undo.dirstate")),
- (self.join("journal.branch"), self.join("undo.branch"))]
- tr = transaction.transaction(self.ui.warn, self.sopener,
- self.sjoin("journal"),
- aftertrans(renames),
- self.store.createmode)
- self._transref = weakref.ref(tr)
- return tr
-
- def recover(self):
- lock = self.lock()
- try:
- if os.path.exists(self.sjoin("journal")):
- self.ui.status(_("rolling back interrupted transaction\n"))
- transaction.rollback(self.sopener, self.sjoin("journal"), self.ui.warn)
- self.invalidate()
- return True
- else:
- self.ui.warn(_("no interrupted transaction available\n"))
- return False
- finally:
- lock.release()
-
- def rollback(self):
- wlock = lock = None
- try:
- wlock = self.wlock()
- lock = self.lock()
- if os.path.exists(self.sjoin("undo")):
- self.ui.status(_("rolling back last transaction\n"))
- transaction.rollback(self.sopener, self.sjoin("undo"), self.ui.warn)
- util.rename(self.join("undo.dirstate"), self.join("dirstate"))
- try:
- branch = self.opener("undo.branch").read()
- self.dirstate.setbranch(branch)
- except IOError:
- self.ui.warn(_("Named branch could not be reset, "
- "current branch still is: %s\n")
- % encoding.tolocal(self.dirstate.branch()))
- self.invalidate()
- self.dirstate.invalidate()
- self.destroyed()
- else:
- self.ui.warn(_("no rollback information available\n"))
- finally:
- release(lock, wlock)
-
- def invalidate(self):
- for a in "changelog manifest".split():
- if a in self.__dict__:
- delattr(self, a)
- self._tags = None
- self._tagtypes = None
- self.nodetagscache = None
- self.branchcache = None
- self._ubranchcache = None
- self._branchcachetip = None
-
- def _lock(self, lockname, wait, releasefn, acquirefn, desc):
- try:
- l = lock.lock(lockname, 0, releasefn, desc=desc)
- except error.LockHeld, inst:
- if not wait:
- raise
- self.ui.warn(_("waiting for lock on %s held by %r\n") %
- (desc, inst.locker))
- # default to 600 seconds timeout
- l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
- releasefn, desc=desc)
- if acquirefn:
- acquirefn()
- return l
-
- def lock(self, wait=True):
- '''Lock the repository store (.hg/store) and return a weak reference
- to the lock. Use this before modifying the store (e.g. committing or
- stripping). If you are opening a transaction, get a lock as well.)'''
- l = self._lockref and self._lockref()
- if l is not None and l.held:
- l.lock()
- return l
-
- l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
- _('repository %s') % self.origroot)
- self._lockref = weakref.ref(l)
- return l
-
- def wlock(self, wait=True):
- '''Lock the non-store parts of the repository (everything under
- .hg except .hg/store) and return a weak reference to the lock.
- Use this before modifying files in .hg.'''
- l = self._wlockref and self._wlockref()
- if l is not None and l.held:
- l.lock()
- return l
-
- l = self._lock(self.join("wlock"), wait, self.dirstate.write,
- self.dirstate.invalidate, _('working directory of %s') %
- self.origroot)
- self._wlockref = weakref.ref(l)
- return l
-
- def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
- """
- commit an individual file as part of a larger transaction
- """
-
- fname = fctx.path()
- text = fctx.data()
- flog = self.file(fname)
- fparent1 = manifest1.get(fname, nullid)
- fparent2 = fparent2o = manifest2.get(fname, nullid)
-
- meta = {}
- copy = fctx.renamed()
- if copy and copy[0] != fname:
- # Mark the new revision of this file as a copy of another
- # file. This copy data will effectively act as a parent
- # of this new revision. If this is a merge, the first
- # parent will be the nullid (meaning "look up the copy data")
- # and the second one will be the other parent. For example:
- #
- # 0 --- 1 --- 3 rev1 changes file foo
- # \ / rev2 renames foo to bar and changes it
- # \- 2 -/ rev3 should have bar with all changes and
- # should record that bar descends from
- # bar in rev2 and foo in rev1
- #
- # this allows this merge to succeed:
- #
- # 0 --- 1 --- 3 rev4 reverts the content change from rev2
- # \ / merging rev3 and rev4 should use bar@rev2
- # \- 2 --- 4 as the merge base
- #
-
- cfname = copy[0]
- crev = manifest1.get(cfname)
- newfparent = fparent2
-
- if manifest2: # branch merge
- if fparent2 == nullid or crev is None: # copied on remote side
- if cfname in manifest2:
- crev = manifest2[cfname]
- newfparent = fparent1
-
- # find source in nearest ancestor if we've lost track
- if not crev:
- self.ui.debug(_(" %s: searching for copy revision for %s\n") %
- (fname, cfname))
- for ancestor in self['.'].ancestors():
- if cfname in ancestor:
- crev = ancestor[cfname].filenode()
- break
-
- self.ui.debug(_(" %s: copy %s:%s\n") % (fname, cfname, hex(crev)))
- meta["copy"] = cfname
- meta["copyrev"] = hex(crev)
- fparent1, fparent2 = nullid, newfparent
- elif fparent2 != nullid:
- # is one parent an ancestor of the other?
- fparentancestor = flog.ancestor(fparent1, fparent2)
- if fparentancestor == fparent1:
- fparent1, fparent2 = fparent2, nullid
- elif fparentancestor == fparent2:
- fparent2 = nullid
-
- # is the file changed?
- if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
- changelist.append(fname)
- return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
-
- # are just the flags changed during merge?
- if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
- changelist.append(fname)
-
- return fparent1
-
- def commit(self, text="", user=None, date=None, match=None, force=False,
- editor=False, extra={}):
- """Add a new revision to current repository.
-
- Revision information is gathered from the working directory,
- match can be used to filter the committed files. If editor is
- supplied, it is called to get a commit message.
- """
-
- def fail(f, msg):
- raise util.Abort('%s: %s' % (f, msg))
-
- if not match:
- match = match_.always(self.root, '')
-
- if not force:
- vdirs = []
- match.dir = vdirs.append
- match.bad = fail
-
- wlock = self.wlock()
- try:
- p1, p2 = self.dirstate.parents()
- wctx = self[None]
-
- if (not force and p2 != nullid and match and
- (match.files() or match.anypats())):
- raise util.Abort(_('cannot partially commit a merge '
- '(do not specify files or patterns)'))
-
- changes = self.status(match=match, clean=force)
- if force:
- changes[0].extend(changes[6]) # mq may commit unchanged files
-
- # check subrepos
- subs = []
- for s in wctx.substate:
- if match(s) and wctx.sub(s).dirty():
- subs.append(s)
- if subs and '.hgsubstate' not in changes[0]:
- changes[0].insert(0, '.hgsubstate')
-
- # make sure all explicit patterns are matched
- if not force and match.files():
- matched = set(changes[0] + changes[1] + changes[2])
-
- for f in match.files():
- if f == '.' or f in matched or f in wctx.substate:
- continue
- if f in changes[3]: # missing
- fail(f, _('file not found!'))
- if f in vdirs: # visited directory
- d = f + '/'
- for mf in matched:
- if mf.startswith(d):
- break
- else:
- fail(f, _("no match under directory!"))
- elif f not in self.dirstate:
- fail(f, _("file not tracked!"))
-
- if (not force and not extra.get("close") and p2 == nullid
- and not (changes[0] or changes[1] or changes[2])
- and self[None].branch() == self['.'].branch()):
- return None
-
- ms = merge_.mergestate(self)
- for f in changes[0]:
- if f in ms and ms[f] == 'u':
- raise util.Abort(_("unresolved merge conflicts "
- "(see hg resolve)"))
-
- cctx = context.workingctx(self, (p1, p2), text, user, date,
- extra, changes)
- if editor:
- cctx._text = editor(self, cctx, subs)
-
- # commit subs
- if subs:
- state = wctx.substate.copy()
- for s in subs:
- self.ui.status(_('committing subrepository %s\n') % s)
- sr = wctx.sub(s).commit(cctx._text, user, date)
- state[s] = (state[s][0], sr)
- subrepo.writestate(self, state)
-
- ret = self.commitctx(cctx, True)
-
- # update dirstate and mergestate
- for f in changes[0] + changes[1]:
- self.dirstate.normal(f)
- for f in changes[2]:
- self.dirstate.forget(f)
- self.dirstate.setparents(ret)
- ms.reset()
-
- return ret
-
- finally:
- wlock.release()
-
- def commitctx(self, ctx, error=False):
- """Add a new revision to current repository.
-
- Revision information is passed via the context argument.
- """
-
- tr = lock = None
- removed = ctx.removed()
- p1, p2 = ctx.p1(), ctx.p2()
- m1 = p1.manifest().copy()
- m2 = p2.manifest()
- user = ctx.user()
-
- xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
- self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
-
- lock = self.lock()
- try:
- tr = self.transaction()
- trp = weakref.proxy(tr)
-
- # check in files
- new = {}
- changed = []
- linkrev = len(self)
- for f in sorted(ctx.modified() + ctx.added()):
- self.ui.note(f + "\n")
- try:
- fctx = ctx[f]
- new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
- changed)
- m1.set(f, fctx.flags())
- except (OSError, IOError):
- if error:
- self.ui.warn(_("trouble committing %s!\n") % f)
- raise
- else:
- removed.append(f)
-
- # update manifest
- m1.update(new)
- removed = [f for f in sorted(removed) if f in m1 or f in m2]
- drop = [f for f in removed if f in m1]
- for f in drop:
- del m1[f]
- mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
- p2.manifestnode(), (new, drop))
-
- # update changelog
- self.changelog.delayupdate()
- n = self.changelog.add(mn, changed + removed, ctx.description(),
- trp, p1.node(), p2.node(),
- user, ctx.date(), ctx.extra().copy())
- p = lambda: self.changelog.writepending() and self.root or ""
- self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
- parent2=xp2, pending=p)
- self.changelog.finalize(trp)
- tr.close()
-
- if self.branchcache:
- self.branchtags()
-
- self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
- return n
- finally:
- del tr
- lock.release()
-
- def destroyed(self):
- '''Inform the repository that nodes have been destroyed.
- Intended for use by strip and rollback, so there's a common
- place for anything that has to be done after destroying history.'''
- # XXX it might be nice if we could take the list of destroyed
- # nodes, but I don't see an easy way for rollback() to do that
-
- # Ensure the persistent tag cache is updated. Doing it now
- # means that the tag cache only has to worry about destroyed
- # heads immediately after a strip/rollback. That in turn
- # guarantees that "cachetip == currenttip" (comparing both rev
- # and node) always means no nodes have been added or destroyed.
-
- # XXX this is suboptimal when qrefresh'ing: we strip the current
- # head, refresh the tag cache, then immediately add a new head.
- # But I think doing it this way is necessary for the "instant
- # tag cache retrieval" case to work.
- tags_.findglobaltags(self.ui, self, {}, {})
-
- def walk(self, match, node=None):
- '''
- walk recursively through the directory tree or a given
- changeset, finding all files matched by the match
- function
- '''
- return self[node].walk(match)
-
- def status(self, node1='.', node2=None, match=None,
- ignored=False, clean=False, unknown=False):
- """return status of files between two nodes or node and working directory
-
- If node1 is None, use the first dirstate parent instead.
- If node2 is None, compare node1 with working directory.
- """
-
- def mfmatches(ctx):
- mf = ctx.manifest().copy()
- for fn in mf.keys():
- if not match(fn):
- del mf[fn]
- return mf
-
- if isinstance(node1, context.changectx):
- ctx1 = node1
- else:
- ctx1 = self[node1]
- if isinstance(node2, context.changectx):
- ctx2 = node2
- else:
- ctx2 = self[node2]
-
- working = ctx2.rev() is None
- parentworking = working and ctx1 == self['.']
- match = match or match_.always(self.root, self.getcwd())
- listignored, listclean, listunknown = ignored, clean, unknown
-
- # load earliest manifest first for caching reasons
- if not working and ctx2.rev() < ctx1.rev():
- ctx2.manifest()
-
- if not parentworking:
- def bad(f, msg):
- if f not in ctx1:
- self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
- match.bad = bad
-
- if working: # we need to scan the working dir
- s = self.dirstate.status(match, listignored, listclean, listunknown)
- cmp, modified, added, removed, deleted, unknown, ignored, clean = s
-
- # check for any possibly clean files
- if parentworking and cmp:
- fixup = []
- # do a full compare of any files that might have changed
- for f in sorted(cmp):
- if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
- or ctx1[f].cmp(ctx2[f].data())):
- modified.append(f)
- else:
- fixup.append(f)
-
- if listclean:
- clean += fixup
-
- # update dirstate for files that are actually clean
- if fixup:
- try:
- # updating the dirstate is optional
- # so we don't wait on the lock
- wlock = self.wlock(False)
- try:
- for f in fixup:
- self.dirstate.normal(f)
- finally:
- wlock.release()
- except error.LockError:
- pass
-
- if not parentworking:
- mf1 = mfmatches(ctx1)
- if working:
- # we are comparing working dir against non-parent
- # generate a pseudo-manifest for the working dir
- mf2 = mfmatches(self['.'])
- for f in cmp + modified + added:
- mf2[f] = None
- mf2.set(f, ctx2.flags(f))
- for f in removed:
- if f in mf2:
- del mf2[f]
- else:
- # we are comparing two revisions
- deleted, unknown, ignored = [], [], []
- mf2 = mfmatches(ctx2)
-
- modified, added, clean = [], [], []
- for fn in mf2:
- if fn in mf1:
- if (mf1.flags(fn) != mf2.flags(fn) or
- (mf1[fn] != mf2[fn] and
- (mf2[fn] or ctx1[fn].cmp(ctx2[fn].data())))):
- modified.append(fn)
- elif listclean:
- clean.append(fn)
- del mf1[fn]
- else:
- added.append(fn)
- removed = mf1.keys()
-
- r = modified, added, removed, deleted, unknown, ignored, clean
- [l.sort() for l in r]
- return r
-
- def add(self, list):
- wlock = self.wlock()
- try:
- rejected = []
- for f in list:
- p = self.wjoin(f)
- try:
- st = os.lstat(p)
- except:
- self.ui.warn(_("%s does not exist!\n") % f)
- rejected.append(f)
- continue
- if st.st_size > 10000000:
- self.ui.warn(_("%s: files over 10MB may cause memory and"
- " performance problems\n"
- "(use 'hg revert %s' to unadd the file)\n")
- % (f, f))
- if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
- self.ui.warn(_("%s not added: only files and symlinks "
- "supported currently\n") % f)
- rejected.append(p)
- elif self.dirstate[f] in 'amn':
- self.ui.warn(_("%s already tracked!\n") % f)
- elif self.dirstate[f] == 'r':
- self.dirstate.normallookup(f)
- else:
- self.dirstate.add(f)
- return rejected
- finally:
- wlock.release()
-
- def forget(self, list):
- wlock = self.wlock()
- try:
- for f in list:
- if self.dirstate[f] != 'a':
- self.ui.warn(_("%s not added!\n") % f)
- else:
- self.dirstate.forget(f)
- finally:
- wlock.release()
-
- def remove(self, list, unlink=False):
- if unlink:
- for f in list:
- try:
- util.unlink(self.wjoin(f))
- except OSError, inst:
- if inst.errno != errno.ENOENT:
- raise
- wlock = self.wlock()
- try:
- for f in list:
- if unlink and os.path.exists(self.wjoin(f)):
- self.ui.warn(_("%s still exists!\n") % f)
- elif self.dirstate[f] == 'a':
- self.dirstate.forget(f)
- elif f not in self.dirstate:
- self.ui.warn(_("%s not tracked!\n") % f)
- else:
- self.dirstate.remove(f)
- finally:
- wlock.release()
-
- def undelete(self, list):
- manifests = [self.manifest.read(self.changelog.read(p)[0])
- for p in self.dirstate.parents() if p != nullid]
- wlock = self.wlock()
- try:
- for f in list:
- if self.dirstate[f] != 'r':
- self.ui.warn(_("%s not removed!\n") % f)
- else:
- m = f in manifests[0] and manifests[0] or manifests[1]
- t = self.file(f).read(m[f])
- self.wwrite(f, t, m.flags(f))
- self.dirstate.normal(f)
- finally:
- wlock.release()
-
- def copy(self, source, dest):
- p = self.wjoin(dest)
- if not (os.path.exists(p) or os.path.islink(p)):
- self.ui.warn(_("%s does not exist!\n") % dest)
- elif not (os.path.isfile(p) or os.path.islink(p)):
- self.ui.warn(_("copy failed: %s is not a file or a "
- "symbolic link\n") % dest)
- else:
- wlock = self.wlock()
- try:
- if self.dirstate[dest] in '?r':
- self.dirstate.add(dest)
- self.dirstate.copy(source, dest)
- finally:
- wlock.release()
-
- def heads(self, start=None):
- heads = self.changelog.heads(start)
- # sort the output in rev descending order
- heads = [(-self.changelog.rev(h), h) for h in heads]
- return [n for (r, n) in sorted(heads)]
-
- def branchheads(self, branch=None, start=None, closed=False):
- if branch is None:
- branch = self[None].branch()
- branches = self.branchmap()
- if branch not in branches:
- return []
- bheads = branches[branch]
- # the cache returns heads ordered lowest to highest
- bheads.reverse()
- if start is not None:
- # filter out the heads that cannot be reached from startrev
- bheads = self.changelog.nodesbetween([start], bheads)[2]
- if not closed:
- bheads = [h for h in bheads if
- ('close' not in self.changelog.read(h)[5])]
- return bheads
-
- def branches(self, nodes):
- if not nodes:
- nodes = [self.changelog.tip()]
- b = []
- for n in nodes:
- t = n
- while 1:
- 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 and n != nullid:
- p = self.changelog.parents(n)[0]
- if i == f:
- l.append(n)
- f = f * 2
- n = p
- i += 1
-
- r.append(l)
-
- return r
-
- def findincoming(self, remote, base=None, heads=None, force=False):
- """Return list of roots of the subsets of missing nodes from remote
-
- If base dict is specified, assume that these nodes and their parents
- exist on the remote side and that no child of a node of base exists
- in both remote and self.
- Furthermore base will be updated to include the nodes that exists
- in self and remote but no children exists in self and remote.
- If a list of heads is specified, return only nodes which are heads
- or ancestors of these heads.
-
- All the ancestors of base are in self and in remote.
- All the descendants of the list returned are missing in self.
- (and so we know that the rest of the nodes are missing in remote, see
- outgoing)
- """
- return self.findcommonincoming(remote, base, heads, force)[1]
-
- def findcommonincoming(self, remote, base=None, heads=None, force=False):
- """Return a tuple (common, missing roots, heads) used to identify
- missing nodes from remote.
-
- If base dict is specified, assume that these nodes and their parents
- exist on the remote side and that no child of a node of base exists
- in both remote and self.
- Furthermore base will be updated to include the nodes that exists
- in self and remote but no children exists in self and remote.
- If a list of heads is specified, return only nodes which are heads
- or ancestors of these heads.
-
- All the ancestors of base are in self and in remote.
- """
- m = self.changelog.nodemap
- search = []
- fetch = set()
- seen = set()
- seenbranch = set()
- if base is None:
- base = {}
-
- if not heads:
- heads = remote.heads()
-
- if self.changelog.tip() == nullid:
- base[nullid] = 1
- if heads != [nullid]:
- return [nullid], [nullid], list(heads)
- return [nullid], [], []
-
- # assume we're closer to the tip than the root
- # and start by examining the heads
- self.ui.status(_("searching for changes\n"))
-
- unknown = []
- for h in heads:
- if h not in m:
- unknown.append(h)
- else:
- base[h] = 1
-
- heads = unknown
- if not unknown:
- return base.keys(), [], []
-
- req = set(unknown)
- 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
-
- self.ui.debug(_("examining %s:%s\n")
- % (short(n[0]), short(n[1])))
- if n[0] == nullid: # found the end of the branch
- pass
- elif n in seenbranch:
- self.ui.debug(_("branch already found\n"))
- continue
- elif n[1] and n[1] in m: # do we know the base?
- self.ui.debug(_("found incomplete branch %s:%s\n")
- % (short(n[0]), short(n[1])))
- search.append(n[0:2]) # schedule branch range for scanning
- seenbranch.add(n)
- else:
- if n[1] not in seen and n[1] not in fetch:
- if n[2] in m and n[3] in m:
- self.ui.debug(_("found new changeset %s\n") %
- short(n[1]))
- fetch.add(n[1]) # earliest unknown
- for p in n[2:4]:
- if p in m:
- base[p] = 1 # latest known
-
- for p in n[2:4]:
- if p not in req and p not in m:
- r.append(p)
- req.add(p)
- seen.add(n[0])
-
- if r:
- reqcnt += 1
- self.ui.debug(_("request %d: %s\n") %
- (reqcnt, " ".join(map(short, r))))
- for p in xrange(0, len(r), 10):
- for b in remote.branches(r[p:p+10]):
- self.ui.debug(_("received %s:%s\n") %
- (short(b[0]), short(b[1])))
- unknown.append(b)
-
- # do binary search on the branches we found
- while search:
- newsearch = []
- reqcnt += 1
- for n, l in zip(search, remote.between(search)):
- l.append(n[1])
- p = n[0]
- f = 1
- for i in l:
- self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
- if i in m:
- if f <= 2:
- self.ui.debug(_("found new branch changeset %s\n") %
- short(p))
- fetch.add(p)
- base[i] = 1
- else:
- self.ui.debug(_("narrowed branch search to %s:%s\n")
- % (short(p), short(i)))
- newsearch.append((p, i))
- break
- p, f = i, f * 2
- search = newsearch
-
- # sanity check our fetch list
- for f in fetch:
- if f in m:
- raise error.RepoError(_("already have changeset ")
- + short(f[:4]))
-
- if base.keys() == [nullid]:
- if force:
- self.ui.warn(_("warning: repository is unrelated\n"))
- else:
- raise util.Abort(_("repository is unrelated"))
-
- self.ui.debug(_("found new changesets starting at ") +
- " ".join([short(f) for f in fetch]) + "\n")
-
- self.ui.debug(_("%d total queries\n") % reqcnt)
-
- return base.keys(), list(fetch), heads
-
- def findoutgoing(self, remote, base=None, heads=None, force=False):
- """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.
- """
- if base is None:
- base = {}
- self.findincoming(remote, base, heads, force=force)
-
- self.ui.debug(_("common changesets up to ")
- + " ".join(map(short, base.keys())) + "\n")
-
- remain = set(self.changelog.nodemap)
-
- # prune everything remote has from the tree
- remain.remove(nullid)
- remove = base.keys()
- while remove:
- n = remove.pop(0)
- if n in remain:
- remain.remove(n)
- for p in self.changelog.parents(n):
- remove.append(p)
-
- # find every node whose parents have been pruned
- subset = []
- # find every remote head that will get new children
- updated_heads = set()
- for n in remain:
- p1, p2 = self.changelog.parents(n)
- if p1 not in remain and p2 not in remain:
- subset.append(n)
- if heads:
- if p1 in heads:
- updated_heads.add(p1)
- if p2 in heads:
- updated_heads.add(p2)
-
- # this is the set of all roots we have to push
- if heads:
- return subset, list(updated_heads)
- else:
- return subset
-
- def pull(self, remote, heads=None, force=False):
- lock = self.lock()
- try:
- common, fetch, rheads = self.findcommonincoming(remote, heads=heads,
- force=force)
- if fetch == [nullid]:
- self.ui.status(_("requesting all changes\n"))
-
- if not fetch:
- self.ui.status(_("no changes found\n"))
- return 0
-
- if heads is None and remote.capable('changegroupsubset'):
- heads = rheads
-
- if heads is None:
- cg = remote.changegroup(fetch, 'pull')
- else:
- if not remote.capable('changegroupsubset'):
- raise util.Abort(_("Partial pull cannot be done because "
- "other repository doesn't support "
- "changegroupsubset."))
- cg = remote.changegroupsubset(fetch, heads, 'pull')
- return self.addchangegroup(cg, 'pull', remote.url())
- finally:
- lock.release()
-
- def push(self, remote, force=False, revs=None):
- # there are two ways to push to remote repo:
- #
- # addchangegroup assumes local user can lock remote
- # repo (local filesystem, old ssh servers).
- #
- # unbundle assumes local user cannot lock remote repo (new ssh
- # servers, http servers).
-
- if remote.capable('unbundle'):
- return self.push_unbundle(remote, force, revs)
- return self.push_addchangegroup(remote, force, revs)
-
- def prepush(self, remote, force, revs):
- common = {}
- remote_heads = remote.heads()
- inc = self.findincoming(remote, common, remote_heads, force=force)
-
- update, updated_heads = self.findoutgoing(remote, common, remote_heads)
- if revs is not None:
- msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
- else:
- bases, heads = update, self.changelog.heads()
-
- def checkbranch(lheads, rheads, updatelh):
- '''
- check whether there are more local heads than remote heads on
- a specific branch.
-
- lheads: local branch heads
- rheads: remote branch heads
- updatelh: outgoing local branch heads
- '''
-
- warn = 0
-
- if not revs and len(lheads) > len(rheads):
- warn = 1
- else:
- updatelheads = [self.changelog.heads(x, lheads)
- for x in updatelh]
- newheads = set(sum(updatelheads, [])) & set(lheads)
-
- if not newheads:
- return True
-
- for r in rheads:
- if r in self.changelog.nodemap:
- desc = self.changelog.heads(r, heads)
- l = [h for h in heads if h in desc]
- if not l:
- newheads.add(r)
- else:
- newheads.add(r)
- if len(newheads) > len(rheads):
- warn = 1
-
- if warn:
- if not rheads: # new branch requires --force
- self.ui.warn(_("abort: push creates new"
- " remote branch '%s'!\n") %
- self[updatelh[0]].branch())
- else:
- self.ui.warn(_("abort: push creates new remote heads!\n"))
-
- self.ui.status(_("(did you forget to merge?"
- " use push -f to force)\n"))
- return False
- return True
-
- if not bases:
- self.ui.status(_("no changes found\n"))
- return None, 1
- elif not force:
- # Check for each named branch if we're creating new remote heads.
- # To be a remote head after push, node must be either:
- # - unknown locally
- # - a local outgoing head descended from update
- # - a remote head that's known locally and not
- # ancestral to an outgoing head
- #
- # New named branches cannot be created without --force.
-
- if remote_heads != [nullid]:
- if remote.capable('branchmap'):
- localhds = {}
- if not revs:
- localhds = self.branchmap()
- else:
- for n in heads:
- branch = self[n].branch()
- if branch in localhds:
- localhds[branch].append(n)
- else:
- localhds[branch] = [n]
-
- remotehds = remote.branchmap()
-
- for lh in localhds:
- if lh in remotehds:
- rheads = remotehds[lh]
- else:
- rheads = []
- lheads = localhds[lh]
- updatelh = [upd for upd in update
- if self[upd].branch() == lh]
- if not updatelh:
- continue
- if not checkbranch(lheads, rheads, updatelh):
- return None, 0
- else:
- if not checkbranch(heads, remote_heads, update):
- return None, 0
-
- if inc:
- self.ui.warn(_("note: unsynced remote changes!\n"))
-
-
- if revs is None:
- # use the fast path, no race possible on push
- cg = self._changegroup(common.keys(), 'push')
- else:
- cg = self.changegroupsubset(update, revs, 'push')
- return cg, remote_heads
-
- def push_addchangegroup(self, remote, force, revs):
- lock = remote.lock()
- try:
- ret = self.prepush(remote, force, revs)
- if ret[0] is not None:
- cg, remote_heads = ret
- return remote.addchangegroup(cg, 'push', self.url())
- return ret[1]
- finally:
- lock.release()
-
- def push_unbundle(self, remote, force, revs):
- # local repo finds heads on server, finds out what revs it
- # must push. once revs transferred, if server finds it has
- # different heads (someone else won commit/push race), server
- # aborts.
-
- ret = self.prepush(remote, force, revs)
- if ret[0] is not None:
- cg, remote_heads = ret
- if force: remote_heads = ['force']
- return remote.unbundle(cg, remote_heads, 'push')
- return ret[1]
-
- def changegroupinfo(self, nodes, source):
- if self.ui.verbose or source == 'bundle':
- self.ui.status(_("%d changesets found\n") % len(nodes))
- if self.ui.debugflag:
- self.ui.debug(_("list of changesets:\n"))
- for node in nodes:
- self.ui.debug("%s\n" % hex(node))
-
- def changegroupsubset(self, bases, heads, source, extranodes=None):
- """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.
-
- 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.
-
- Another wrinkle is doing the reverse, figuring out which changeset in
- the changegroup a particular filenode or manifestnode belongs to.
-
- The caller can specify some nodes that must be included in the
- changegroup using the extranodes argument. It should be a dict
- where the keys are the filenames (or 1 for the manifest), and the
- values are lists of (node, linknode) tuples, where node is a wanted
- node and linknode is the changelog node that should be transmitted as
- the linkrev.
- """
-
- if extranodes is None:
- # can we go through the fast path ?
- heads.sort()
- allheads = self.heads()
- allheads.sort()
- if heads == allheads:
- common = []
- # parents of bases are known from both sides
- for n in bases:
- for p in self.changelog.parents(n):
- if p != nullid:
- common.append(p)
- return self._changegroup(common, source)
-
- self.hook('preoutgoing', throw=True, source=source)
-
- # Set up some initial variables
- # Make it easy to refer to self.changelog
- cl = self.changelog
- # msng is short for missing - compute the list of changesets in this
- # changegroup.
- msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
- self.changegroupinfo(msng_cl_lst, source)
- # 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.
- knownheads = set()
- # We assume that all parents of bases are known heads.
- for n in bases:
- knownheads.update(cl.parents(n))
- knownheads.discard(nullid)
- knownheads = list(knownheads)
- if knownheads:
- # 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.
- has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
- junk = None
- # Transform the list into a set.
- has_cl_set = set(has_cl_set)
- else:
- # If there were no known heads, the recipient cannot be assumed to
- # know about any changesets.
- has_cl_set = set()
-
- # Make it easy to refer to self.manifest
- mnfst = self.manifest
- # We don't know which manifests are missing yet
- msng_mnfst_set = {}
- # Nor do we know which filenodes are missing.
- msng_filenode_set = {}
-
- junk = mnfst.index[len(mnfst) - 1] # Get around a bug in lazyindex
- junk = None
-
- # A changeset always belongs to itself, so the changenode lookup
- # function for a changenode is identity.
- def identity(x):
- return x
-
- # 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.
- def prune_parents(revlog, hasset, msngset):
- haslst = list(hasset)
- haslst.sort(key=revlog.rev)
- 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.add(n)
- p = [p for p in revlog.parents(n) if p != nullid]
- parentlst.extend(p)
- for n in hasset:
- msngset.pop(n, None)
-
- # This is a function generating function used to set up an environment
- # for the inner function to execute in.
- def manifest_and_file_collector(changedfileset):
- # 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.
- def collect_manifests_and_files(clnode):
- c = cl.read(clnode)
- for f in c[3]:
- # This is to make sure we only have one instance of each
- # filename string for each filename.
- changedfileset.setdefault(f, f)
- msng_mnfst_set.setdefault(c[0], clnode)
- return collect_manifests_and_files
-
- # 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.
- def prune_manifests():
- has_mnfst_set = set()
- for n in msng_mnfst_set:
- # If a 'missing' manifest thinks it belongs to a changenode
- # the recipient is assumed to have, obviously the recipient
- # must have that manifest.
- linknode = cl.node(mnfst.linkrev(mnfst.rev(n)))
- if linknode in has_cl_set:
- has_mnfst_set.add(n)
- prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
-
- # Use the information collected in collect_manifests_and_files to say
- # which changenode any manifestnode belongs to.
- def lookup_manifest_link(mnfstnode):
- return msng_mnfst_set[mnfstnode]
-
- # A function generating function that sets up the initial environment
- # the inner function.
- def filenode_collector(changedfiles):
- next_rev = [0]
- # 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.
- def collect_msng_filenodes(mnfstnode):
- 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.
- deltamf = mnfst.readdelta(mnfstnode)
- # For each line in the delta
- for f, fnode in deltamf.iteritems():
- f = changedfiles.get(f, None)
- # And if the file is in the list of files we care
- # about.
- if f is not None:
- # Get the changenode this manifest belongs to
- clnode = msng_mnfst_set[mnfstnode]
- # Create the set of filenodes for the file if
- # there isn't one already.
- ndset = msng_filenode_set.setdefault(f, {})
- # And set the filenode's changelog node to the
- # manifest's if it hasn't been set already.
- ndset.setdefault(fnode, clnode)
- else:
- # Otherwise we need a full manifest.
- m = mnfst.read(mnfstnode)
- # For every file in we care about.
- for f in changedfiles:
- fnode = m.get(f, None)
- # If it's in the manifest
- if fnode is not None:
- # See comments above.
- clnode = msng_mnfst_set[mnfstnode]
- ndset = msng_filenode_set.setdefault(f, {})
- ndset.setdefault(fnode, clnode)
- # Remember the revision we hope to see next.
- next_rev[0] = r + 1
- return collect_msng_filenodes
-
- # We have a list of filenodes we think we need for a file, lets remove
- # all those we know the recipient must have.
- def prune_filenodes(f, filerevlog):
- msngset = msng_filenode_set[f]
- hasset = set()
- # If a 'missing' filenode thinks it belongs to a changenode we
- # assume the recipient must have, then the recipient must have
- # that filenode.
- for n in msngset:
- clnode = cl.node(filerevlog.linkrev(filerevlog.rev(n)))
- if clnode in has_cl_set:
- hasset.add(n)
- prune_parents(filerevlog, hasset, msngset)
-
- # A function generator function that sets up the a context for the
- # inner function.
- def lookup_filenode_link_func(fname):
- msngset = msng_filenode_set[fname]
- # Lookup the changenode the filenode belongs to.
- def lookup_filenode_link(fnode):
- return msngset[fnode]
- return lookup_filenode_link
-
- # Add the nodes that were explicitly requested.
- def add_extra_nodes(name, nodes):
- if not extranodes or name not in extranodes:
- return
-
- for node, linknode in extranodes[name]:
- if node not in nodes:
- nodes[node] = linknode
-
- # Now that we have all theses utility functions to help out and
- # logically divide up the task, generate the group.
- def gengroup():
- # The set of changed files starts empty.
- changedfiles = {}
- # Create a changenode group generator that will call our functions
- # back to lookup the owning changenode and collect information.
- group = cl.group(msng_cl_lst, identity,
- manifest_and_file_collector(changedfiles))
- for chnk in group:
- yield chnk
-
- # The list of manifests has been collected by the generator
- # calling our functions back.
- prune_manifests()
- add_extra_nodes(1, msng_mnfst_set)
- msng_mnfst_lst = msng_mnfst_set.keys()
- # Sort the manifestnodes by revision number.
- msng_mnfst_lst.sort(key=mnfst.rev)
- # Create a generator for the manifestnodes that calls our lookup
- # and data collection functions back.
- group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
- filenode_collector(changedfiles))
- for chnk in group:
- yield chnk
-
- # These are no longer needed, dereference and toss the memory for
- # them.
- msng_mnfst_lst = None
- msng_mnfst_set.clear()
-
- if extranodes:
- for fname in extranodes:
- if isinstance(fname, int):
- continue
- msng_filenode_set.setdefault(fname, {})
- changedfiles[fname] = 1
- # Go through all our files in order sorted by name.
- for fname in sorted(changedfiles):
- filerevlog = self.file(fname)
- if not len(filerevlog):
- raise util.Abort(_("empty or missing revlog for %s") % fname)
- # Toss out the filenodes that the recipient isn't really
- # missing.
- if fname in msng_filenode_set:
- prune_filenodes(fname, filerevlog)
- add_extra_nodes(fname, msng_filenode_set[fname])
- msng_filenode_lst = msng_filenode_set[fname].keys()
- else:
- msng_filenode_lst = []
- # If any filenodes are left, generate the group for them,
- # otherwise don't bother.
- if len(msng_filenode_lst) > 0:
- yield changegroup.chunkheader(len(fname))
- yield fname
- # Sort the filenodes by their revision #
- msng_filenode_lst.sort(key=filerevlog.rev)
- # Create a group generator and only pass in a changenode
- # lookup function as we need to collect no information
- # from filenodes.
- group = filerevlog.group(msng_filenode_lst,
- lookup_filenode_link_func(fname))
- for chnk in group:
- yield chnk
- if fname in msng_filenode_set:
- # Don't need this anymore, toss it to free memory.
- del msng_filenode_set[fname]
- # Signal that no more groups are left.
- yield changegroup.closechunk()
-
- if msng_cl_lst:
- self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
-
- return util.chunkbuffer(gengroup())
-
- def changegroup(self, basenodes, source):
- # to avoid a race we use changegroupsubset() (issue1320)
- return self.changegroupsubset(basenodes, self.heads(), source)
-
- def _changegroup(self, common, source):
- """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.
-
- common is the set of common nodes between remote and self"""
-
- self.hook('preoutgoing', throw=True, source=source)
-
- cl = self.changelog
- nodes = cl.findmissing(common)
- revset = set([cl.rev(n) for n in nodes])
- self.changegroupinfo(nodes, source)
-
- def identity(x):
- return x
-
- def gennodelst(log):
- for r in log:
- if log.linkrev(r) in revset:
- yield log.node(r)
-
- def changed_file_collector(changedfileset):
- def collect_changed_files(clnode):
- c = cl.read(clnode)
- changedfileset.update(c[3])
- return collect_changed_files
-
- def lookuprevlink_func(revlog):
- def lookuprevlink(n):
- return cl.node(revlog.linkrev(revlog.rev(n)))
- return lookuprevlink
-
- def gengroup():
- # construct a list of all changed files
- changedfiles = set()
-
- for chnk in cl.group(nodes, identity,
- changed_file_collector(changedfiles)):
- yield chnk
-
- mnfst = self.manifest
- nodeiter = gennodelst(mnfst)
- for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
- yield chnk
-
- for fname in sorted(changedfiles):
- filerevlog = self.file(fname)
- if not len(filerevlog):
- raise util.Abort(_("empty or missing revlog for %s") % fname)
- nodeiter = gennodelst(filerevlog)
- nodeiter = list(nodeiter)
- if nodeiter:
- yield changegroup.chunkheader(len(fname))
- yield fname
- lookup = lookuprevlink_func(filerevlog)
- for chnk in filerevlog.group(nodeiter, lookup):
- yield chnk
-
- yield changegroup.closechunk()
-
- if nodes:
- self.hook('outgoing', node=hex(nodes[0]), source=source)
-
- return util.chunkbuffer(gengroup())
-
- def addchangegroup(self, source, srctype, url, emptyok=False):
- """add changegroup to repo.
-
- return values:
- - nothing changed or no source: 0
- - more heads than before: 1+added heads (2..n)
- - less heads than before: -1-removed heads (-2..-n)
- - number of heads stays the same: 1
- """
- def csmap(x):
- self.ui.debug(_("add changeset %s\n") % short(x))
- return len(cl)
-
- def revmap(x):
- return cl.rev(x)
-
- if not source:
- return 0
-
- self.hook('prechangegroup', throw=True, source=srctype, url=url)
-
- changesets = files = revisions = 0
-
- # write changelog data to temp files so concurrent readers will not see
- # inconsistent view
- cl = self.changelog
- cl.delayupdate()
- oldheads = len(cl.heads())
-
- tr = self.transaction()
- try:
- trp = weakref.proxy(tr)
- # pull off the changeset group
- self.ui.status(_("adding changesets\n"))
- clstart = len(cl)
- chunkiter = changegroup.chunkiter(source)
- if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok:
- raise util.Abort(_("received changelog group is empty"))
- clend = len(cl)
- changesets = clend - clstart
-
- # pull off the manifest group
- self.ui.status(_("adding manifests\n"))
- chunkiter = changegroup.chunkiter(source)
- # no need to check for empty manifest group here:
- # if the result of the merge of 1 and 2 is the same in 3 and 4,
- # no new manifest will be created and the manifest group will
- # be empty during the pull
- self.manifest.addgroup(chunkiter, revmap, trp)
-
- # 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 = len(fl)
- chunkiter = changegroup.chunkiter(source)
- if fl.addgroup(chunkiter, revmap, trp) is None:
- raise util.Abort(_("received file revlog group is empty"))
- revisions += len(fl) - o
- files += 1
-
- newheads = len(cl.heads())
- heads = ""
- if oldheads and newheads != oldheads:
- heads = _(" (%+d heads)") % (newheads - oldheads)
-
- self.ui.status(_("added %d changesets"
- " with %d changes to %d files%s\n")
- % (changesets, revisions, files, heads))
-
- if changesets > 0:
- p = lambda: cl.writepending() and self.root or ""
- self.hook('pretxnchangegroup', throw=True,
- node=hex(cl.node(clstart)), source=srctype,
- url=url, pending=p)
-
- # make changelog see real files again
- cl.finalize(trp)
-
- tr.close()
- finally:
- del tr
-
- if changesets > 0:
- # forcefully update the on-disk branch cache
- self.ui.debug(_("updating the branch cache\n"))
- self.branchtags()
- self.hook("changegroup", node=hex(cl.node(clstart)),
- source=srctype, url=url)
-
- for i in xrange(clstart, clend):
- self.hook("incoming", node=hex(cl.node(i)),
- source=srctype, url=url)
-
- # never return 0 here:
- if newheads < oldheads:
- return newheads - oldheads - 1
- else:
- return newheads - oldheads + 1
-
-
- def stream_in(self, remote):
- fp = remote.stream_out()
- l = fp.readline()
- try:
- resp = int(l)
- except ValueError:
- raise error.ResponseError(
- _('Unexpected response from remote server:'), l)
- if resp == 1:
- raise util.Abort(_('operation forbidden by server'))
- elif resp == 2:
- raise util.Abort(_('locking the remote repository failed'))
- elif resp != 0:
- raise util.Abort(_('the server sent an unknown error code'))
- self.ui.status(_('streaming all changes\n'))
- l = fp.readline()
- try:
- total_files, total_bytes = map(int, l.split(' ', 1))
- except (ValueError, TypeError):
- raise error.ResponseError(
- _('Unexpected response from remote server:'), l)
- self.ui.status(_('%d files to transfer, %s of data\n') %
- (total_files, util.bytecount(total_bytes)))
- start = time.time()
- for i in xrange(total_files):
- # XXX doesn't support '\n' or '\r' in filenames
- l = fp.readline()
- try:
- name, size = l.split('\0', 1)
- size = int(size)
- except (ValueError, TypeError):
- raise error.ResponseError(
- _('Unexpected response from remote server:'), l)
- self.ui.debug(_('adding %s (%s)\n') % (name, util.bytecount(size)))
- # for backwards compat, name was partially encoded
- ofp = self.sopener(store.decodedir(name), 'w')
- for chunk in util.filechunkiter(fp, limit=size):
- ofp.write(chunk)
- ofp.close()
- elapsed = time.time() - start
- if elapsed <= 0:
- elapsed = 0.001
- self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
- (util.bytecount(total_bytes), elapsed,
- util.bytecount(total_bytes / elapsed)))
- self.invalidate()
- return len(self.heads()) + 1
-
- def clone(self, remote, heads=[], stream=False):
- '''clone remote repository.
-
- keyword arguments:
- heads: list of revs to clone (forces use of pull)
- stream: use streaming clone if possible'''
-
- # now, all clients that can request uncompressed clones can
- # read repo formats supported by all servers that can serve
- # them.
-
- # if revlog format changes, client will have to check version
- # and format flags on "stream" capability, and use
- # uncompressed only if compatible.
-
- if stream and not heads and remote.capable('stream'):
- return self.stream_in(remote)
- return self.pull(remote, heads)
-
-# used to avoid circular references so destructors work
-def aftertrans(files):
- renamefiles = [tuple(t) for t in files]
- def a():
- for src, dest in renamefiles:
- util.rename(src, dest)
- return a
-
-def instance(ui, path, create):
- return localrepository(ui, util.drop_scheme('file', path), create)
-
-def islocal(path):
- return True
diff --git a/sys/src/cmd/hg/mercurial/lock.py b/sys/src/cmd/hg/mercurial/lock.py
deleted file mode 100644
index a3d116e6a..000000000
--- a/sys/src/cmd/hg/mercurial/lock.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# lock.py - simple advisory locking scheme for mercurial
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import util, error
-import errno, os, socket, time
-import warnings
-
-class lock(object):
- '''An advisory lock held by one process to control access to a set
- of files. Non-cooperating processes or incorrectly written scripts
- can ignore Mercurial's locking scheme and stomp all over the
- repository, so don't do that.
-
- Typically used via localrepository.lock() to lock the repository
- store (.hg/store/) or localrepository.wlock() to lock everything
- else under .hg/.'''
-
- # lock is symlink on platforms that support it, file on others.
-
- # symlink is used because create of directory entry and contents
- # are atomic even over nfs.
-
- # 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.desc = desc
- self.lock()
-
- def __del__(self):
- if self.held:
- warnings.warn("use lock.release instead of del lock",
- category=DeprecationWarning,
- stacklevel=2)
-
- # ensure the lock will be removed
- # even if recursive locking did occur
- self.held = 1
-
- self.release()
-
- def lock(self):
- timeout = self.timeout
- while 1:
- try:
- self.trylock()
- return 1
- except error.LockHeld, inst:
- if timeout != 0:
- time.sleep(1)
- if timeout > 0:
- timeout -= 1
- continue
- raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
- inst.locker)
-
- def trylock(self):
- if self.held:
- self.held += 1
- return
- if lock._host is None:
- lock._host = socket.gethostname()
- lockname = '%s:%s' % (lock._host, os.getpid())
- while not self.held:
- try:
- util.makelock(lockname, self.f)
- self.held = 1
- except (OSError, IOError), why:
- if why.errno == errno.EEXIST:
- locker = self.testlock()
- if locker is not None:
- raise error.LockHeld(errno.EAGAIN, self.f, self.desc,
- locker)
- else:
- raise error.LockUnavailable(why.errno, why.strerror,
- why.filename, self.desc)
-
- def testlock(self):
- """return id of locker if lock is valid, else None.
-
- If old-style lock, we cannot tell what machine locker is on.
- with new-style lock, if locker is on this machine, we can
- see if locker is alive. If locker is on this machine but
- not alive, we can safely break lock.
-
- The lock file is only deleted when None is returned.
-
- """
- locker = util.readlock(self.f)
- try:
- host, pid = locker.split(":", 1)
- except ValueError:
- return locker
- if host != lock._host:
- return locker
- try:
- pid = int(pid)
- except:
- return locker
- if util.testpid(pid):
- return locker
- # if locker dead, break lock. must do this with another lock
- # held, or can race and break valid lock.
- try:
- l = lock(self.f + '.break')
- l.trylock()
- os.unlink(self.f)
- l.release()
- except error.LockError:
- return locker
-
- def release(self):
- if self.held > 1:
- self.held -= 1
- elif self.held is 1:
- self.held = 0
- if self.releasefn:
- self.releasefn()
- try:
- os.unlink(self.f)
- except: pass
-
-def release(*locks):
- for lock in locks:
- if lock is not None:
- lock.release()
-
diff --git a/sys/src/cmd/hg/mercurial/lsprof.py b/sys/src/cmd/hg/mercurial/lsprof.py
deleted file mode 100644
index 07f9425ff..000000000
--- a/sys/src/cmd/hg/mercurial/lsprof.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#! /usr/bin/env python
-
-import sys
-from _lsprof import Profiler, profiler_entry
-
-__all__ = ['profile', 'Stats']
-
-def profile(f, *args, **kwds):
- """XXX docstring"""
- p = Profiler()
- p.enable(subcalls=True, builtins=True)
- try:
- f(*args, **kwds)
- finally:
- p.disable()
- return Stats(p.getstats())
-
-
-class Stats(object):
- """XXX docstring"""
-
- def __init__(self, data):
- self.data = data
-
- def sort(self, crit="inlinetime"):
- """XXX docstring"""
- if crit not in profiler_entry.__dict__:
- raise ValueError("Can't sort by %s" % crit)
- self.data.sort(key=lambda x: getattr(x, crit), reverse=True)
- for e in self.data:
- if e.calls:
- e.calls.sort(key=lambda x: getattr(x, crit), reverse=True)
-
- def pprint(self, top=None, file=None, limit=None, climit=None):
- """XXX docstring"""
- if file is None:
- file = sys.stdout
- d = self.data
- if top is not None:
- d = d[:top]
- cols = "% 12s %12s %11.4f %11.4f %s\n"
- hcols = "% 12s %12s %12s %12s %s\n"
- file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
- "Inline(ms)", "module:lineno(function)"))
- count = 0
- for e in d:
- file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
- e.inlinetime, label(e.code)))
- count += 1
- if limit is not None and count == limit:
- return
- ccount = 0
- if e.calls:
- for se in e.calls:
- file.write(cols % ("+%s" % se.callcount, se.reccallcount,
- se.totaltime, se.inlinetime,
- "+%s" % label(se.code)))
- count += 1
- ccount += 1
- if limit is not None and count == limit:
- return
- if climit is not None and ccount == climit:
- break
-
- def freeze(self):
- """Replace all references to code objects with string
- descriptions; this makes it possible to pickle the instance."""
-
- # this code is probably rather ickier than it needs to be!
- for i in range(len(self.data)):
- e = self.data[i]
- if not isinstance(e.code, str):
- self.data[i] = type(e)((label(e.code),) + e[1:])
- if e.calls:
- for j in range(len(e.calls)):
- se = e.calls[j]
- if not isinstance(se.code, str):
- e.calls[j] = type(se)((label(se.code),) + se[1:])
-
-_fn2mod = {}
-
-def label(code):
- if isinstance(code, str):
- return code
- try:
- mname = _fn2mod[code.co_filename]
- except KeyError:
- for k, v in list(sys.modules.iteritems()):
- if v is None:
- continue
- if not hasattr(v, '__file__'):
- continue
- if not isinstance(v.__file__, str):
- continue
- if v.__file__.startswith(code.co_filename):
- mname = _fn2mod[code.co_filename] = k
- break
- else:
- mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
-
- return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
-
-
-if __name__ == '__main__':
- import os
- sys.argv = sys.argv[1:]
- if not sys.argv:
- print >> sys.stderr, "usage: lsprof.py <script> <arguments...>"
- sys.exit(2)
- sys.path.insert(0, os.path.abspath(os.path.dirname(sys.argv[0])))
- stats = profile(execfile, sys.argv[0], globals(), locals())
- stats.sort()
- stats.pprint()
diff --git a/sys/src/cmd/hg/mercurial/lsprofcalltree.py b/sys/src/cmd/hg/mercurial/lsprofcalltree.py
deleted file mode 100644
index 358b951d1..000000000
--- a/sys/src/cmd/hg/mercurial/lsprofcalltree.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""
-lsprofcalltree.py - lsprof output which is readable by kcachegrind
-
-Authors:
- * David Allouche <david <at> allouche.net>
- * Jp Calderone & Itamar Shtull-Trauring
- * Johan Dahlin
-
-This software may be used and distributed according to the terms
-of the GNU General Public License, incorporated herein by reference.
-"""
-
-def label(code):
- if isinstance(code, str):
- return '~' + code # built-in functions ('~' sorts at the end)
- else:
- return '%s %s:%d' % (code.co_name,
- code.co_filename,
- code.co_firstlineno)
-
-class KCacheGrind(object):
- def __init__(self, profiler):
- self.data = profiler.getstats()
- self.out_file = None
-
- def output(self, out_file):
- self.out_file = out_file
- print >> out_file, 'events: Ticks'
- self._print_summary()
- for entry in self.data:
- self._entry(entry)
-
- def _print_summary(self):
- max_cost = 0
- for entry in self.data:
- totaltime = int(entry.totaltime * 1000)
- max_cost = max(max_cost, totaltime)
- print >> self.out_file, 'summary: %d' % (max_cost,)
-
- def _entry(self, entry):
- out_file = self.out_file
-
- code = entry.code
- #print >> out_file, 'ob=%s' % (code.co_filename,)
- if isinstance(code, str):
- print >> out_file, 'fi=~'
- else:
- print >> out_file, 'fi=%s' % (code.co_filename,)
- print >> out_file, 'fn=%s' % (label(code),)
-
- inlinetime = int(entry.inlinetime * 1000)
- if isinstance(code, str):
- print >> out_file, '0 ', inlinetime
- else:
- print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
-
- # recursive calls are counted in entry.calls
- if entry.calls:
- calls = entry.calls
- else:
- calls = []
-
- if isinstance(code, str):
- lineno = 0
- else:
- lineno = code.co_firstlineno
-
- for subentry in calls:
- self._subentry(lineno, subentry)
- print >> out_file
-
- def _subentry(self, lineno, subentry):
- out_file = self.out_file
- code = subentry.code
- #print >> out_file, 'cob=%s' % (code.co_filename,)
- print >> out_file, 'cfn=%s' % (label(code),)
- if isinstance(code, str):
- print >> out_file, 'cfi=~'
- print >> out_file, 'calls=%d 0' % (subentry.callcount,)
- else:
- print >> out_file, 'cfi=%s' % (code.co_filename,)
- print >> out_file, 'calls=%d %d' % (
- subentry.callcount, code.co_firstlineno)
-
- totaltime = int(subentry.totaltime * 1000)
- print >> out_file, '%d %d' % (lineno, totaltime)
diff --git a/sys/src/cmd/hg/mercurial/mail.py b/sys/src/cmd/hg/mercurial/mail.py
deleted file mode 100644
index 3d8222c4f..000000000
--- a/sys/src/cmd/hg/mercurial/mail.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# mail.py - mail sending bits for mercurial
-#
-# Copyright 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import util, encoding
-import os, smtplib, socket, quopri
-import email.Header, email.MIMEText, email.Utils
-
-def _smtp(ui):
- '''build an smtp connection and return a function to send mail'''
- local_hostname = ui.config('smtp', 'local_hostname')
- s = smtplib.SMTP(local_hostname=local_hostname)
- mailhost = ui.config('smtp', 'host')
- if not mailhost:
- raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
- mailport = int(ui.config('smtp', 'port', 25))
- ui.note(_('sending mail: smtp host %s, port %s\n') %
- (mailhost, mailport))
- s.connect(host=mailhost, port=mailport)
- if ui.configbool('smtp', 'tls'):
- if not hasattr(socket, 'ssl'):
- raise util.Abort(_("can't use TLS: Python SSL support "
- "not installed"))
- ui.note(_('(using tls)\n'))
- s.ehlo()
- s.starttls()
- s.ehlo()
- username = ui.config('smtp', 'username')
- password = ui.config('smtp', 'password')
- if username and not password:
- password = ui.getpass()
- if username and password:
- ui.note(_('(authenticating to mail server as %s)\n') %
- (username))
- try:
- s.login(username, password)
- except smtplib.SMTPException, inst:
- raise util.Abort(inst)
-
- def send(sender, recipients, msg):
- try:
- return s.sendmail(sender, recipients, msg)
- except smtplib.SMTPRecipientsRefused, inst:
- recipients = [r[1] for r in inst.recipients.values()]
- raise util.Abort('\n' + '\n'.join(recipients))
- except smtplib.SMTPException, inst:
- raise util.Abort(inst)
-
- return send
-
-def _sendmail(ui, sender, recipients, msg):
- '''send mail using sendmail.'''
- program = ui.config('email', 'method')
- cmdline = '%s -f %s %s' % (program, util.email(sender),
- ' '.join(map(util.email, recipients)))
- ui.note(_('sending mail: %s\n') % cmdline)
- fp = util.popen(cmdline, 'w')
- fp.write(msg)
- ret = fp.close()
- if ret:
- raise util.Abort('%s %s' % (
- os.path.basename(program.split(None, 1)[0]),
- util.explain_exit(ret)[0]))
-
-def connect(ui):
- '''make a mail connection. return a function to send mail.
- call as sendmail(sender, list-of-recipients, msg).'''
- if ui.config('email', 'method', 'smtp') == 'smtp':
- return _smtp(ui)
- return lambda s, r, m: _sendmail(ui, s, r, m)
-
-def sendmail(ui, sender, recipients, msg):
- send = connect(ui)
- return send(sender, recipients, msg)
-
-def validateconfig(ui):
- '''determine if we have enough config data to try sending email.'''
- method = ui.config('email', 'method', 'smtp')
- if method == 'smtp':
- if not ui.config('smtp', 'host'):
- raise util.Abort(_('smtp specified as email transport, '
- 'but no smtp host configured'))
- else:
- if not util.find_exe(method):
- raise util.Abort(_('%r specified as email transport, '
- 'but not in PATH') % method)
-
-def mimetextpatch(s, subtype='plain', display=False):
- '''If patch in utf-8 transfer-encode it.'''
-
- enc = None
- for line in s.splitlines():
- if len(line) > 950:
- s = quopri.encodestring(s)
- enc = "quoted-printable"
- break
-
- cs = 'us-ascii'
- if not display:
- try:
- s.decode('us-ascii')
- except UnicodeDecodeError:
- try:
- s.decode('utf-8')
- cs = 'utf-8'
- except UnicodeDecodeError:
- # We'll go with us-ascii as a fallback.
- pass
-
- msg = email.MIMEText.MIMEText(s, subtype, cs)
- if enc:
- del msg['Content-Transfer-Encoding']
- msg['Content-Transfer-Encoding'] = enc
- return msg
-
-def _charsets(ui):
- '''Obtains charsets to send mail parts not containing patches.'''
- charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')]
- fallbacks = [encoding.fallbackencoding.lower(),
- encoding.encoding.lower(), 'utf-8']
- for cs in fallbacks: # find unique charsets while keeping order
- if cs not in charsets:
- charsets.append(cs)
- return [cs for cs in charsets if not cs.endswith('ascii')]
-
-def _encode(ui, s, charsets):
- '''Returns (converted) string, charset tuple.
- Finds out best charset by cycling through sendcharsets in descending
- order. Tries both encoding and fallbackencoding for input. Only as
- last resort send as is in fake ascii.
- Caveat: Do not use for mail parts containing patches!'''
- try:
- s.decode('ascii')
- except UnicodeDecodeError:
- sendcharsets = charsets or _charsets(ui)
- for ics in (encoding.encoding, encoding.fallbackencoding):
- try:
- u = s.decode(ics)
- except UnicodeDecodeError:
- continue
- for ocs in sendcharsets:
- try:
- return u.encode(ocs), ocs
- except UnicodeEncodeError:
- pass
- except LookupError:
- ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs)
- # if ascii, or all conversion attempts fail, send (broken) ascii
- return s, 'us-ascii'
-
-def headencode(ui, s, charsets=None, display=False):
- '''Returns RFC-2047 compliant header from given string.'''
- if not display:
- # split into words?
- s, cs = _encode(ui, s, charsets)
- return str(email.Header.Header(s, cs))
- return s
-
-def addressencode(ui, address, charsets=None, display=False):
- '''Turns address into RFC-2047 compliant header.'''
- if display or not address:
- return address or ''
- name, addr = email.Utils.parseaddr(address)
- name = headencode(ui, name, charsets)
- try:
- acc, dom = addr.split('@')
- acc = acc.encode('ascii')
- dom = dom.encode('idna')
- addr = '%s@%s' % (acc, dom)
- except UnicodeDecodeError:
- raise util.Abort(_('invalid email address: %s') % addr)
- except ValueError:
- try:
- # too strict?
- addr = addr.encode('ascii')
- except UnicodeDecodeError:
- raise util.Abort(_('invalid local address: %s') % addr)
- return email.Utils.formataddr((name, addr))
-
-def mimeencode(ui, s, charsets=None, display=False):
- '''creates mime text object, encodes it if needed, and sets
- charset and transfer-encoding accordingly.'''
- cs = 'us-ascii'
- if not display:
- s, cs = _encode(ui, s, charsets)
- return email.MIMEText.MIMEText(s, 'plain', cs)
diff --git a/sys/src/cmd/hg/mercurial/manifest.py b/sys/src/cmd/hg/mercurial/manifest.py
deleted file mode 100644
index 7f7558403..000000000
--- a/sys/src/cmd/hg/mercurial/manifest.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# manifest.py - manifest revision class for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import mdiff, parsers, error, revlog
-import array, struct
-
-class manifestdict(dict):
- def __init__(self, mapping=None, flags=None):
- if mapping is None: mapping = {}
- if flags is None: flags = {}
- dict.__init__(self, mapping)
- self._flags = flags
- def flags(self, f):
- return self._flags.get(f, "")
- def set(self, f, flags):
- self._flags[f] = flags
- def copy(self):
- return manifestdict(dict.copy(self), dict.copy(self._flags))
-
-class manifest(revlog.revlog):
- def __init__(self, opener):
- self.mapcache = None
- self.listcache = None
- revlog.revlog.__init__(self, opener, "00manifest.i")
-
- def parse(self, lines):
- mfdict = manifestdict()
- parsers.parse_manifest(mfdict, mfdict._flags, lines)
- return mfdict
-
- def readdelta(self, node):
- r = self.rev(node)
- return self.parse(mdiff.patchtext(self.revdiff(r - 1, r)))
-
- def read(self, node):
- if node == revlog.nullid:
- return manifestdict() # don't upset local cache
- if self.mapcache and self.mapcache[0] == node:
- return self.mapcache[1]
- text = self.revision(node)
- self.listcache = array.array('c', text)
- mapping = self.parse(text)
- self.mapcache = (node, mapping)
- return mapping
-
- def _search(self, m, s, lo=0, hi=None):
- '''return a tuple (start, end) that says where to find s within m.
-
- If the string is found m[start:end] are the line containing
- that string. If start == end the string was not found and
- they indicate the proper sorted insertion point. This was
- taken from bisect_left, and modified to find line start/end as
- it goes along.
-
- m should be a buffer or a string
- s is a string'''
- def advance(i, c):
- while i < lenm and m[i] != c:
- i += 1
- return i
- if not s:
- return (lo, lo)
- lenm = len(m)
- if not hi:
- hi = lenm
- while lo < hi:
- mid = (lo + hi) // 2
- start = mid
- while start > 0 and m[start-1] != '\n':
- start -= 1
- end = advance(start, '\0')
- if m[start:end] < s:
- # we know that after the null there are 40 bytes of sha1
- # this translates to the bisect lo = mid + 1
- lo = advance(end + 40, '\n') + 1
- else:
- # this translates to the bisect hi = mid
- hi = start
- end = advance(lo, '\0')
- found = m[lo:end]
- if cmp(s, found) == 0:
- # we know that after the null there are 40 bytes of sha1
- end = advance(end + 40, '\n')
- return (lo, end+1)
- else:
- return (lo, lo)
-
- def find(self, node, f):
- '''look up entry for a single file efficiently.
- return (node, flags) pair if found, (None, None) if not.'''
- if self.mapcache and node == self.mapcache[0]:
- return self.mapcache[1].get(f), self.mapcache[1].flags(f)
- text = self.revision(node)
- start, end = self._search(text, f)
- if start == end:
- return None, None
- l = text[start:end]
- f, n = l.split('\0')
- return revlog.bin(n[:40]), n[40:-1]
-
- def add(self, map, transaction, link, p1=None, p2=None,
- changed=None):
- # apply the changes collected during the bisect loop to our addlist
- # return a delta suitable for addrevision
- def addlistdelta(addlist, x):
- # start from the bottom up
- # so changes to the offsets don't mess things up.
- i = len(x)
- while i > 0:
- i -= 1
- start = x[i][0]
- end = x[i][1]
- if x[i][2]:
- addlist[start:end] = array.array('c', x[i][2])
- else:
- del addlist[start:end]
- return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2]
- for d in x ])
-
- def checkforbidden(l):
- for f in l:
- if '\n' in f or '\r' in f:
- raise error.RevlogError(
- _("'\\n' and '\\r' disallowed in filenames: %r") % f)
-
- # if we're using the listcache, make sure it is valid and
- # parented by the same node we're diffing against
- if not (changed and self.listcache and p1 and self.mapcache[0] == p1):
- files = sorted(map)
- checkforbidden(files)
-
- # if this is changed to support newlines in filenames,
- # be sure to check the templates/ dir again (especially *-raw.tmpl)
- hex, flags = revlog.hex, map.flags
- text = ["%s\000%s%s\n" % (f, hex(map[f]), flags(f))
- for f in files]
- self.listcache = array.array('c', "".join(text))
- cachedelta = None
- else:
- addlist = self.listcache
-
- checkforbidden(changed[0])
- # combine the changed lists into one list for sorting
- work = [[x, 0] for x in changed[0]]
- work[len(work):] = [[x, 1] for x in changed[1]]
- work.sort()
-
- delta = []
- dstart = None
- dend = None
- dline = [""]
- start = 0
- # zero copy representation of addlist as a buffer
- addbuf = buffer(addlist)
-
- # start with a readonly loop that finds the offset of
- # each line and creates the deltas
- for w in work:
- f = w[0]
- # bs will either be the index of the item or the insert point
- start, end = self._search(addbuf, f, start)
- if w[1] == 0:
- l = "%s\000%s%s\n" % (f, revlog.hex(map[f]), map.flags(f))
- else:
- l = ""
- if start == end and w[1] == 1:
- # item we want to delete was not found, error out
- raise AssertionError(
- _("failed to remove %s from manifest") % f)
- if dstart != None and dstart <= start and dend >= start:
- if dend < end:
- dend = end
- if l:
- dline.append(l)
- else:
- if dstart != None:
- delta.append([dstart, dend, "".join(dline)])
- dstart = start
- dend = end
- dline = [l]
-
- if dstart != None:
- delta.append([dstart, dend, "".join(dline)])
- # apply the delta to the addlist, and get a delta for addrevision
- cachedelta = addlistdelta(addlist, delta)
-
- # the delta is only valid if we've been processing the tip revision
- if self.mapcache[0] != self.tip():
- cachedelta = None
- self.listcache = addlist
-
- n = self.addrevision(buffer(self.listcache), transaction, link,
- p1, p2, cachedelta)
- self.mapcache = (n, map)
-
- return n
diff --git a/sys/src/cmd/hg/mercurial/match.py b/sys/src/cmd/hg/mercurial/match.py
deleted file mode 100644
index 34b6fc1e8..000000000
--- a/sys/src/cmd/hg/mercurial/match.py
+++ /dev/null
@@ -1,249 +0,0 @@
-# match.py - filename matching
-#
-# Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import re
-import util
-
-class match(object):
- def __init__(self, root, cwd, patterns, include=[], exclude=[],
- default='glob', exact=False):
- """build an object to match a set of file patterns
-
- arguments:
- root - the canonical root of the tree you're matching against
- cwd - the current working directory, if relevant
- patterns - patterns to find
- include - patterns to include
- exclude - patterns to exclude
- default - if a pattern in names has no explicit type, assume this one
- exact - patterns are actually literals
-
- a pattern is one of:
- 'glob:<glob>' - a glob relative to cwd
- 're:<regexp>' - a regular expression
- 'path:<path>' - a path relative to canonroot
- 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
- 'relpath:<path>' - a path relative to cwd
- 'relre:<regexp>' - a regexp that needn't match the start of a name
- '<something>' - a pattern of the specified default type
- """
-
- self._root = root
- self._cwd = cwd
- self._files = []
- self._anypats = bool(include or exclude)
-
- if include:
- im = _buildmatch(_normalize(include, 'glob', root, cwd), '(?:/|$)')
- if exclude:
- em = _buildmatch(_normalize(exclude, 'glob', root, cwd), '(?:/|$)')
- if exact:
- self._files = patterns
- pm = self.exact
- elif patterns:
- pats = _normalize(patterns, default, root, cwd)
- self._files = _roots(pats)
- self._anypats = self._anypats or _anypats(pats)
- pm = _buildmatch(pats, '$')
-
- if patterns or exact:
- if include:
- if exclude:
- m = lambda f: im(f) and not em(f) and pm(f)
- else:
- m = lambda f: im(f) and pm(f)
- else:
- if exclude:
- m = lambda f: not em(f) and pm(f)
- else:
- m = pm
- else:
- if include:
- if exclude:
- m = lambda f: im(f) and not em(f)
- else:
- m = im
- else:
- if exclude:
- m = lambda f: not em(f)
- else:
- m = lambda f: True
-
- self.matchfn = m
- self._fmap = set(self._files)
-
- def __call__(self, fn):
- return self.matchfn(fn)
- def __iter__(self):
- for f in self._files:
- yield f
- def bad(self, f, msg):
- '''callback for each explicit file that can't be
- found/accessed, with an error message
- '''
- pass
- def dir(self, f):
- pass
- def missing(self, f):
- pass
- def exact(self, f):
- return f in self._fmap
- def rel(self, f):
- return util.pathto(self._root, self._cwd, f)
- def files(self):
- return self._files
- def anypats(self):
- return self._anypats
-
-class exact(match):
- def __init__(self, root, cwd, files):
- match.__init__(self, root, cwd, files, exact = True)
-
-class always(match):
- def __init__(self, root, cwd):
- match.__init__(self, root, cwd, [])
-
-def patkind(pat):
- return _patsplit(pat, None)[0]
-
-def _patsplit(pat, default):
- """Split a string into an optional pattern kind prefix and the
- actual pattern."""
- if ':' in pat:
- kind, val = pat.split(':', 1)
- if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre'):
- return kind, val
- return default, pat
-
-def _globre(pat):
- "convert a glob pattern into a regexp"
- i, n = 0, len(pat)
- res = ''
- group = 0
- escape = re.escape
- def peek(): return i < n and pat[i]
- while i < n:
- c = pat[i]
- i = i+1
- if c not in '*?[{},\\':
- res += escape(c)
- elif c == '*':
- if peek() == '*':
- i += 1
- res += '.*'
- else:
- res += '[^/]*'
- elif c == '?':
- res += '.'
- elif c == '[':
- j = i
- if j < n and pat[j] in '!]':
- j += 1
- while j < n and pat[j] != ']':
- j += 1
- if j >= n:
- res += '\\['
- else:
- stuff = pat[i:j].replace('\\','\\\\')
- i = j + 1
- if stuff[0] == '!':
- stuff = '^' + stuff[1:]
- elif stuff[0] == '^':
- stuff = '\\' + stuff
- res = '%s[%s]' % (res, stuff)
- elif c == '{':
- group += 1
- res += '(?:'
- elif c == '}' and group:
- res += ')'
- group -= 1
- elif c == ',' and group:
- res += '|'
- elif c == '\\':
- p = peek()
- if p:
- i += 1
- res += escape(p)
- else:
- res += escape(c)
- else:
- res += escape(c)
- return res
-
-def _regex(kind, name, tail):
- '''convert a pattern into a regular expression'''
- if not name:
- return ''
- if kind == 're':
- return name
- elif kind == 'path':
- return '^' + re.escape(name) + '(?:/|$)'
- elif kind == 'relglob':
- return '(?:|.*/)' + _globre(name) + tail
- elif kind == 'relpath':
- return re.escape(name) + '(?:/|$)'
- elif kind == 'relre':
- if name.startswith('^'):
- return name
- return '.*' + name
- return _globre(name) + tail
-
-def _buildmatch(pats, tail):
- """build a matching function from a set of patterns"""
- try:
- pat = '(?:%s)' % '|'.join([_regex(k, p, tail) for (k, p) in pats])
- if len(pat) > 20000:
- raise OverflowError()
- return re.compile(pat).match
- except OverflowError:
- # We're using a Python with a tiny regex engine and we
- # made it explode, so we'll divide the pattern list in two
- # until it works
- l = len(pats)
- if l < 2:
- raise
- a, b = _buildmatch(pats[:l//2], tail), _buildmatch(pats[l//2:], tail)
- return lambda s: a(s) or b(s)
- except re.error:
- for k, p in pats:
- try:
- re.compile('(?:%s)' % _regex(k, p, tail))
- except re.error:
- raise util.Abort("invalid pattern (%s): %s" % (k, p))
- raise util.Abort("invalid pattern")
-
-def _normalize(names, default, root, cwd):
- pats = []
- for kind, name in [_patsplit(p, default) for p in names]:
- if kind in ('glob', 'relpath'):
- name = util.canonpath(root, cwd, name)
- elif kind in ('relglob', 'path'):
- name = util.normpath(name)
-
- pats.append((kind, name))
- return pats
-
-def _roots(patterns):
- r = []
- for kind, name in patterns:
- if kind == 'glob': # find the non-glob prefix
- root = []
- for p in name.split('/'):
- if '[' in p or '{' in p or '*' in p or '?' in p:
- break
- root.append(p)
- r.append('/'.join(root) or '.')
- elif kind in ('relpath', 'path'):
- r.append(name or '.')
- elif kind == 'relglob':
- r.append('.')
- return r
-
-def _anypats(patterns):
- for kind, name in patterns:
- if kind in ('glob', 're', 'relglob', 'relre'):
- return True
diff --git a/sys/src/cmd/hg/mercurial/mdiff.py b/sys/src/cmd/hg/mercurial/mdiff.py
deleted file mode 100644
index 44d4d4560..000000000
--- a/sys/src/cmd/hg/mercurial/mdiff.py
+++ /dev/null
@@ -1,269 +0,0 @@
-# mdiff.py - diff and patch routines for mercurial
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import bdiff, mpatch, util
-import re, struct
-
-def splitnewlines(text):
- '''like str.splitlines, but only split on newlines.'''
- lines = [l + '\n' for l in text.split('\n')]
- if lines:
- if lines[-1] == '\n':
- lines.pop()
- else:
- lines[-1] = lines[-1][:-1]
- return lines
-
-class diffopts(object):
- '''context is the number of context lines
- text treats all files as text
- showfunc enables diff -p output
- git enables the git extended patch format
- nodates removes dates from diff headers
- ignorews ignores all whitespace changes in the diff
- ignorewsamount ignores changes in the amount of whitespace
- ignoreblanklines ignores changes whose lines are all blank'''
-
- defaults = {
- 'context': 3,
- 'text': False,
- 'showfunc': False,
- 'git': False,
- 'nodates': False,
- 'ignorews': False,
- 'ignorewsamount': False,
- 'ignoreblanklines': False,
- }
-
- __slots__ = defaults.keys()
-
- def __init__(self, **opts):
- for k in self.__slots__:
- v = opts.get(k)
- if v is None:
- v = self.defaults[k]
- setattr(self, k, v)
-
- try:
- self.context = int(self.context)
- except ValueError:
- raise util.Abort(_('diff context lines count must be '
- 'an integer, not %r') % self.context)
-
-defaultopts = diffopts()
-
-def wsclean(opts, text):
- if opts.ignorews:
- text = re.sub('[ \t]+', '', text)
- elif opts.ignorewsamount:
- text = re.sub('[ \t]+', ' ', text)
- text = re.sub('[ \t]+\n', '\n', text)
- if opts.ignoreblanklines:
- text = re.sub('\n+', '', text)
- return text
-
-def diffline(revs, a, b, opts):
- parts = ['diff']
- if opts.git:
- parts.append('--git')
- if revs and not opts.git:
- parts.append(' '.join(["-r %s" % rev for rev in revs]))
- if opts.git:
- parts.append('a/%s' % a)
- parts.append('b/%s' % b)
- else:
- parts.append(a)
- return ' '.join(parts) + '\n'
-
-def unidiff(a, ad, b, bd, fn1, fn2, r=None, opts=defaultopts):
- def datetag(date, addtab=True):
- if not opts.git and not opts.nodates:
- return '\t%s\n' % date
- if addtab and ' ' in fn1:
- return '\t\n'
- return '\n'
-
- if not a and not b: return ""
- epoch = util.datestr((0, 0))
-
- if not opts.text and (util.binary(a) or util.binary(b)):
- if a and b and len(a) == len(b) and a == b:
- return ""
- l = ['Binary file %s has changed\n' % fn1]
- elif not a:
- b = splitnewlines(b)
- if a is None:
- l1 = '--- /dev/null%s' % datetag(epoch, False)
- else:
- l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
- l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
- l3 = "@@ -0,0 +1,%d @@\n" % len(b)
- l = [l1, l2, l3] + ["+" + e for e in b]
- elif not b:
- a = splitnewlines(a)
- l1 = "--- %s%s" % ("a/" + fn1, datetag(ad))
- if b is None:
- l2 = '+++ /dev/null%s' % datetag(epoch, False)
- else:
- l2 = "+++ %s%s" % ("b/" + fn2, datetag(bd))
- l3 = "@@ -1,%d +0,0 @@\n" % len(a)
- l = [l1, l2, l3] + ["-" + e for e in a]
- else:
- al = splitnewlines(a)
- bl = splitnewlines(b)
- l = list(bunidiff(a, b, al, bl, "a/" + fn1, "b/" + fn2, opts=opts))
- if not l: return ""
- # difflib uses a space, rather than a tab
- l[0] = "%s%s" % (l[0][:-2], datetag(ad))
- l[1] = "%s%s" % (l[1][:-2], datetag(bd))
-
- for ln in xrange(len(l)):
- if l[ln][-1] != '\n':
- l[ln] += "\n\ No newline at end of file\n"
-
- if r:
- l.insert(0, diffline(r, fn1, fn2, opts))
-
- return "".join(l)
-
-# somewhat self contained replacement for difflib.unified_diff
-# t1 and t2 are the text to be diffed
-# l1 and l2 are the text broken up into lines
-# header1 and header2 are the filenames for the diff output
-def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
- def contextend(l, len):
- ret = l + opts.context
- if ret > len:
- ret = len
- return ret
-
- def contextstart(l):
- ret = l - opts.context
- if ret < 0:
- return 0
- return ret
-
- def yieldhunk(hunk, header):
- if header:
- for x in header:
- yield x
- (astart, a2, bstart, b2, delta) = hunk
- aend = contextend(a2, len(l1))
- alen = aend - astart
- blen = b2 - bstart + aend - a2
-
- func = ""
- if opts.showfunc:
- # walk backwards from the start of the context
- # to find a line starting with an alphanumeric char.
- for x in xrange(astart - 1, -1, -1):
- t = l1[x].rstrip()
- if funcre.match(t):
- func = ' ' + t[:40]
- break
-
- yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
- bstart + 1, blen, func)
- for x in delta:
- yield x
- for x in xrange(a2, aend):
- yield ' ' + l1[x]
-
- header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
-
- if opts.showfunc:
- funcre = re.compile('\w')
-
- # bdiff.blocks gives us the matching sequences in the files. The loop
- # below finds the spaces between those matching sequences and translates
- # them into diff output.
- #
- diff = bdiff.blocks(t1, t2)
- hunk = None
- for i, s1 in enumerate(diff):
- # The first match is special.
- # we've either found a match starting at line 0 or a match later
- # in the file. If it starts later, old and new below will both be
- # empty and we'll continue to the next match.
- if i > 0:
- s = diff[i-1]
- else:
- s = [0, 0, 0, 0]
- delta = []
- a1 = s[1]
- a2 = s1[0]
- b1 = s[3]
- b2 = s1[2]
-
- old = l1[a1:a2]
- new = l2[b1:b2]
-
- # bdiff sometimes gives huge matches past eof, this check eats them,
- # and deals with the special first match case described above
- if not old and not new:
- continue
-
- if opts.ignorews or opts.ignorewsamount or opts.ignoreblanklines:
- if wsclean(opts, "".join(old)) == wsclean(opts, "".join(new)):
- continue
-
- astart = contextstart(a1)
- bstart = contextstart(b1)
- prev = None
- if hunk:
- # join with the previous hunk if it falls inside the context
- if astart < hunk[1] + opts.context + 1:
- prev = hunk
- astart = hunk[1]
- bstart = hunk[3]
- else:
- for x in yieldhunk(hunk, header):
- yield x
- # we only want to yield the header if the files differ, and
- # we only want to yield it once.
- header = None
- if prev:
- # we've joined the previous hunk, record the new ending points.
- hunk[1] = a2
- hunk[3] = b2
- delta = hunk[4]
- else:
- # create a new hunk
- hunk = [ astart, a2, bstart, b2, delta ]
-
- delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
- delta[len(delta):] = [ '-' + x for x in old ]
- delta[len(delta):] = [ '+' + x for x in new ]
-
- if hunk:
- for x in yieldhunk(hunk, header):
- yield x
-
-def patchtext(bin):
- pos = 0
- t = []
- while pos < len(bin):
- p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
- pos += 12
- t.append(bin[pos:pos + l])
- pos += l
- return "".join(t)
-
-def patch(a, bin):
- return mpatch.patches(a, [bin])
-
-# similar to difflib.SequenceMatcher.get_matching_blocks
-def get_matching_blocks(a, b):
- return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
-
-def trivialdiffheader(length):
- return struct.pack(">lll", 0, 0, length)
-
-patches = mpatch.patches
-patchedsize = mpatch.patchedsize
-textdiff = bdiff.bdiff
diff --git a/sys/src/cmd/hg/mercurial/merge.py b/sys/src/cmd/hg/mercurial/merge.py
deleted file mode 100644
index fc04d040d..000000000
--- a/sys/src/cmd/hg/mercurial/merge.py
+++ /dev/null
@@ -1,481 +0,0 @@
-# merge.py - directory-level update/merge handling for Mercurial
-#
-# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import nullid, nullrev, hex, bin
-from i18n import _
-import util, filemerge, copies, subrepo
-import errno, os, shutil
-
-class mergestate(object):
- '''track 3-way merge state of individual files'''
- def __init__(self, repo):
- self._repo = repo
- self._read()
- def reset(self, node=None):
- self._state = {}
- if node:
- self._local = node
- shutil.rmtree(self._repo.join("merge"), True)
- def _read(self):
- self._state = {}
- try:
- localnode = None
- f = self._repo.opener("merge/state")
- for i, l in enumerate(f):
- if i == 0:
- localnode = l[:-1]
- else:
- bits = l[:-1].split("\0")
- self._state[bits[0]] = bits[1:]
- self._local = bin(localnode)
- except IOError, err:
- if err.errno != errno.ENOENT:
- raise
- def _write(self):
- f = self._repo.opener("merge/state", "w")
- f.write(hex(self._local) + "\n")
- for d, v in self._state.iteritems():
- f.write("\0".join([d] + v) + "\n")
- def add(self, fcl, fco, fca, fd, flags):
- hash = util.sha1(fcl.path()).hexdigest()
- self._repo.opener("merge/" + hash, "w").write(fcl.data())
- self._state[fd] = ['u', hash, fcl.path(), fca.path(),
- hex(fca.filenode()), fco.path(), flags]
- self._write()
- def __contains__(self, dfile):
- return dfile in self._state
- def __getitem__(self, dfile):
- return self._state[dfile][0]
- def __iter__(self):
- l = self._state.keys()
- l.sort()
- for f in l:
- yield f
- def mark(self, dfile, state):
- self._state[dfile][0] = state
- self._write()
- def resolve(self, dfile, wctx, octx):
- if self[dfile] == 'r':
- return 0
- state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
- f = self._repo.opener("merge/" + hash)
- self._repo.wwrite(dfile, f.read(), flags)
- fcd = wctx[dfile]
- fco = octx[ofile]
- fca = self._repo.filectx(afile, fileid=anode)
- r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
- if not r:
- self.mark(dfile, 'r')
- return r
-
-def _checkunknown(wctx, mctx):
- "check for collisions between unknown files and files in mctx"
- for f in wctx.unknown():
- if f in mctx and mctx[f].cmp(wctx[f].data()):
- raise util.Abort(_("untracked file in working directory differs"
- " from file in requested revision: '%s'") % f)
-
-def _checkcollision(mctx):
- "check for case folding collisions in the destination context"
- folded = {}
- for fn in mctx:
- fold = fn.lower()
- if fold in folded:
- raise util.Abort(_("case-folding collision between %s and %s")
- % (fn, folded[fold]))
- folded[fold] = fn
-
-def _forgetremoved(wctx, mctx, branchmerge):
- """
- Forget removed files
-
- 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 we're merging, and the other revision has removed a file
- that is not present in the working directory, we need to mark it
- as removed.
- """
-
- action = []
- state = branchmerge and 'r' or 'f'
- for f in wctx.deleted():
- if f not in mctx:
- action.append((f, state))
-
- if not branchmerge:
- for f in wctx.removed():
- if f not in mctx:
- action.append((f, "f"))
-
- return action
-
-def manifestmerge(repo, p1, p2, pa, overwrite, partial):
- """
- Merge p1 and p2 with ancestor ma and generate merge action list
-
- overwrite = whether we clobber working files
- partial = function to filter file lists
- """
-
- def fmerge(f, f2, fa):
- """merge flags"""
- a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
- if m == n: # flags agree
- return m # unchanged
- if m and n and not a: # flags set, don't agree, differ from parent
- r = repo.ui.promptchoice(
- _(" conflicting flags for %s\n"
- "(n)one, e(x)ec or sym(l)ink?") % f,
- (_("&None"), _("E&xec"), _("Sym&link")), 0)
- if r == 1: return "x" # Exec
- if r == 2: return "l" # Symlink
- return ""
- if m and m != a: # changed from a to m
- return m
- if n and n != a: # changed from a to n
- return n
- return '' # flag was cleared
-
- def act(msg, m, f, *args):
- repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
- action.append((f, m) + args)
-
- action, copy = [], {}
-
- if overwrite:
- pa = p1
- elif pa == p2: # backwards
- pa = p1.p1()
- elif pa and repo.ui.configbool("merge", "followcopies", True):
- dirs = repo.ui.configbool("merge", "followdirs", True)
- copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
- for of, fl in diverge.iteritems():
- act("divergent renames", "dr", of, fl)
-
- repo.ui.note(_("resolving manifests\n"))
- repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
- repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
-
- m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
- copied = set(copy.values())
-
- # Compare manifests
- for f, n in m1.iteritems():
- if partial and not partial(f):
- continue
- if f in m2:
- rflags = fmerge(f, f, f)
- a = ma.get(f, nullid)
- if n == m2[f] or m2[f] == a: # same or local newer
- if m1.flags(f) != rflags:
- act("update permissions", "e", f, rflags)
- elif n == a: # remote newer
- act("remote is newer", "g", f, rflags)
- else: # both changed
- act("versions differ", "m", f, f, f, rflags, False)
- elif f in copied: # files we'll deal with on m2 side
- pass
- elif f in copy:
- f2 = copy[f]
- if f2 not in m2: # directory rename
- act("remote renamed directory to " + f2, "d",
- f, None, f2, m1.flags(f))
- else: # case 2 A,B/B/B or case 4,21 A/B/B
- act("local copied/moved to " + f2, "m",
- f, f2, f, fmerge(f, f2, f2), False)
- elif f in ma: # clean, a different, no remote
- if n != ma[f]:
- if repo.ui.promptchoice(
- _(" local changed %s which remote deleted\n"
- "use (c)hanged version or (d)elete?") % f,
- (_("&Changed"), _("&Delete")), 0):
- act("prompt delete", "r", f)
- else:
- act("prompt keep", "a", f)
- elif n[20:] == "a": # added, no remote
- act("remote deleted", "f", f)
- elif n[20:] != "u":
- act("other deleted", "r", f)
-
- for f, n in m2.iteritems():
- if partial and not partial(f):
- continue
- if f in m1 or f in copied: # files already visited
- continue
- if f in copy:
- f2 = copy[f]
- if f2 not in m1: # directory rename
- act("local renamed directory to " + f2, "d",
- None, f, f2, m2.flags(f))
- elif f2 in m2: # rename case 1, A/A,B/A
- act("remote copied to " + f, "m",
- f2, f, f, fmerge(f2, f, f2), False)
- else: # case 3,20 A/B/A
- act("remote moved to " + f, "m",
- f2, f, f, fmerge(f2, f, f2), True)
- elif f not in ma:
- act("remote created", "g", f, m2.flags(f))
- elif n != ma[f]:
- if repo.ui.promptchoice(
- _("remote changed %s which local deleted\n"
- "use (c)hanged version or leave (d)eleted?") % f,
- (_("&Changed"), _("&Deleted")), 0) == 0:
- act("prompt recreating", "g", f, m2.flags(f))
-
- return action
-
-def actionkey(a):
- return a[1] == 'r' and -1 or 0, a
-
-def applyupdates(repo, action, wctx, mctx):
- "apply the merge action list to the working directory"
-
- updated, merged, removed, unresolved = 0, 0, 0, 0
- ms = mergestate(repo)
- ms.reset(wctx.parents()[0].node())
- moves = []
- action.sort(key=actionkey)
- substate = wctx.substate # prime
-
- # prescan for merges
- for a in action:
- f, m = a[:2]
- if m == 'm': # merge
- f2, fd, flags, move = a[2:]
- if f == '.hgsubstate': # merged internally
- continue
- repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
- fcl = wctx[f]
- fco = mctx[f2]
- fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
- ms.add(fcl, fco, fca, fd, flags)
- if f != fd and move:
- moves.append(f)
-
- # remove renamed files after safely stored
- for f in moves:
- if util.lexists(repo.wjoin(f)):
- repo.ui.debug(_("removing %s\n") % f)
- os.unlink(repo.wjoin(f))
-
- audit_path = util.path_auditor(repo.root)
-
- for a in action:
- f, m = a[:2]
- if f and f[0] == "/":
- continue
- if m == "r": # remove
- repo.ui.note(_("removing %s\n") % f)
- audit_path(f)
- if f == '.hgsubstate': # subrepo states need updating
- subrepo.submerge(repo, wctx, mctx, wctx)
- try:
- util.unlink(repo.wjoin(f))
- except OSError, inst:
- if inst.errno != errno.ENOENT:
- repo.ui.warn(_("update failed to remove %s: %s!\n") %
- (f, inst.strerror))
- removed += 1
- elif m == "m": # merge
- if f == '.hgsubstate': # subrepo states need updating
- subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
- continue
- f2, fd, flags, move = a[2:]
- r = ms.resolve(fd, wctx, mctx)
- if r is not None and r > 0:
- unresolved += 1
- else:
- if r is None:
- updated += 1
- else:
- merged += 1
- util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
- if f != fd and move and util.lexists(repo.wjoin(f)):
- repo.ui.debug(_("removing %s\n") % f)
- os.unlink(repo.wjoin(f))
- elif m == "g": # get
- flags = a[2]
- repo.ui.note(_("getting %s\n") % f)
- t = mctx.filectx(f).data()
- repo.wwrite(f, t, flags)
- updated += 1
- if f == '.hgsubstate': # subrepo states need updating
- subrepo.submerge(repo, wctx, mctx, wctx)
- elif m == "d": # directory rename
- f2, fd, flags = a[2:]
- if f:
- repo.ui.note(_("moving %s to %s\n") % (f, fd))
- t = wctx.filectx(f).data()
- repo.wwrite(fd, t, flags)
- util.unlink(repo.wjoin(f))
- if f2:
- repo.ui.note(_("getting %s to %s\n") % (f2, fd))
- t = mctx.filectx(f2).data()
- repo.wwrite(fd, t, flags)
- updated += 1
- elif m == "dr": # divergent renames
- fl = a[2]
- repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
- for nf in fl:
- repo.ui.warn(" %s\n" % nf)
- elif m == "e": # exec
- flags = a[2]
- util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
-
- return updated, merged, removed, unresolved
-
-def recordupdates(repo, action, branchmerge):
- "record merge actions to the dirstate"
-
- for a in action:
- f, m = a[:2]
- if m == "r": # remove
- if branchmerge:
- repo.dirstate.remove(f)
- else:
- repo.dirstate.forget(f)
- elif m == "a": # re-add
- if not branchmerge:
- repo.dirstate.add(f)
- elif m == "f": # forget
- repo.dirstate.forget(f)
- elif m == "e": # exec change
- repo.dirstate.normallookup(f)
- elif m == "g": # get
- if branchmerge:
- repo.dirstate.normaldirty(f)
- else:
- repo.dirstate.normal(f)
- elif m == "m": # merge
- f2, fd, flag, move = a[2:]
- if branchmerge:
- # We've done a branch merge, mark this file as merged
- # so that we properly record the merger later
- repo.dirstate.merge(fd)
- if f != f2: # copy/rename
- if move:
- repo.dirstate.remove(f)
- if f != fd:
- repo.dirstate.copy(f, fd)
- else:
- repo.dirstate.copy(f2, fd)
- 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.
- repo.dirstate.normallookup(fd)
- if move:
- repo.dirstate.forget(f)
- elif m == "d": # directory rename
- f2, fd, flag = a[2:]
- if not f2 and f not in repo.dirstate:
- # untracked file moved
- continue
- if branchmerge:
- repo.dirstate.add(fd)
- if f:
- repo.dirstate.remove(f)
- repo.dirstate.copy(f, fd)
- if f2:
- repo.dirstate.copy(f2, fd)
- else:
- repo.dirstate.normal(fd)
- if f:
- repo.dirstate.forget(f)
-
-def update(repo, node, branchmerge, force, partial):
- """
- Perform a merge between the working directory and the given node
-
- branchmerge = whether to merge between branches
- force = whether to force branch merging or file overwriting
- partial = a function to filter file lists (dirstate not updated)
- """
-
- wlock = repo.wlock()
- try:
- wc = repo[None]
- if node is None:
- # tip of current branch
- try:
- node = repo.branchtags()[wc.branch()]
- except KeyError:
- if wc.branch() == "default": # no default branch!
- node = repo.lookup("tip") # update to tip
- else:
- raise util.Abort(_("branch %s not found") % wc.branch())
- overwrite = force and not branchmerge
- pl = wc.parents()
- p1, p2 = pl[0], repo[node]
- pa = p1.ancestor(p2)
- fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
- fastforward = False
-
- ### check phase
- if not overwrite and len(pl) > 1:
- raise util.Abort(_("outstanding uncommitted merges"))
- if branchmerge:
- if pa == p2:
- raise util.Abort(_("can't merge with ancestor"))
- elif pa == p1:
- if p1.branch() != p2.branch():
- fastforward = True
- else:
- raise util.Abort(_("nothing to merge (use 'hg update'"
- " or check 'hg heads')"))
- if not force and (wc.files() or wc.deleted()):
- raise util.Abort(_("outstanding uncommitted changes "
- "(use 'hg status' to list changes)"))
- elif not overwrite:
- if pa == p1 or pa == p2: # linear
- pass # all good
- elif p1.branch() == p2.branch():
- if wc.files() or wc.deleted():
- raise util.Abort(_("crosses branches (use 'hg merge' or "
- "'hg update -C' to discard changes)"))
- raise util.Abort(_("crosses branches (use 'hg merge' "
- "or 'hg update -C')"))
- elif wc.files() or wc.deleted():
- raise util.Abort(_("crosses named branches (use "
- "'hg update -C' to discard changes)"))
- else:
- # Allow jumping branches if there are no changes
- overwrite = True
-
- ### calculate phase
- action = []
- if not force:
- _checkunknown(wc, p2)
- if not util.checkcase(repo.path):
- _checkcollision(p2)
- action += _forgetremoved(wc, p2, branchmerge)
- action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
-
- ### apply phase
- if not branchmerge: # just jump to the new rev
- fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
- if not partial:
- repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
-
- stats = applyupdates(repo, action, wc, p2)
-
- if not partial:
- recordupdates(repo, action, branchmerge)
- repo.dirstate.setparents(fp1, fp2)
- if not branchmerge and not fastforward:
- repo.dirstate.setbranch(p2.branch())
- repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
-
- return stats
- finally:
- wlock.release()
diff --git a/sys/src/cmd/hg/mercurial/minirst.py b/sys/src/cmd/hg/mercurial/minirst.py
deleted file mode 100644
index 201c21d99..000000000
--- a/sys/src/cmd/hg/mercurial/minirst.py
+++ /dev/null
@@ -1,343 +0,0 @@
-# minirst.py - minimal reStructuredText parser
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-"""simplified reStructuredText parser.
-
-This parser knows just enough about reStructuredText to parse the
-Mercurial docstrings.
-
-It cheats in a major way: nested blocks are not really nested. They
-are just indented blocks that look like they are nested. This relies
-on the user to keep the right indentation for the blocks.
-
-It only supports a small subset of reStructuredText:
-
-- paragraphs
-
-- definition lists (must use ' ' to indent definitions)
-
-- lists (items must start with '-')
-
-- field lists (colons cannot be escaped)
-
-- literal blocks
-
-- option lists (supports only long options without arguments)
-
-- inline markup is not recognized at all.
-"""
-
-import re, sys, textwrap
-
-
-def findblocks(text):
- """Find continuous blocks of lines in text.
-
- Returns a list of dictionaries representing the blocks. Each block
- has an 'indent' field and a 'lines' field.
- """
- blocks = [[]]
- lines = text.splitlines()
- for line in lines:
- if line.strip():
- blocks[-1].append(line)
- elif blocks[-1]:
- blocks.append([])
- if not blocks[-1]:
- del blocks[-1]
-
- for i, block in enumerate(blocks):
- indent = min((len(l) - len(l.lstrip())) for l in block)
- blocks[i] = dict(indent=indent, lines=[l[indent:] for l in block])
- return blocks
-
-
-def findliteralblocks(blocks):
- """Finds literal blocks and adds a 'type' field to the blocks.
-
- Literal blocks are given the type 'literal', all other blocks are
- given type the 'paragraph'.
- """
- i = 0
- while i < len(blocks):
- # Searching for a block that looks like this:
- #
- # +------------------------------+
- # | paragraph |
- # | (ends with "::") |
- # +------------------------------+
- # +---------------------------+
- # | indented literal block |
- # +---------------------------+
- blocks[i]['type'] = 'paragraph'
- if blocks[i]['lines'][-1].endswith('::') and i+1 < len(blocks):
- indent = blocks[i]['indent']
- adjustment = blocks[i+1]['indent'] - indent
-
- if blocks[i]['lines'] == ['::']:
- # Expanded form: remove block
- del blocks[i]
- i -= 1
- elif blocks[i]['lines'][-1].endswith(' ::'):
- # Partially minimized form: remove space and both
- # colons.
- blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-3]
- else:
- # Fully minimized form: remove just one colon.
- blocks[i]['lines'][-1] = blocks[i]['lines'][-1][:-1]
-
- # List items are formatted with a hanging indent. We must
- # correct for this here while we still have the original
- # information on the indentation of the subsequent literal
- # blocks available.
- if blocks[i]['lines'][0].startswith('- '):
- indent += 2
- adjustment -= 2
-
- # Mark the following indented blocks.
- while i+1 < len(blocks) and blocks[i+1]['indent'] > indent:
- blocks[i+1]['type'] = 'literal'
- blocks[i+1]['indent'] -= adjustment
- i += 1
- i += 1
- return blocks
-
-
-def findsections(blocks):
- """Finds sections.
-
- The blocks must have a 'type' field, i.e., they should have been
- run through findliteralblocks first.
- """
- for block in blocks:
- # Searching for a block that looks like this:
- #
- # +------------------------------+
- # | Section title |
- # | ------------- |
- # +------------------------------+
- if (block['type'] == 'paragraph' and
- len(block['lines']) == 2 and
- block['lines'][1] == '-' * len(block['lines'][0])):
- block['type'] = 'section'
- return blocks
-
-
-def findbulletlists(blocks):
- """Finds bullet lists.
-
- The blocks must have a 'type' field, i.e., they should have been
- run through findliteralblocks first.
- """
- i = 0
- while i < len(blocks):
- # Searching for a paragraph that looks like this:
- #
- # +------+-----------------------+
- # | "- " | list item |
- # +------| (body elements)+ |
- # +-----------------------+
- if (blocks[i]['type'] == 'paragraph' and
- blocks[i]['lines'][0].startswith('- ')):
- items = []
- for line in blocks[i]['lines']:
- if line.startswith('- '):
- items.append(dict(type='bullet', lines=[],
- indent=blocks[i]['indent']))
- line = line[2:]
- items[-1]['lines'].append(line)
- blocks[i:i+1] = items
- i += len(items) - 1
- i += 1
- return blocks
-
-
-_optionre = re.compile(r'^(--[a-z-]+)((?:[ =][a-zA-Z][\w-]*)? +)(.*)$')
-def findoptionlists(blocks):
- """Finds option lists.
-
- The blocks must have a 'type' field, i.e., they should have been
- run through findliteralblocks first.
- """
- i = 0
- while i < len(blocks):
- # Searching for a paragraph that looks like this:
- #
- # +----------------------------+-------------+
- # | "--" option " " | description |
- # +-------+--------------------+ |
- # | (body elements)+ |
- # +----------------------------------+
- if (blocks[i]['type'] == 'paragraph' and
- _optionre.match(blocks[i]['lines'][0])):
- options = []
- for line in blocks[i]['lines']:
- m = _optionre.match(line)
- if m:
- option, arg, rest = m.groups()
- width = len(option) + len(arg)
- options.append(dict(type='option', lines=[],
- indent=blocks[i]['indent'],
- width=width))
- options[-1]['lines'].append(line)
- blocks[i:i+1] = options
- i += len(options) - 1
- i += 1
- return blocks
-
-
-_fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):( +)(.*)')
-def findfieldlists(blocks):
- """Finds fields lists.
-
- The blocks must have a 'type' field, i.e., they should have been
- run through findliteralblocks first.
- """
- i = 0
- while i < len(blocks):
- # Searching for a paragraph that looks like this:
- #
- #
- # +--------------------+----------------------+
- # | ":" field name ":" | field body |
- # +-------+------------+ |
- # | (body elements)+ |
- # +-----------------------------------+
- if (blocks[i]['type'] == 'paragraph' and
- _fieldre.match(blocks[i]['lines'][0])):
- indent = blocks[i]['indent']
- fields = []
- for line in blocks[i]['lines']:
- m = _fieldre.match(line)
- if m:
- key, spaces, rest = m.groups()
- width = 2 + len(key) + len(spaces)
- fields.append(dict(type='field', lines=[],
- indent=indent, width=width))
- # Turn ":foo: bar" into "foo bar".
- line = '%s %s%s' % (key, spaces, rest)
- fields[-1]['lines'].append(line)
- blocks[i:i+1] = fields
- i += len(fields) - 1
- i += 1
- return blocks
-
-
-def finddefinitionlists(blocks):
- """Finds definition lists.
-
- The blocks must have a 'type' field, i.e., they should have been
- run through findliteralblocks first.
- """
- i = 0
- while i < len(blocks):
- # Searching for a paragraph that looks like this:
- #
- # +----------------------------+
- # | term |
- # +--+-------------------------+--+
- # | definition |
- # | (body elements)+ |
- # +----------------------------+
- if (blocks[i]['type'] == 'paragraph' and
- len(blocks[i]['lines']) > 1 and
- not blocks[i]['lines'][0].startswith(' ') and
- blocks[i]['lines'][1].startswith(' ')):
- definitions = []
- for line in blocks[i]['lines']:
- if not line.startswith(' '):
- definitions.append(dict(type='definition', lines=[],
- indent=blocks[i]['indent']))
- definitions[-1]['lines'].append(line)
- definitions[-1]['hang'] = len(line) - len(line.lstrip())
- blocks[i:i+1] = definitions
- i += len(definitions) - 1
- i += 1
- return blocks
-
-
-def addmargins(blocks):
- """Adds empty blocks for vertical spacing.
-
- This groups bullets, options, and definitions together with no vertical
- space between them, and adds an empty block between all other blocks.
- """
- i = 1
- while i < len(blocks):
- if (blocks[i]['type'] == blocks[i-1]['type'] and
- blocks[i]['type'] in ('bullet', 'option', 'field', 'definition')):
- i += 1
- else:
- blocks.insert(i, dict(lines=[''], indent=0, type='margin'))
- i += 2
- return blocks
-
-
-def formatblock(block, width):
- """Format a block according to width."""
- indent = ' ' * block['indent']
- if block['type'] == 'margin':
- return ''
- elif block['type'] == 'literal':
- indent += ' '
- return indent + ('\n' + indent).join(block['lines'])
- elif block['type'] == 'section':
- return indent + ('\n' + indent).join(block['lines'])
- elif block['type'] == 'definition':
- term = indent + block['lines'][0]
- defindent = indent + block['hang'] * ' '
- text = ' '.join(map(str.strip, block['lines'][1:]))
- return "%s\n%s" % (term, textwrap.fill(text, width=width,
- initial_indent=defindent,
- subsequent_indent=defindent))
- else:
- initindent = subindent = indent
- text = ' '.join(map(str.strip, block['lines']))
- if block['type'] == 'bullet':
- initindent = indent + '- '
- subindent = indent + ' '
- elif block['type'] in ('option', 'field'):
- subindent = indent + block['width'] * ' '
-
- return textwrap.fill(text, width=width,
- initial_indent=initindent,
- subsequent_indent=subindent)
-
-
-def format(text, width):
- """Parse and format the text according to width."""
- blocks = findblocks(text)
- blocks = findliteralblocks(blocks)
- blocks = findsections(blocks)
- blocks = findbulletlists(blocks)
- blocks = findoptionlists(blocks)
- blocks = findfieldlists(blocks)
- blocks = finddefinitionlists(blocks)
- blocks = addmargins(blocks)
- return '\n'.join(formatblock(b, width) for b in blocks)
-
-
-if __name__ == "__main__":
- from pprint import pprint
-
- def debug(func, blocks):
- blocks = func(blocks)
- print "*** after %s:" % func.__name__
- pprint(blocks)
- print
- return blocks
-
- text = open(sys.argv[1]).read()
- blocks = debug(findblocks, text)
- blocks = debug(findliteralblocks, blocks)
- blocks = debug(findsections, blocks)
- blocks = debug(findbulletlists, blocks)
- blocks = debug(findoptionlists, blocks)
- blocks = debug(findfieldlists, blocks)
- blocks = debug(finddefinitionlists, blocks)
- blocks = debug(addmargins, blocks)
- print '\n'.join(formatblock(b, 30) for b in blocks)
diff --git a/sys/src/cmd/hg/mercurial/mpatch.c b/sys/src/cmd/hg/mercurial/mpatch.c
deleted file mode 100644
index 86400d1a2..000000000
--- a/sys/src/cmd/hg/mercurial/mpatch.c
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- mpatch.c - efficient binary patching for Mercurial
-
- This implements a patch algorithm that's O(m + nlog n) where m is the
- size of the output and n is the number of patches.
-
- Given a list of binary patches, it unpacks each into a hunk list,
- then combines the hunk lists with a treewise recursion to form a
- single hunk list. This hunk list is then applied to the original
- text.
-
- The text (or binary) fragments are copied directly from their source
- Python objects into a preallocated output string to avoid the
- allocation of intermediate Python objects. Working memory is about 2x
- the total number of hunks.
-
- Copyright 2005, 2006 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.
-*/
-
-#include <Python.h>
-#include <stdlib.h>
-#include <string.h>
-
-/* Definitions to get compatibility with python 2.4 and earlier which
- does not have Py_ssize_t. See also PEP 353.
- Note: msvc (8 or earlier) does not have ssize_t, so we use Py_ssize_t.
-*/
-#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
-typedef int Py_ssize_t;
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
-#endif
-
-#ifdef _WIN32
-# ifdef _MSC_VER
-/* msvc 6.0 has problems */
-# define inline __inline
-typedef unsigned long uint32_t;
-# else
-# include <stdint.h>
-# endif
-static uint32_t ntohl(uint32_t x)
-{
- return ((x & 0x000000ffUL) << 24) |
- ((x & 0x0000ff00UL) << 8) |
- ((x & 0x00ff0000UL) >> 8) |
- ((x & 0xff000000UL) >> 24);
-}
-#else
-/* not windows */
-# include <sys/types.h>
-# if defined __BEOS__ && !defined __HAIKU__
-# include <ByteOrder.h>
-# else
-# include <arpa/inet.h>
-# endif
-# include <inttypes.h>
-#endif
-
-static char mpatch_doc[] = "Efficient binary patching.";
-static PyObject *mpatch_Error;
-
-struct frag {
- int start, end, len;
- const char *data;
-};
-
-struct flist {
- struct frag *base, *head, *tail;
-};
-
-static struct flist *lalloc(int size)
-{
- struct flist *a = NULL;
-
- if (size < 1)
- size = 1;
-
- a = (struct flist *)malloc(sizeof(struct flist));
- if (a) {
- a->base = (struct frag *)malloc(sizeof(struct frag) * size);
- if (a->base) {
- a->head = a->tail = a->base;
- return a;
- }
- free(a);
- a = NULL;
- }
- if (!PyErr_Occurred())
- PyErr_NoMemory();
- return NULL;
-}
-
-static void lfree(struct flist *a)
-{
- if (a) {
- free(a->base);
- free(a);
- }
-}
-
-static int lsize(struct flist *a)
-{
- return a->tail - a->head;
-}
-
-/* move hunks in source that are less cut to dest, compensating
- for changes in offset. the last hunk may be split if necessary.
-*/
-static int gather(struct flist *dest, struct flist *src, int cut, int offset)
-{
- struct frag *d = dest->tail, *s = src->head;
- int postend, c, l;
-
- while (s != src->tail) {
- if (s->start + offset >= cut)
- break; /* we've gone far enough */
-
- postend = offset + s->start + s->len;
- if (postend <= cut) {
- /* save this hunk */
- offset += s->start + s->len - s->end;
- *d++ = *s++;
- }
- else {
- /* break up this hunk */
- c = cut - offset;
- if (s->end < c)
- c = s->end;
- l = cut - offset - s->start;
- if (s->len < l)
- l = s->len;
-
- offset += s->start + l - c;
-
- d->start = s->start;
- d->end = c;
- d->len = l;
- d->data = s->data;
- d++;
- s->start = c;
- s->len = s->len - l;
- s->data = s->data + l;
-
- break;
- }
- }
-
- dest->tail = d;
- src->head = s;
- return offset;
-}
-
-/* like gather, but with no output list */
-static int discard(struct flist *src, int cut, int offset)
-{
- struct frag *s = src->head;
- int postend, c, l;
-
- while (s != src->tail) {
- if (s->start + offset >= cut)
- break;
-
- postend = offset + s->start + s->len;
- if (postend <= cut) {
- offset += s->start + s->len - s->end;
- s++;
- }
- else {
- c = cut - offset;
- if (s->end < c)
- c = s->end;
- l = cut - offset - s->start;
- if (s->len < l)
- l = s->len;
-
- offset += s->start + l - c;
- s->start = c;
- s->len = s->len - l;
- s->data = s->data + l;
-
- break;
- }
- }
-
- src->head = s;
- return offset;
-}
-
-/* combine hunk lists a and b, while adjusting b for offset changes in a/
- this deletes a and b and returns the resultant list. */
-static struct flist *combine(struct flist *a, struct flist *b)
-{
- struct flist *c = NULL;
- struct frag *bh, *ct;
- int offset = 0, post;
-
- if (a && b)
- c = lalloc((lsize(a) + lsize(b)) * 2);
-
- if (c) {
-
- for (bh = b->head; bh != b->tail; bh++) {
- /* save old hunks */
- offset = gather(c, a, bh->start, offset);
-
- /* discard replaced hunks */
- post = discard(a, bh->end, offset);
-
- /* insert new hunk */
- ct = c->tail;
- ct->start = bh->start - offset;
- ct->end = bh->end - post;
- ct->len = bh->len;
- ct->data = bh->data;
- c->tail++;
- offset = post;
- }
-
- /* hold on to tail from a */
- memcpy(c->tail, a->head, sizeof(struct frag) * lsize(a));
- c->tail += lsize(a);
- }
-
- lfree(a);
- lfree(b);
- return c;
-}
-
-/* decode a binary patch into a hunk list */
-static struct flist *decode(const char *bin, int len)
-{
- struct flist *l;
- struct frag *lt;
- const char *data = bin + 12, *end = bin + len;
- char decode[12]; /* for dealing with alignment issues */
-
- /* assume worst case size, we won't have many of these lists */
- l = lalloc(len / 12 + 1);
- if (!l)
- return NULL;
-
- lt = l->tail;
-
- while (data <= end) {
- memcpy(decode, bin, 12);
- lt->start = ntohl(*(uint32_t *)decode);
- lt->end = ntohl(*(uint32_t *)(decode + 4));
- lt->len = ntohl(*(uint32_t *)(decode + 8));
- if (lt->start > lt->end || lt->len < 0)
- break; /* sanity check */
- bin = data + lt->len;
- if (bin < data)
- break; /* big data + big (bogus) len can wrap around */
- lt->data = data;
- data = bin + 12;
- lt++;
- }
-
- if (bin != end) {
- if (!PyErr_Occurred())
- PyErr_SetString(mpatch_Error, "patch cannot be decoded");
- lfree(l);
- return NULL;
- }
-
- l->tail = lt;
- return l;
-}
-
-/* calculate the size of resultant text */
-static int calcsize(int len, struct flist *l)
-{
- int outlen = 0, last = 0;
- struct frag *f = l->head;
-
- while (f != l->tail) {
- if (f->start < last || f->end > len) {
- if (!PyErr_Occurred())
- PyErr_SetString(mpatch_Error,
- "invalid patch");
- return -1;
- }
- outlen += f->start - last;
- last = f->end;
- outlen += f->len;
- f++;
- }
-
- outlen += len - last;
- return outlen;
-}
-
-static int apply(char *buf, const char *orig, int len, struct flist *l)
-{
- struct frag *f = l->head;
- int last = 0;
- char *p = buf;
-
- while (f != l->tail) {
- if (f->start < last || f->end > len) {
- if (!PyErr_Occurred())
- PyErr_SetString(mpatch_Error,
- "invalid patch");
- return 0;
- }
- memcpy(p, orig + last, f->start - last);
- p += f->start - last;
- memcpy(p, f->data, f->len);
- last = f->end;
- p += f->len;
- f++;
- }
- memcpy(p, orig + last, len - last);
- return 1;
-}
-
-/* recursively generate a patch of all bins between start and end */
-static struct flist *fold(PyObject *bins, int start, int end)
-{
- int len;
- Py_ssize_t blen;
- const char *buffer;
-
- if (start + 1 == end) {
- /* trivial case, output a decoded list */
- PyObject *tmp = PyList_GetItem(bins, start);
- if (!tmp)
- return NULL;
- if (PyObject_AsCharBuffer(tmp, &buffer, &blen))
- return NULL;
- return decode(buffer, blen);
- }
-
- /* divide and conquer, memory management is elsewhere */
- len = (end - start) / 2;
- return combine(fold(bins, start, start + len),
- fold(bins, start + len, end));
-}
-
-static PyObject *
-patches(PyObject *self, PyObject *args)
-{
- PyObject *text, *bins, *result;
- struct flist *patch;
- const char *in;
- char *out;
- int len, outlen;
- Py_ssize_t inlen;
-
- if (!PyArg_ParseTuple(args, "OO:mpatch", &text, &bins))
- return NULL;
-
- len = PyList_Size(bins);
- if (!len) {
- /* nothing to do */
- Py_INCREF(text);
- return text;
- }
-
- if (PyObject_AsCharBuffer(text, &in, &inlen))
- return NULL;
-
- patch = fold(bins, 0, len);
- if (!patch)
- return NULL;
-
- outlen = calcsize(inlen, patch);
- if (outlen < 0) {
- result = NULL;
- goto cleanup;
- }
- result = PyString_FromStringAndSize(NULL, outlen);
- if (!result) {
- result = NULL;
- goto cleanup;
- }
- out = PyString_AsString(result);
- if (!apply(out, in, inlen, patch)) {
- Py_DECREF(result);
- result = NULL;
- }
-cleanup:
- lfree(patch);
- return result;
-}
-
-/* calculate size of a patched file directly */
-static PyObject *
-patchedsize(PyObject *self, PyObject *args)
-{
- long orig, start, end, len, outlen = 0, last = 0;
- int patchlen;
- char *bin, *binend, *data;
- char decode[12]; /* for dealing with alignment issues */
-
- if (!PyArg_ParseTuple(args, "ls#", &orig, &bin, &patchlen))
- return NULL;
-
- binend = bin + patchlen;
- data = bin + 12;
-
- while (data <= binend) {
- memcpy(decode, bin, 12);
- start = ntohl(*(uint32_t *)decode);
- end = ntohl(*(uint32_t *)(decode + 4));
- len = ntohl(*(uint32_t *)(decode + 8));
- if (start > end)
- break; /* sanity check */
- bin = data + len;
- if (bin < data)
- break; /* big data + big (bogus) len can wrap around */
- data = bin + 12;
- outlen += start - last;
- last = end;
- outlen += len;
- }
-
- if (bin != binend) {
- if (!PyErr_Occurred())
- PyErr_SetString(mpatch_Error, "patch cannot be decoded");
- return NULL;
- }
-
- outlen += orig - last;
- return Py_BuildValue("l", outlen);
-}
-
-static PyMethodDef methods[] = {
- {"patches", patches, METH_VARARGS, "apply a series of patches\n"},
- {"patchedsize", patchedsize, METH_VARARGS, "calculed patched size\n"},
- {NULL, NULL}
-};
-
-PyMODINIT_FUNC
-initmpatch(void)
-{
- Py_InitModule3("mpatch", methods, mpatch_doc);
- mpatch_Error = PyErr_NewException("mpatch.mpatchError", NULL, NULL);
-}
-
diff --git a/sys/src/cmd/hg/mercurial/node.py b/sys/src/cmd/hg/mercurial/node.py
deleted file mode 100644
index 2a3be39c1..000000000
--- a/sys/src/cmd/hg/mercurial/node.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# node.py - basic nodeid manipulation for mercurial
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import binascii
-
-nullrev = -1
-nullid = "\0" * 20
-
-# This ugly style has a noticeable effect in manifest parsing
-hex = binascii.hexlify
-bin = binascii.unhexlify
-
-def short(node):
- return hex(node[:6])
diff --git a/sys/src/cmd/hg/mercurial/osutil.c b/sys/src/cmd/hg/mercurial/osutil.c
deleted file mode 100644
index a9874d0c9..000000000
--- a/sys/src/cmd/hg/mercurial/osutil.c
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- osutil.c - native operating system services
-
- Copyright 2007 Matt Mackall and others
-
- This software may be used and distributed according to the terms of
- the GNU General Public License, incorporated herein by reference.
-*/
-
-#define _ATFILE_SOURCE
-#include <Python.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <string.h>
-
-#ifdef _WIN32
-# include <windows.h>
-# include <io.h>
-#else
-# include <dirent.h>
-# include <sys/stat.h>
-# include <sys/types.h>
-# include <unistd.h>
-#endif
-
-// some platforms lack the PATH_MAX definition (eg. GNU/Hurd)
-#ifndef PATH_MAX
-#define PATH_MAX 4096
-#endif
-
-#ifdef _WIN32
-/*
-stat struct compatible with hg expectations
-Mercurial only uses st_mode, st_size and st_mtime
-the rest is kept to minimize changes between implementations
-*/
-struct hg_stat {
- int st_dev;
- int st_mode;
- int st_nlink;
- __int64 st_size;
- int st_mtime;
- int st_ctime;
-};
-struct listdir_stat {
- PyObject_HEAD
- struct hg_stat st;
-};
-#else
-struct listdir_stat {
- PyObject_HEAD
- struct stat st;
-};
-#endif
-
-#define listdir_slot(name) \
- static PyObject *listdir_stat_##name(PyObject *self, void *x) \
- { \
- return PyInt_FromLong(((struct listdir_stat *)self)->st.name); \
- }
-
-listdir_slot(st_dev)
-listdir_slot(st_mode)
-listdir_slot(st_nlink)
-#ifdef _WIN32
-static PyObject *listdir_stat_st_size(PyObject *self, void *x)
-{
- return PyLong_FromLongLong(
- (PY_LONG_LONG)((struct listdir_stat *)self)->st.st_size);
-}
-#else
-listdir_slot(st_size)
-#endif
-listdir_slot(st_mtime)
-listdir_slot(st_ctime)
-
-static struct PyGetSetDef listdir_stat_getsets[] = {
- {"st_dev", listdir_stat_st_dev, 0, 0, 0},
- {"st_mode", listdir_stat_st_mode, 0, 0, 0},
- {"st_nlink", listdir_stat_st_nlink, 0, 0, 0},
- {"st_size", listdir_stat_st_size, 0, 0, 0},
- {"st_mtime", listdir_stat_st_mtime, 0, 0, 0},
- {"st_ctime", listdir_stat_st_ctime, 0, 0, 0},
- {0, 0, 0, 0, 0}
-};
-
-static PyObject *listdir_stat_new(PyTypeObject *t, PyObject *a, PyObject *k)
-{
- return t->tp_alloc(t, 0);
-}
-
-static void listdir_stat_dealloc(PyObject *o)
-{
- o->ob_type->tp_free(o);
-}
-
-static PyTypeObject listdir_stat_type = {
- PyObject_HEAD_INIT(NULL)
- 0, /*ob_size*/
- "osutil.stat", /*tp_name*/
- sizeof(struct listdir_stat), /*tp_basicsize*/
- 0, /*tp_itemsize*/
- (destructor)listdir_stat_dealloc, /*tp_dealloc*/
- 0, /*tp_print*/
- 0, /*tp_getattr*/
- 0, /*tp_setattr*/
- 0, /*tp_compare*/
- 0, /*tp_repr*/
- 0, /*tp_as_number*/
- 0, /*tp_as_sequence*/
- 0, /*tp_as_mapping*/
- 0, /*tp_hash */
- 0, /*tp_call*/
- 0, /*tp_str*/
- 0, /*tp_getattro*/
- 0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
- "stat objects", /* tp_doc */
- 0, /* tp_traverse */
- 0, /* tp_clear */
- 0, /* tp_richcompare */
- 0, /* tp_weaklistoffset */
- 0, /* tp_iter */
- 0, /* tp_iternext */
- 0, /* tp_methods */
- 0, /* tp_members */
- listdir_stat_getsets, /* tp_getset */
- 0, /* tp_base */
- 0, /* tp_dict */
- 0, /* tp_descr_get */
- 0, /* tp_descr_set */
- 0, /* tp_dictoffset */
- 0, /* tp_init */
- 0, /* tp_alloc */
- listdir_stat_new, /* tp_new */
-};
-
-#ifdef _WIN32
-
-static int to_python_time(const FILETIME *tm)
-{
- /* number of seconds between epoch and January 1 1601 */
- const __int64 a0 = (__int64)134774L * (__int64)24L * (__int64)3600L;
- /* conversion factor from 100ns to 1s */
- const __int64 a1 = 10000000;
- /* explicit (int) cast to suspend compiler warnings */
- return (int)((((__int64)tm->dwHighDateTime << 32)
- + tm->dwLowDateTime) / a1 - a0);
-}
-
-static PyObject *make_item(const WIN32_FIND_DATAA *fd, int wantstat)
-{
- PyObject *py_st;
- struct hg_stat *stp;
-
- int kind = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- ? _S_IFDIR : _S_IFREG;
-
- if (!wantstat)
- return Py_BuildValue("si", fd->cFileName, kind);
-
- py_st = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
- if (!py_st)
- return NULL;
-
- stp = &((struct listdir_stat *)py_st)->st;
- /*
- use kind as st_mode
- rwx bits on Win32 are meaningless
- and Hg does not use them anyway
- */
- stp->st_mode = kind;
- stp->st_mtime = to_python_time(&fd->ftLastWriteTime);
- stp->st_ctime = to_python_time(&fd->ftCreationTime);
- if (kind == _S_IFREG)
- stp->st_size = ((__int64)fd->nFileSizeHigh << 32)
- + fd->nFileSizeLow;
- return Py_BuildValue("siN", fd->cFileName,
- kind, py_st);
-}
-
-static PyObject *_listdir(char *path, int plen, int wantstat, char *skip)
-{
- PyObject *rval = NULL; /* initialize - return value */
- PyObject *list;
- HANDLE fh;
- WIN32_FIND_DATAA fd;
- char *pattern;
-
- /* build the path + \* pattern string */
- pattern = malloc(plen+3); /* path + \* + \0 */
- if (!pattern) {
- PyErr_NoMemory();
- goto error_nomem;
- }
- strcpy(pattern, path);
-
- if (plen > 0) {
- char c = path[plen-1];
- if (c != ':' && c != '/' && c != '\\')
- pattern[plen++] = '\\';
- }
- strcpy(pattern + plen, "*");
-
- fh = FindFirstFileA(pattern, &fd);
- if (fh == INVALID_HANDLE_VALUE) {
- PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
- goto error_file;
- }
-
- list = PyList_New(0);
- if (!list)
- goto error_list;
-
- do {
- PyObject *item;
-
- if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
- if (!strcmp(fd.cFileName, ".")
- || !strcmp(fd.cFileName, ".."))
- continue;
-
- if (skip && !strcmp(fd.cFileName, skip)) {
- rval = PyList_New(0);
- goto error;
- }
- }
-
- item = make_item(&fd, wantstat);
- if (!item)
- goto error;
-
- if (PyList_Append(list, item)) {
- Py_XDECREF(item);
- goto error;
- }
-
- Py_XDECREF(item);
- } while (FindNextFileA(fh, &fd));
-
- if (GetLastError() != ERROR_NO_MORE_FILES) {
- PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
- goto error;
- }
-
- rval = list;
- Py_XINCREF(rval);
-error:
- Py_XDECREF(list);
-error_list:
- FindClose(fh);
-error_file:
- free(pattern);
-error_nomem:
- return rval;
-}
-
-#else
-
-int entkind(struct dirent *ent)
-{
-#ifdef DT_REG
- switch (ent->d_type) {
- case DT_REG: return S_IFREG;
- case DT_DIR: return S_IFDIR;
- case DT_LNK: return S_IFLNK;
- case DT_BLK: return S_IFBLK;
- case DT_CHR: return S_IFCHR;
- case DT_FIFO: return S_IFIFO;
- case DT_SOCK: return S_IFSOCK;
- }
-#endif
- return -1;
-}
-
-static PyObject *_listdir(char *path, int pathlen, int keepstat, char *skip)
-{
- PyObject *list, *elem, *stat, *ret = NULL;
- char fullpath[PATH_MAX + 10];
- int kind, err;
- struct stat st;
- struct dirent *ent;
- DIR *dir;
-#ifdef AT_SYMLINK_NOFOLLOW
- int dfd = -1;
-#endif
-
- if (pathlen >= PATH_MAX) {
- PyErr_SetString(PyExc_ValueError, "path too long");
- goto error_value;
- }
- strncpy(fullpath, path, PATH_MAX);
- fullpath[pathlen] = '/';
-
-#ifdef AT_SYMLINK_NOFOLLOW
- dfd = open(path, O_RDONLY);
- if (dfd == -1) {
- PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
- goto error_value;
- }
- dir = fdopendir(dfd);
-#else
- dir = opendir(path);
-#endif
- if (!dir) {
- PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
- goto error_dir;
- }
-
- list = PyList_New(0);
- if (!list)
- goto error_list;
-
- while ((ent = readdir(dir))) {
- if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
- continue;
-
- kind = entkind(ent);
- if (kind == -1 || keepstat) {
-#ifdef AT_SYMLINK_NOFOLLOW
- err = fstatat(dfd, ent->d_name, &st,
- AT_SYMLINK_NOFOLLOW);
-#else
- strncpy(fullpath + pathlen + 1, ent->d_name,
- PATH_MAX - pathlen);
- fullpath[PATH_MAX] = 0;
- err = lstat(fullpath, &st);
-#endif
- if (err == -1) {
- strncpy(fullpath + pathlen + 1, ent->d_name,
- PATH_MAX - pathlen);
- fullpath[PATH_MAX] = 0;
- PyErr_SetFromErrnoWithFilename(PyExc_OSError,
- fullpath);
- goto error;
- }
- kind = st.st_mode & S_IFMT;
- }
-
- /* quit early? */
- if (skip && kind == S_IFDIR && !strcmp(ent->d_name, skip)) {
- ret = PyList_New(0);
- goto error;
- }
-
- if (keepstat) {
- stat = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
- if (!stat)
- goto error;
- memcpy(&((struct listdir_stat *)stat)->st, &st, sizeof(st));
- elem = Py_BuildValue("siN", ent->d_name, kind, stat);
- } else
- elem = Py_BuildValue("si", ent->d_name, kind);
- if (!elem)
- goto error;
-
- PyList_Append(list, elem);
- Py_DECREF(elem);
- }
-
- ret = list;
- Py_INCREF(ret);
-
-error:
- Py_DECREF(list);
-error_list:
- closedir(dir);
-error_dir:
-#ifdef AT_SYMLINK_NOFOLLOW
- close(dfd);
-#endif
-error_value:
- return ret;
-}
-
-#endif /* ndef _WIN32 */
-
-static PyObject *listdir(PyObject *self, PyObject *args, PyObject *kwargs)
-{
- PyObject *statobj = NULL; /* initialize - optional arg */
- PyObject *skipobj = NULL; /* initialize - optional arg */
- char *path, *skip = NULL;
- int wantstat, plen;
-
- static char *kwlist[] = {"path", "stat", "skip", NULL};
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|OO:listdir",
- kwlist, &path, &plen, &statobj, &skipobj))
- return NULL;
-
- wantstat = statobj && PyObject_IsTrue(statobj);
-
- if (skipobj && skipobj != Py_None) {
- skip = PyString_AsString(skipobj);
- if (!skip)
- return NULL;
- }
-
- return _listdir(path, plen, wantstat, skip);
-}
-
-#ifdef _WIN32
-static PyObject *posixfile(PyObject *self, PyObject *args, PyObject *kwds)
-{
- static char *kwlist[] = {"name", "mode", "buffering", NULL};
- PyObject *file_obj = NULL;
- char *name = NULL;
- char *mode = "rb";
- DWORD access = 0;
- DWORD creation;
- HANDLE handle;
- int fd, flags = 0;
- int bufsize = -1;
- char m0, m1, m2;
- char fpmode[4];
- int fppos = 0;
- int plus;
- FILE *fp;
-
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:posixfile", kwlist,
- Py_FileSystemDefaultEncoding,
- &name, &mode, &bufsize))
- return NULL;
-
- m0 = mode[0];
- m1 = m0 ? mode[1] : '\0';
- m2 = m1 ? mode[2] : '\0';
- plus = m1 == '+' || m2 == '+';
-
- fpmode[fppos++] = m0;
- if (m1 == 'b' || m2 == 'b') {
- flags = _O_BINARY;
- fpmode[fppos++] = 'b';
- }
- else
- flags = _O_TEXT;
- if (plus) {
- flags |= _O_RDWR;
- access = GENERIC_READ | GENERIC_WRITE;
- fpmode[fppos++] = '+';
- }
- fpmode[fppos++] = '\0';
-
- switch (m0) {
- case 'r':
- creation = OPEN_EXISTING;
- if (!plus) {
- flags |= _O_RDONLY;
- access = GENERIC_READ;
- }
- break;
- case 'w':
- creation = CREATE_ALWAYS;
- if (!plus) {
- access = GENERIC_WRITE;
- flags |= _O_WRONLY;
- }
- break;
- case 'a':
- creation = OPEN_ALWAYS;
- flags |= _O_APPEND;
- if (!plus) {
- flags |= _O_WRONLY;
- access = GENERIC_WRITE;
- }
- break;
- default:
- PyErr_Format(PyExc_ValueError,
- "mode string must begin with one of 'r', 'w', "
- "or 'a', not '%c'", m0);
- goto bail;
- }
-
- handle = CreateFile(name, access,
- FILE_SHARE_READ | FILE_SHARE_WRITE |
- FILE_SHARE_DELETE,
- NULL,
- creation,
- FILE_ATTRIBUTE_NORMAL,
- 0);
-
- if (handle == INVALID_HANDLE_VALUE) {
- PyErr_SetFromWindowsErrWithFilename(GetLastError(), name);
- goto bail;
- }
-
- fd = _open_osfhandle((intptr_t) handle, flags);
- if (fd == -1) {
- CloseHandle(handle);
- PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
- goto bail;
- }
-
- fp = _fdopen(fd, fpmode);
- if (fp == NULL) {
- _close(fd);
- PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
- goto bail;
- }
-
- file_obj = PyFile_FromFile(fp, name, mode, fclose);
- if (file_obj == NULL) {
- fclose(fp);
- goto bail;
- }
-
- PyFile_SetBufSize(file_obj, bufsize);
-bail:
- PyMem_Free(name);
- return file_obj;
-}
-#endif
-
-static char osutil_doc[] = "Native operating system services.";
-
-static PyMethodDef methods[] = {
- {"listdir", (PyCFunction)listdir, METH_VARARGS | METH_KEYWORDS,
- "list a directory\n"},
-#ifdef _WIN32
- {"posixfile", (PyCFunction)posixfile, METH_VARARGS | METH_KEYWORDS,
- "Open a file with POSIX-like semantics.\n"
-"On error, this function may raise either a WindowsError or an IOError."},
-#endif
- {NULL, NULL}
-};
-
-PyMODINIT_FUNC initosutil(void)
-{
- if (PyType_Ready(&listdir_stat_type) == -1)
- return;
-
- Py_InitModule3("osutil", methods, osutil_doc);
-}
diff --git a/sys/src/cmd/hg/mercurial/parsers.c b/sys/src/cmd/hg/mercurial/parsers.c
deleted file mode 100644
index 93c10c05d..000000000
--- a/sys/src/cmd/hg/mercurial/parsers.c
+++ /dev/null
@@ -1,435 +0,0 @@
-/*
- parsers.c - efficient content parsing
-
- Copyright 2008 Matt Mackall <mpm@selenic.com> and others
-
- This software may be used and distributed according to the terms of
- the GNU General Public License, incorporated herein by reference.
-*/
-
-#include <Python.h>
-#include <ctype.h>
-#include <string.h>
-
-static int hexdigit(char c)
-{
- if (c >= '0' && c <= '9')
- return c - '0';
- if (c >= 'a' && c <= 'f')
- return c - 'a' + 10;
- if (c >= 'A' && c <= 'F')
- return c - 'A' + 10;
-
- PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
- return 0;
-}
-
-/*
- * Turn a hex-encoded string into binary.
- */
-static PyObject *unhexlify(const char *str, int len)
-{
- PyObject *ret;
- const char *c;
- char *d;
-
- ret = PyString_FromStringAndSize(NULL, len / 2);
- if (!ret)
- return NULL;
-
- d = PyString_AS_STRING(ret);
- for (c = str; c < str + len;) {
- int hi = hexdigit(*c++);
- int lo = hexdigit(*c++);
- *d++ = (hi << 4) | lo;
- }
-
- return ret;
-}
-
-/*
- * This code assumes that a manifest is stitched together with newline
- * ('\n') characters.
- */
-static PyObject *parse_manifest(PyObject *self, PyObject *args)
-{
- PyObject *mfdict, *fdict;
- char *str, *cur, *start, *zero;
- int len;
-
- if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
- &PyDict_Type, &mfdict,
- &PyDict_Type, &fdict,
- &str, &len))
- goto quit;
-
- for (start = cur = str, zero = NULL; cur < str + len; cur++) {
- PyObject *file = NULL, *node = NULL;
- PyObject *flags = NULL;
- int nlen;
-
- if (!*cur) {
- zero = cur;
- continue;
- }
- else if (*cur != '\n')
- continue;
-
- if (!zero) {
- PyErr_SetString(PyExc_ValueError,
- "manifest entry has no separator");
- goto quit;
- }
-
- file = PyString_FromStringAndSize(start, zero - start);
- if (!file)
- goto bail;
-
- nlen = cur - zero - 1;
-
- node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
- if (!node)
- goto bail;
-
- if (nlen > 40) {
- PyObject *flags;
-
- flags = PyString_FromStringAndSize(zero + 41,
- nlen - 40);
- if (!flags)
- goto bail;
-
- if (PyDict_SetItem(fdict, file, flags) == -1)
- goto bail;
- }
-
- if (PyDict_SetItem(mfdict, file, node) == -1)
- goto bail;
-
- start = cur + 1;
- zero = NULL;
-
- Py_XDECREF(flags);
- Py_XDECREF(node);
- Py_XDECREF(file);
- continue;
- bail:
- Py_XDECREF(flags);
- Py_XDECREF(node);
- Py_XDECREF(file);
- goto quit;
- }
-
- if (len > 0 && *(cur - 1) != '\n') {
- PyErr_SetString(PyExc_ValueError,
- "manifest contains trailing garbage");
- goto quit;
- }
-
- Py_INCREF(Py_None);
- return Py_None;
-quit:
- return NULL;
-}
-
-#ifdef _WIN32
-# ifdef _MSC_VER
-/* msvc 6.0 has problems */
-# define inline __inline
-typedef unsigned long uint32_t;
-typedef unsigned __int64 uint64_t;
-# else
-# include <stdint.h>
-# endif
-static uint32_t ntohl(uint32_t x)
-{
- return ((x & 0x000000ffUL) << 24) |
- ((x & 0x0000ff00UL) << 8) |
- ((x & 0x00ff0000UL) >> 8) |
- ((x & 0xff000000UL) >> 24);
-}
-#else
-/* not windows */
-# include <sys/types.h>
-# if defined __BEOS__ && !defined __HAIKU__
-# include <ByteOrder.h>
-# else
-# include <arpa/inet.h>
-# endif
-# include <inttypes.h>
-#endif
-
-static PyObject *parse_dirstate(PyObject *self, PyObject *args)
-{
- PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
- PyObject *fname = NULL, *cname = NULL, *entry = NULL;
- char *str, *cur, *end, *cpos;
- int state, mode, size, mtime;
- unsigned int flen;
- int len;
- char decode[16]; /* for alignment */
-
- if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
- &PyDict_Type, &dmap,
- &PyDict_Type, &cmap,
- &str, &len))
- goto quit;
-
- /* read parents */
- if (len < 40)
- goto quit;
-
- parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
- if (!parents)
- goto quit;
-
- /* read filenames */
- cur = str + 40;
- end = str + len;
-
- while (cur < end - 17) {
- /* unpack header */
- state = *cur;
- memcpy(decode, cur + 1, 16);
- mode = ntohl(*(uint32_t *)(decode));
- size = ntohl(*(uint32_t *)(decode + 4));
- mtime = ntohl(*(uint32_t *)(decode + 8));
- flen = ntohl(*(uint32_t *)(decode + 12));
- cur += 17;
- if (flen > end - cur) {
- PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
- goto quit;
- }
-
- entry = Py_BuildValue("ciii", state, mode, size, mtime);
- if (!entry)
- goto quit;
- PyObject_GC_UnTrack(entry); /* don't waste time with this */
-
- cpos = memchr(cur, 0, flen);
- if (cpos) {
- fname = PyString_FromStringAndSize(cur, cpos - cur);
- cname = PyString_FromStringAndSize(cpos + 1,
- flen - (cpos - cur) - 1);
- if (!fname || !cname ||
- PyDict_SetItem(cmap, fname, cname) == -1 ||
- PyDict_SetItem(dmap, fname, entry) == -1)
- goto quit;
- Py_DECREF(cname);
- } else {
- fname = PyString_FromStringAndSize(cur, flen);
- if (!fname ||
- PyDict_SetItem(dmap, fname, entry) == -1)
- goto quit;
- }
- cur += flen;
- Py_DECREF(fname);
- Py_DECREF(entry);
- fname = cname = entry = NULL;
- }
-
- ret = parents;
- Py_INCREF(ret);
-quit:
- Py_XDECREF(fname);
- Py_XDECREF(cname);
- Py_XDECREF(entry);
- Py_XDECREF(parents);
- return ret;
-}
-
-const char nullid[20];
-const int nullrev = -1;
-
-/* create an index tuple, insert into the nodemap */
-static PyObject * _build_idx_entry(PyObject *nodemap, int n, uint64_t offset_flags,
- int comp_len, int uncomp_len, int base_rev,
- int link_rev, int parent_1, int parent_2,
- const char *c_node_id)
-{
- int err;
- PyObject *entry, *node_id, *n_obj;
-
- node_id = PyString_FromStringAndSize(c_node_id, 20);
- n_obj = PyInt_FromLong(n);
- if (!node_id || !n_obj)
- err = -1;
- else
- err = PyDict_SetItem(nodemap, node_id, n_obj);
-
- Py_XDECREF(n_obj);
- if (err)
- goto error_dealloc;
-
- entry = Py_BuildValue("LiiiiiiN", offset_flags, comp_len,
- uncomp_len, base_rev, link_rev,
- parent_1, parent_2, node_id);
- if (!entry)
- goto error_dealloc;
- PyObject_GC_UnTrack(entry); /* don't waste time with this */
-
- return entry;
-
-error_dealloc:
- Py_XDECREF(node_id);
- return NULL;
-}
-
-/* RevlogNG format (all in big endian, data may be inlined):
- * 6 bytes: offset
- * 2 bytes: flags
- * 4 bytes: compressed length
- * 4 bytes: uncompressed length
- * 4 bytes: base revision
- * 4 bytes: link revision
- * 4 bytes: parent 1 revision
- * 4 bytes: parent 2 revision
- * 32 bytes: nodeid (only 20 bytes used)
- */
-static int _parse_index_ng (const char *data, int size, int inlined,
- PyObject *index, PyObject *nodemap)
-{
- PyObject *entry;
- int n = 0, err;
- uint64_t offset_flags;
- int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
- const char *c_node_id;
- const char *end = data + size;
- char decode[64]; /* to enforce alignment with inline data */
-
- while (data < end) {
- unsigned int step;
-
- memcpy(decode, data, 64);
- offset_flags = ntohl(*((uint32_t *) (decode + 4)));
- if (n == 0) /* mask out version number for the first entry */
- offset_flags &= 0xFFFF;
- else {
- uint32_t offset_high = ntohl(*((uint32_t *) decode));
- offset_flags |= ((uint64_t) offset_high) << 32;
- }
-
- comp_len = ntohl(*((uint32_t *) (decode + 8)));
- uncomp_len = ntohl(*((uint32_t *) (decode + 12)));
- base_rev = ntohl(*((uint32_t *) (decode + 16)));
- link_rev = ntohl(*((uint32_t *) (decode + 20)));
- parent_1 = ntohl(*((uint32_t *) (decode + 24)));
- parent_2 = ntohl(*((uint32_t *) (decode + 28)));
- c_node_id = decode + 32;
-
- entry = _build_idx_entry(nodemap, n, offset_flags,
- comp_len, uncomp_len, base_rev,
- link_rev, parent_1, parent_2,
- c_node_id);
- if (!entry)
- return 0;
-
- if (inlined) {
- err = PyList_Append(index, entry);
- Py_DECREF(entry);
- if (err)
- return 0;
- } else
- PyList_SET_ITEM(index, n, entry); /* steals reference */
-
- n++;
- step = 64 + (inlined ? comp_len : 0);
- if (end - data < step)
- break;
- data += step;
- }
- if (data != end) {
- if (!PyErr_Occurred())
- PyErr_SetString(PyExc_ValueError, "corrupt index file");
- return 0;
- }
-
- /* create the nullid/nullrev entry in the nodemap and the
- * magic nullid entry in the index at [-1] */
- entry = _build_idx_entry(nodemap,
- nullrev, 0, 0, 0, -1, -1, -1, -1, nullid);
- if (!entry)
- return 0;
- if (inlined) {
- err = PyList_Append(index, entry);
- Py_DECREF(entry);
- if (err)
- return 0;
- } else
- PyList_SET_ITEM(index, n, entry); /* steals reference */
-
- return 1;
-}
-
-/* This function parses a index file and returns a Python tuple of the
- * following format: (index, nodemap, cache)
- *
- * index: a list of tuples containing the RevlogNG records
- * nodemap: a dict mapping node ids to indices in the index list
- * cache: if data is inlined, a tuple (index_file_content, 0) else None
- */
-static PyObject *parse_index(PyObject *self, PyObject *args)
-{
- const char *data;
- int size, inlined;
- PyObject *rval = NULL, *index = NULL, *nodemap = NULL, *cache = NULL;
- PyObject *data_obj = NULL, *inlined_obj;
-
- if (!PyArg_ParseTuple(args, "s#O", &data, &size, &inlined_obj))
- return NULL;
- inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
-
- /* If no data is inlined, we know the size of the index list in
- * advance: size divided by size of one one revlog record (64 bytes)
- * plus one for the nullid */
- index = inlined ? PyList_New(0) : PyList_New(size / 64 + 1);
- if (!index)
- goto quit;
-
- nodemap = PyDict_New();
- if (!nodemap)
- goto quit;
-
- /* set up the cache return value */
- if (inlined) {
- /* Note that the reference to data_obj is only borrowed */
- data_obj = PyTuple_GET_ITEM(args, 0);
- cache = Py_BuildValue("iO", 0, data_obj);
- if (!cache)
- goto quit;
- } else {
- cache = Py_None;
- Py_INCREF(Py_None);
- }
-
- /* actually populate the index and the nodemap with data */
- if (!_parse_index_ng (data, size, inlined, index, nodemap))
- goto quit;
-
- rval = Py_BuildValue("NNN", index, nodemap, cache);
- if (!rval)
- goto quit;
- return rval;
-
-quit:
- Py_XDECREF(index);
- Py_XDECREF(nodemap);
- Py_XDECREF(cache);
- Py_XDECREF(rval);
- return NULL;
-}
-
-
-static char parsers_doc[] = "Efficient content parsing.";
-
-static PyMethodDef methods[] = {
- {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
- {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
- {"parse_index", parse_index, METH_VARARGS, "parse a revlog index\n"},
- {NULL, NULL}
-};
-
-PyMODINIT_FUNC initparsers(void)
-{
- Py_InitModule3("parsers", methods, parsers_doc);
-}
diff --git a/sys/src/cmd/hg/mercurial/patch.py b/sys/src/cmd/hg/mercurial/patch.py
deleted file mode 100644
index d04a76aaf..000000000
--- a/sys/src/cmd/hg/mercurial/patch.py
+++ /dev/null
@@ -1,1454 +0,0 @@
-# patch.py - patch file parsing routines
-#
-# Copyright 2006 Brendan Cully <brendan@kublai.com>
-# Copyright 2007 Chris Mason <chris.mason@oracle.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-from node import hex, nullid, short
-import base85, cmdutil, mdiff, util, diffhelpers, copies
-import cStringIO, email.Parser, os, re
-import sys, tempfile, zlib
-
-gitre = re.compile('diff --git a/(.*) b/(.*)')
-
-class PatchError(Exception):
- pass
-
-class NoHunks(PatchError):
- pass
-
-# helper functions
-
-def copyfile(src, dst, basedir):
- abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
- if os.path.exists(absdst):
- raise util.Abort(_("cannot create %s: destination already exists") %
- dst)
-
- dstdir = os.path.dirname(absdst)
- if dstdir and not os.path.isdir(dstdir):
- try:
- os.makedirs(dstdir)
- except IOError:
- raise util.Abort(
- _("cannot create %s: unable to create destination directory")
- % dst)
-
- util.copyfile(abssrc, absdst)
-
-# public functions
-
-def extract(ui, fileobj):
- '''extract patch from data read from fileobj.
-
- patch can be a normal patch or contained in an email message.
-
- return tuple (filename, message, user, date, node, p1, p2).
- Any item in the returned tuple can be None. If filename is None,
- fileobj did not contain a patch. Caller must unlink filename when done.'''
-
- # attempt to detect the start of a patch
- # (this heuristic is borrowed from quilt)
- diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
- r'retrieving revision [0-9]+(\.[0-9]+)*$|'
- r'(---|\*\*\*)[ \t])', re.MULTILINE)
-
- fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
- tmpfp = os.fdopen(fd, 'w')
- try:
- msg = email.Parser.Parser().parse(fileobj)
-
- subject = msg['Subject']
- user = msg['From']
- gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
- # should try to parse msg['Date']
- date = None
- nodeid = None
- branch = None
- parents = []
-
- if subject:
- if subject.startswith('[PATCH'):
- pend = subject.find(']')
- if pend >= 0:
- subject = subject[pend+1:].lstrip()
- subject = subject.replace('\n\t', ' ')
- ui.debug('Subject: %s\n' % subject)
- if user:
- ui.debug('From: %s\n' % user)
- diffs_seen = 0
- ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
- message = ''
- for part in msg.walk():
- content_type = part.get_content_type()
- ui.debug('Content-Type: %s\n' % content_type)
- if content_type not in ok_types:
- continue
- payload = part.get_payload(decode=True)
- m = diffre.search(payload)
- if m:
- hgpatch = False
- ignoretext = False
-
- ui.debug(_('found patch at byte %d\n') % m.start(0))
- diffs_seen += 1
- cfp = cStringIO.StringIO()
- for line in payload[:m.start(0)].splitlines():
- if line.startswith('# HG changeset patch'):
- ui.debug(_('patch generated by hg export\n'))
- hgpatch = True
- # drop earlier commit message content
- cfp.seek(0)
- cfp.truncate()
- subject = None
- elif hgpatch:
- if line.startswith('# User '):
- user = line[7:]
- ui.debug('From: %s\n' % user)
- elif line.startswith("# Date "):
- date = line[7:]
- elif line.startswith("# Branch "):
- branch = line[9:]
- elif line.startswith("# Node ID "):
- nodeid = line[10:]
- elif line.startswith("# Parent "):
- parents.append(line[10:])
- elif line == '---' and gitsendmail:
- ignoretext = True
- if not line.startswith('# ') and not ignoretext:
- cfp.write(line)
- cfp.write('\n')
- message = cfp.getvalue()
- if tmpfp:
- tmpfp.write(payload)
- if not payload.endswith('\n'):
- tmpfp.write('\n')
- elif not diffs_seen and message and content_type == 'text/plain':
- message += '\n' + payload
- except:
- tmpfp.close()
- os.unlink(tmpname)
- raise
-
- if subject and not message.startswith(subject):
- message = '%s\n%s' % (subject, message)
- tmpfp.close()
- if not diffs_seen:
- os.unlink(tmpname)
- return None, message, user, date, branch, None, None, None
- p1 = parents and parents.pop(0) or None
- p2 = parents and parents.pop(0) or None
- return tmpname, message, user, date, branch, nodeid, p1, p2
-
-GP_PATCH = 1 << 0 # we have to run patch
-GP_FILTER = 1 << 1 # there's some copy/rename operation
-GP_BINARY = 1 << 2 # there's a binary patch
-
-class patchmeta(object):
- """Patched file metadata
-
- 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
- or COPY. 'path' is patched file path. 'oldpath' is set to the
- origin file when 'op' is either COPY or RENAME, None otherwise. If
- file mode is changed, 'mode' is a tuple (islink, isexec) where
- 'islink' is True if the file is a symlink and 'isexec' is True if
- the file is executable. Otherwise, 'mode' is None.
- """
- def __init__(self, path):
- self.path = path
- self.oldpath = None
- self.mode = None
- self.op = 'MODIFY'
- self.lineno = 0
- self.binary = False
-
- def setmode(self, mode):
- islink = mode & 020000
- isexec = mode & 0100
- self.mode = (islink, isexec)
-
-def readgitpatch(lr):
- """extract git-style metadata about patches from <patchname>"""
-
- # Filter patch for git information
- gp = None
- gitpatches = []
- # Can have a git patch with only metadata, causing patch to complain
- dopatch = 0
-
- lineno = 0
- for line in lr:
- lineno += 1
- line = line.rstrip(' \r\n')
- if line.startswith('diff --git'):
- m = gitre.match(line)
- if m:
- if gp:
- gitpatches.append(gp)
- src, dst = m.group(1, 2)
- gp = patchmeta(dst)
- gp.lineno = lineno
- elif gp:
- if line.startswith('--- '):
- if gp.op in ('COPY', 'RENAME'):
- dopatch |= GP_FILTER
- gitpatches.append(gp)
- gp = None
- dopatch |= GP_PATCH
- continue
- if line.startswith('rename from '):
- gp.op = 'RENAME'
- gp.oldpath = line[12:]
- elif line.startswith('rename to '):
- gp.path = line[10:]
- elif line.startswith('copy from '):
- gp.op = 'COPY'
- gp.oldpath = line[10:]
- elif line.startswith('copy to '):
- gp.path = line[8:]
- elif line.startswith('deleted file'):
- gp.op = 'DELETE'
- # is the deleted file a symlink?
- gp.setmode(int(line[-6:], 8))
- elif line.startswith('new file mode '):
- gp.op = 'ADD'
- gp.setmode(int(line[-6:], 8))
- elif line.startswith('new mode '):
- gp.setmode(int(line[-6:], 8))
- elif line.startswith('GIT binary patch'):
- dopatch |= GP_BINARY
- gp.binary = True
- if gp:
- gitpatches.append(gp)
-
- if not gitpatches:
- dopatch = GP_PATCH
-
- return (dopatch, gitpatches)
-
-class linereader(object):
- # simple class to allow pushing lines back into the input stream
- def __init__(self, fp, textmode=False):
- self.fp = fp
- self.buf = []
- self.textmode = textmode
-
- def push(self, line):
- if line is not None:
- self.buf.append(line)
-
- def readline(self):
- if self.buf:
- l = self.buf[0]
- del self.buf[0]
- return l
- l = self.fp.readline()
- if self.textmode and l.endswith('\r\n'):
- l = l[:-2] + '\n'
- return l
-
- def __iter__(self):
- while 1:
- l = self.readline()
- if not l:
- break
- yield l
-
-# @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
-unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
-contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
-
-class patchfile(object):
- def __init__(self, ui, fname, opener, missing=False, eol=None):
- self.fname = fname
- self.eol = eol
- self.opener = opener
- self.ui = ui
- self.lines = []
- self.exists = False
- self.missing = missing
- if not missing:
- try:
- self.lines = self.readlines(fname)
- self.exists = True
- except IOError:
- pass
- else:
- self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
-
- self.hash = {}
- self.dirty = 0
- self.offset = 0
- self.rej = []
- self.fileprinted = False
- self.printfile(False)
- self.hunks = 0
-
- def readlines(self, fname):
- fp = self.opener(fname, 'r')
- try:
- return list(linereader(fp, self.eol is not None))
- finally:
- fp.close()
-
- def writelines(self, fname, lines):
- fp = self.opener(fname, 'w')
- try:
- if self.eol and self.eol != '\n':
- for l in lines:
- if l and l[-1] == '\n':
- l = l[:-1] + self.eol
- fp.write(l)
- else:
- fp.writelines(lines)
- finally:
- fp.close()
-
- def unlink(self, fname):
- os.unlink(fname)
-
- def printfile(self, warn):
- if self.fileprinted:
- return
- if warn or self.ui.verbose:
- self.fileprinted = True
- s = _("patching file %s\n") % self.fname
- if warn:
- self.ui.warn(s)
- else:
- self.ui.note(s)
-
-
- def findlines(self, l, linenum):
- # looks through the hash and finds candidate lines. The
- # result is a list of line numbers sorted based on distance
- # from linenum
-
- try:
- cand = self.hash[l]
- except:
- return []
-
- if len(cand) > 1:
- # resort our list of potentials forward then back.
- cand.sort(key=lambda x: abs(x - linenum))
- return cand
-
- def hashlines(self):
- self.hash = {}
- for x, s in enumerate(self.lines):
- self.hash.setdefault(s, []).append(x)
-
- def write_rej(self):
- # our rejects are a little different from patch(1). This always
- # creates rejects in the same form as the original patch. A file
- # header is inserted so that you can run the reject through patch again
- # without having to type the filename.
-
- if not self.rej:
- return
-
- fname = self.fname + ".rej"
- self.ui.warn(
- _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
- (len(self.rej), self.hunks, fname))
-
- def rejlines():
- base = os.path.basename(self.fname)
- yield "--- %s\n+++ %s\n" % (base, base)
- for x in self.rej:
- for l in x.hunk:
- yield l
- if l[-1] != '\n':
- yield "\n\ No newline at end of file\n"
-
- self.writelines(fname, rejlines())
-
- def write(self, dest=None):
- if not self.dirty:
- return
- if not dest:
- dest = self.fname
- self.writelines(dest, self.lines)
-
- def close(self):
- self.write()
- self.write_rej()
-
- def apply(self, h, reverse):
- if not h.complete():
- raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
- (h.number, h.desc, len(h.a), h.lena, len(h.b),
- h.lenb))
-
- self.hunks += 1
- if reverse:
- h.reverse()
-
- if self.missing:
- self.rej.append(h)
- return -1
-
- if self.exists and h.createfile():
- self.ui.warn(_("file %s already exists\n") % self.fname)
- self.rej.append(h)
- return -1
-
- if isinstance(h, githunk):
- if h.rmfile():
- self.unlink(self.fname)
- else:
- self.lines[:] = h.new()
- self.offset += len(h.new())
- self.dirty = 1
- return 0
-
- # fast case first, no offsets, no fuzz
- old = h.old()
- # patch starts counting at 1 unless we are adding the file
- if h.starta == 0:
- start = 0
- else:
- start = h.starta + self.offset - 1
- orig_start = start
- if diffhelpers.testhunk(old, self.lines, start) == 0:
- if h.rmfile():
- self.unlink(self.fname)
- else:
- self.lines[start : start + h.lena] = h.new()
- self.offset += h.lenb - h.lena
- self.dirty = 1
- return 0
-
- # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
- self.hashlines()
- if h.hunk[-1][0] != ' ':
- # if the hunk tried to put something at the bottom of the file
- # override the start line and use eof here
- search_start = len(self.lines)
- else:
- search_start = orig_start
-
- for fuzzlen in xrange(3):
- for toponly in [ True, False ]:
- old = h.old(fuzzlen, toponly)
-
- cand = self.findlines(old[0][1:], search_start)
- for l in cand:
- if diffhelpers.testhunk(old, self.lines, l) == 0:
- newlines = h.new(fuzzlen, toponly)
- self.lines[l : l + len(old)] = newlines
- self.offset += len(newlines) - len(old)
- self.dirty = 1
- if fuzzlen:
- fuzzstr = "with fuzz %d " % fuzzlen
- f = self.ui.warn
- self.printfile(True)
- else:
- fuzzstr = ""
- f = self.ui.note
- offset = l - orig_start - fuzzlen
- if offset == 1:
- msg = _("Hunk #%d succeeded at %d %s"
- "(offset %d line).\n")
- else:
- msg = _("Hunk #%d succeeded at %d %s"
- "(offset %d lines).\n")
- f(msg % (h.number, l+1, fuzzstr, offset))
- return fuzzlen
- self.printfile(True)
- self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
- self.rej.append(h)
- return -1
-
-class hunk(object):
- def __init__(self, desc, num, lr, context, create=False, remove=False):
- self.number = num
- self.desc = desc
- self.hunk = [ desc ]
- self.a = []
- self.b = []
- if context:
- self.read_context_hunk(lr)
- else:
- self.read_unified_hunk(lr)
- self.create = create
- self.remove = remove and not create
-
- def read_unified_hunk(self, lr):
- m = unidesc.match(self.desc)
- if not m:
- raise PatchError(_("bad hunk #%d") % self.number)
- self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
- if self.lena is None:
- self.lena = 1
- else:
- self.lena = int(self.lena)
- if self.lenb is None:
- self.lenb = 1
- else:
- self.lenb = int(self.lenb)
- self.starta = int(self.starta)
- self.startb = int(self.startb)
- diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
- # if we hit eof before finishing out the hunk, the last line will
- # be zero length. Lets try to fix it up.
- while len(self.hunk[-1]) == 0:
- del self.hunk[-1]
- del self.a[-1]
- del self.b[-1]
- self.lena -= 1
- self.lenb -= 1
-
- def read_context_hunk(self, lr):
- self.desc = lr.readline()
- m = contextdesc.match(self.desc)
- if not m:
- raise PatchError(_("bad hunk #%d") % self.number)
- foo, self.starta, foo2, aend, foo3 = m.groups()
- self.starta = int(self.starta)
- if aend is None:
- aend = self.starta
- self.lena = int(aend) - self.starta
- if self.starta:
- self.lena += 1
- for x in xrange(self.lena):
- l = lr.readline()
- if l.startswith('---'):
- lr.push(l)
- break
- s = l[2:]
- if l.startswith('- ') or l.startswith('! '):
- u = '-' + s
- elif l.startswith(' '):
- u = ' ' + s
- else:
- raise PatchError(_("bad hunk #%d old text line %d") %
- (self.number, x))
- self.a.append(u)
- self.hunk.append(u)
-
- l = lr.readline()
- if l.startswith('\ '):
- s = self.a[-1][:-1]
- self.a[-1] = s
- self.hunk[-1] = s
- l = lr.readline()
- m = contextdesc.match(l)
- if not m:
- raise PatchError(_("bad hunk #%d") % self.number)
- foo, self.startb, foo2, bend, foo3 = m.groups()
- self.startb = int(self.startb)
- if bend is None:
- bend = self.startb
- self.lenb = int(bend) - self.startb
- if self.startb:
- self.lenb += 1
- hunki = 1
- for x in xrange(self.lenb):
- l = lr.readline()
- if l.startswith('\ '):
- s = self.b[-1][:-1]
- self.b[-1] = s
- self.hunk[hunki-1] = s
- continue
- if not l:
- lr.push(l)
- break
- s = l[2:]
- if l.startswith('+ ') or l.startswith('! '):
- u = '+' + s
- elif l.startswith(' '):
- u = ' ' + s
- elif len(self.b) == 0:
- # this can happen when the hunk does not add any lines
- lr.push(l)
- break
- else:
- raise PatchError(_("bad hunk #%d old text line %d") %
- (self.number, x))
- self.b.append(s)
- while True:
- if hunki >= len(self.hunk):
- h = ""
- else:
- h = self.hunk[hunki]
- hunki += 1
- if h == u:
- break
- elif h.startswith('-'):
- continue
- else:
- self.hunk.insert(hunki-1, u)
- break
-
- if not self.a:
- # this happens when lines were only added to the hunk
- for x in self.hunk:
- if x.startswith('-') or x.startswith(' '):
- self.a.append(x)
- if not self.b:
- # this happens when lines were only deleted from the hunk
- for x in self.hunk:
- if x.startswith('+') or x.startswith(' '):
- self.b.append(x[1:])
- # @@ -start,len +start,len @@
- self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
- self.startb, self.lenb)
- self.hunk[0] = self.desc
-
- def reverse(self):
- self.create, self.remove = self.remove, self.create
- origlena = self.lena
- origstarta = self.starta
- self.lena = self.lenb
- self.starta = self.startb
- self.lenb = origlena
- self.startb = origstarta
- self.a = []
- self.b = []
- # self.hunk[0] is the @@ description
- for x in xrange(1, len(self.hunk)):
- o = self.hunk[x]
- if o.startswith('-'):
- n = '+' + o[1:]
- self.b.append(o[1:])
- elif o.startswith('+'):
- n = '-' + o[1:]
- self.a.append(n)
- else:
- n = o
- self.b.append(o[1:])
- self.a.append(o)
- self.hunk[x] = o
-
- def fix_newline(self):
- diffhelpers.fix_newline(self.hunk, self.a, self.b)
-
- def complete(self):
- return len(self.a) == self.lena and len(self.b) == self.lenb
-
- def createfile(self):
- return self.starta == 0 and self.lena == 0 and self.create
-
- def rmfile(self):
- return self.startb == 0 and self.lenb == 0 and self.remove
-
- def fuzzit(self, l, fuzz, toponly):
- # this removes context lines from the top and bottom of list 'l'. It
- # checks the hunk to make sure only context lines are removed, and then
- # returns a new shortened list of lines.
- fuzz = min(fuzz, len(l)-1)
- if fuzz:
- top = 0
- bot = 0
- hlen = len(self.hunk)
- for x in xrange(hlen-1):
- # the hunk starts with the @@ line, so use x+1
- if self.hunk[x+1][0] == ' ':
- top += 1
- else:
- break
- if not toponly:
- for x in xrange(hlen-1):
- if self.hunk[hlen-bot-1][0] == ' ':
- bot += 1
- else:
- break
-
- # top and bot now count context in the hunk
- # adjust them if either one is short
- context = max(top, bot, 3)
- if bot < context:
- bot = max(0, fuzz - (context - bot))
- else:
- bot = min(fuzz, bot)
- if top < context:
- top = max(0, fuzz - (context - top))
- else:
- top = min(fuzz, top)
-
- return l[top:len(l)-bot]
- return l
-
- def old(self, fuzz=0, toponly=False):
- return self.fuzzit(self.a, fuzz, toponly)
-
- def newctrl(self):
- res = []
- for x in self.hunk:
- c = x[0]
- if c == ' ' or c == '+':
- res.append(x)
- return res
-
- def new(self, fuzz=0, toponly=False):
- return self.fuzzit(self.b, fuzz, toponly)
-
-class githunk(object):
- """A git hunk"""
- def __init__(self, gitpatch):
- self.gitpatch = gitpatch
- self.text = None
- self.hunk = []
-
- def createfile(self):
- return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
-
- def rmfile(self):
- return self.gitpatch.op == 'DELETE'
-
- def complete(self):
- return self.text is not None
-
- def new(self):
- return [self.text]
-
-class binhunk(githunk):
- 'A binary patch file. Only understands literals so far.'
- def __init__(self, gitpatch):
- super(binhunk, self).__init__(gitpatch)
- self.hunk = ['GIT binary patch\n']
-
- def extract(self, lr):
- line = lr.readline()
- self.hunk.append(line)
- while line and not line.startswith('literal '):
- line = lr.readline()
- self.hunk.append(line)
- if not line:
- raise PatchError(_('could not extract binary patch'))
- size = int(line[8:].rstrip())
- dec = []
- line = lr.readline()
- self.hunk.append(line)
- while len(line) > 1:
- l = line[0]
- if l <= 'Z' and l >= 'A':
- l = ord(l) - ord('A') + 1
- else:
- l = ord(l) - ord('a') + 27
- dec.append(base85.b85decode(line[1:-1])[:l])
- line = lr.readline()
- self.hunk.append(line)
- text = zlib.decompress(''.join(dec))
- if len(text) != size:
- raise PatchError(_('binary patch is %d bytes, not %d') %
- len(text), size)
- self.text = text
-
-class symlinkhunk(githunk):
- """A git symlink hunk"""
- def __init__(self, gitpatch, hunk):
- super(symlinkhunk, self).__init__(gitpatch)
- self.hunk = hunk
-
- def complete(self):
- return True
-
- def fix_newline(self):
- return
-
-def parsefilename(str):
- # --- filename \t|space stuff
- s = str[4:].rstrip('\r\n')
- i = s.find('\t')
- if i < 0:
- i = s.find(' ')
- if i < 0:
- return s
- return s[:i]
-
-def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
- def pathstrip(path, count=1):
- pathlen = len(path)
- i = 0
- if count == 0:
- return '', path.rstrip()
- while count > 0:
- 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] == '/':
- i += 1
- count -= 1
- return path[:i].lstrip(), path[i:].rstrip()
-
- nulla = afile_orig == "/dev/null"
- nullb = bfile_orig == "/dev/null"
- abase, afile = pathstrip(afile_orig, strip)
- gooda = not nulla and util.lexists(afile)
- bbase, bfile = pathstrip(bfile_orig, strip)
- if afile == bfile:
- goodb = gooda
- else:
- goodb = not nullb and os.path.exists(bfile)
- createfunc = hunk.createfile
- if reverse:
- createfunc = hunk.rmfile
- missing = not goodb and not gooda and not createfunc()
-
- # some diff programs apparently produce create patches where the
- # afile is not /dev/null, but rather the same name as the bfile
- if missing and afile == bfile:
- # this isn't very pretty
- hunk.create = True
- if createfunc():
- missing = False
- else:
- hunk.create = False
-
- # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
- # diff is between a file and its backup. In this case, the original
- # file should be patched (see original mpatch code).
- isbackup = (abase == bbase and bfile.startswith(afile))
- fname = None
- if not missing:
- if gooda and goodb:
- fname = isbackup and afile or bfile
- elif gooda:
- fname = afile
-
- if not fname:
- if not nullb:
- fname = isbackup and afile or bfile
- elif not nulla:
- fname = afile
- else:
- raise PatchError(_("undefined source and destination files"))
-
- return fname, missing
-
-def scangitpatch(lr, firstline):
- """
- Git patches can emit:
- - rename a to b
- - change b
- - copy a to c
- - change c
-
- We cannot apply this sequence as-is, the renamed 'a' could not be
- found for it would have been renamed already. And we cannot copy
- from 'b' instead because 'b' would have been changed already. So
- we scan the git patch for copy and rename commands so we can
- perform the copies ahead of time.
- """
- pos = 0
- try:
- pos = lr.fp.tell()
- fp = lr.fp
- except IOError:
- fp = cStringIO.StringIO(lr.fp.read())
- gitlr = linereader(fp, lr.textmode)
- gitlr.push(firstline)
- (dopatch, gitpatches) = readgitpatch(gitlr)
- fp.seek(pos)
- return dopatch, gitpatches
-
-def iterhunks(ui, fp, sourcefile=None, textmode=False):
- """Read a patch and yield the following events:
- - ("file", afile, bfile, firsthunk): select a new target file.
- - ("hunk", hunk): a new hunk is ready to be applied, follows a
- "file" event.
- - ("git", gitchanges): current diff is in git format, gitchanges
- maps filenames to gitpatch records. Unique event.
-
- If textmode is True, input line-endings are normalized to LF.
- """
- changed = {}
- current_hunk = None
- afile = ""
- bfile = ""
- state = None
- hunknum = 0
- emitfile = False
- git = False
-
- # our states
- BFILE = 1
- context = None
- lr = linereader(fp, textmode)
- dopatch = True
- # gitworkdone is True if a git operation (copy, rename, ...) was
- # performed already for the current file. Useful when the file
- # section may have no hunk.
- gitworkdone = False
-
- while True:
- newfile = False
- x = lr.readline()
- if not x:
- break
- if current_hunk:
- if x.startswith('\ '):
- current_hunk.fix_newline()
- yield 'hunk', current_hunk
- current_hunk = None
- gitworkdone = False
- if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
- ((context is not False) and x.startswith('***************')))):
- try:
- if context is None and x.startswith('***************'):
- context = True
- gpatch = changed.get(bfile)
- create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
- remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
- current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
- if remove:
- gpatch = changed.get(afile[2:])
- if gpatch and gpatch.mode[0]:
- current_hunk = symlinkhunk(gpatch, current_hunk)
- except PatchError, err:
- ui.debug(err)
- current_hunk = None
- continue
- hunknum += 1
- if emitfile:
- emitfile = False
- yield 'file', (afile, bfile, current_hunk)
- elif state == BFILE and x.startswith('GIT binary patch'):
- current_hunk = binhunk(changed[bfile])
- hunknum += 1
- if emitfile:
- emitfile = False
- yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
- current_hunk.extract(lr)
- elif x.startswith('diff --git'):
- # check for git diff, scanning the whole patch file if needed
- m = gitre.match(x)
- if m:
- afile, bfile = m.group(1, 2)
- if not git:
- git = True
- dopatch, gitpatches = scangitpatch(lr, x)
- yield 'git', gitpatches
- for gp in gitpatches:
- changed[gp.path] = gp
- # else error?
- # copy/rename + modify should modify target, not source
- gp = changed.get(bfile)
- if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
- afile = bfile
- gitworkdone = True
- newfile = True
- elif x.startswith('---'):
- # check for a unified diff
- l2 = lr.readline()
- if not l2.startswith('+++'):
- lr.push(l2)
- continue
- newfile = True
- context = False
- afile = parsefilename(x)
- bfile = parsefilename(l2)
- elif x.startswith('***'):
- # check for a context diff
- l2 = lr.readline()
- if not l2.startswith('---'):
- lr.push(l2)
- continue
- l3 = lr.readline()
- lr.push(l3)
- if not l3.startswith("***************"):
- lr.push(l2)
- continue
- newfile = True
- context = True
- afile = parsefilename(x)
- bfile = parsefilename(l2)
-
- if newfile:
- emitfile = True
- state = BFILE
- hunknum = 0
- if current_hunk:
- if current_hunk.complete():
- yield 'hunk', current_hunk
- else:
- raise PatchError(_("malformed patch %s %s") % (afile,
- current_hunk.desc))
-
- if hunknum == 0 and dopatch and not gitworkdone:
- raise NoHunks
-
-def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
- eol=None):
- """
- Reads a patch from fp and tries to apply it.
-
- The dict 'changed' is filled in with all of the filenames changed
- by the patch. Returns 0 for a clean patch, -1 if any rejects were
- found and 1 if there was any fuzz.
-
- If 'eol' is None, the patch content and patched file are read in
- binary mode. Otherwise, line endings are ignored when patching then
- normalized to 'eol' (usually '\n' or \r\n').
- """
- rejects = 0
- err = 0
- current_file = None
- gitpatches = None
- opener = util.opener(os.getcwd())
- textmode = eol is not None
-
- def closefile():
- if not current_file:
- return 0
- current_file.close()
- return len(current_file.rej)
-
- for state, values in iterhunks(ui, fp, sourcefile, textmode):
- if state == 'hunk':
- if not current_file:
- continue
- current_hunk = values
- ret = current_file.apply(current_hunk, reverse)
- if ret >= 0:
- changed.setdefault(current_file.fname, None)
- if ret > 0:
- err = 1
- elif state == 'file':
- rejects += closefile()
- afile, bfile, first_hunk = values
- try:
- if sourcefile:
- current_file = patchfile(ui, sourcefile, opener, eol=eol)
- else:
- current_file, missing = selectfile(afile, bfile, first_hunk,
- strip, reverse)
- current_file = patchfile(ui, current_file, opener, missing, eol)
- except PatchError, err:
- ui.warn(str(err) + '\n')
- current_file, current_hunk = None, None
- rejects += 1
- continue
- elif state == 'git':
- gitpatches = values
- cwd = os.getcwd()
- for gp in gitpatches:
- if gp.op in ('COPY', 'RENAME'):
- copyfile(gp.oldpath, gp.path, cwd)
- changed[gp.path] = gp
- else:
- raise util.Abort(_('unsupported parser state: %s') % state)
-
- rejects += closefile()
-
- if rejects:
- return -1
- return err
-
-def diffopts(ui, opts={}, untrusted=False):
- def get(key, name=None, getter=ui.configbool):
- return (opts.get(key) or
- getter('diff', name or key, None, untrusted=untrusted))
- return mdiff.diffopts(
- text=opts.get('text'),
- git=get('git'),
- nodates=get('nodates'),
- showfunc=get('show_function', 'showfunc'),
- ignorews=get('ignore_all_space', 'ignorews'),
- ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
- ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
- context=get('unified', getter=ui.config))
-
-def updatedir(ui, repo, patches, similarity=0):
- '''Update dirstate after patch application according to metadata'''
- if not patches:
- return
- copies = []
- removes = set()
- cfiles = patches.keys()
- cwd = repo.getcwd()
- if cwd:
- cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
- for f in patches:
- gp = patches[f]
- if not gp:
- continue
- if gp.op == 'RENAME':
- copies.append((gp.oldpath, gp.path))
- removes.add(gp.oldpath)
- elif gp.op == 'COPY':
- copies.append((gp.oldpath, gp.path))
- elif gp.op == 'DELETE':
- removes.add(gp.path)
- for src, dst in copies:
- repo.copy(src, dst)
- if (not similarity) and removes:
- repo.remove(sorted(removes), True)
- for f in patches:
- gp = patches[f]
- if gp and gp.mode:
- islink, isexec = gp.mode
- dst = repo.wjoin(gp.path)
- # patch won't create empty files
- if gp.op == 'ADD' and not os.path.exists(dst):
- flags = (isexec and 'x' or '') + (islink and 'l' or '')
- repo.wwrite(gp.path, '', flags)
- elif gp.op != 'DELETE':
- util.set_flags(dst, islink, isexec)
- cmdutil.addremove(repo, cfiles, similarity=similarity)
- files = patches.keys()
- files.extend([r for r in removes if r not in files])
- return sorted(files)
-
-def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
- """use <patcher> to apply <patchname> to the working directory.
- returns whether patch was applied with fuzz factor."""
-
- fuzz = False
- if cwd:
- args.append('-d %s' % util.shellquote(cwd))
- fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
- util.shellquote(patchname)))
-
- for line in fp:
- line = line.rstrip()
- ui.note(line + '\n')
- if line.startswith('patching file '):
- pf = util.parse_patch_output(line)
- printed_file = False
- files.setdefault(pf, None)
- elif line.find('with fuzz') >= 0:
- fuzz = True
- if not printed_file:
- ui.warn(pf + '\n')
- printed_file = True
- ui.warn(line + '\n')
- elif line.find('saving rejects to file') >= 0:
- ui.warn(line + '\n')
- elif line.find('FAILED') >= 0:
- if not printed_file:
- ui.warn(pf + '\n')
- printed_file = True
- ui.warn(line + '\n')
- code = fp.close()
- if code:
- raise PatchError(_("patch command failed: %s") %
- util.explain_exit(code)[0])
- return fuzz
-
-def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
- """use builtin patch to apply <patchobj> to the working directory.
- returns whether patch was applied with fuzz factor."""
-
- if eolmode is None:
- eolmode = ui.config('patch', 'eol', 'strict')
- try:
- eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()]
- except KeyError:
- raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
-
- try:
- fp = open(patchobj, 'rb')
- except TypeError:
- fp = patchobj
- if cwd:
- curdir = os.getcwd()
- os.chdir(cwd)
- try:
- ret = applydiff(ui, fp, files, strip=strip, eol=eol)
- finally:
- if cwd:
- os.chdir(curdir)
- if ret < 0:
- raise PatchError
- return ret > 0
-
-def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
- """Apply <patchname> to the working directory.
-
- 'eolmode' specifies how end of lines should be handled. It can be:
- - 'strict': inputs are read in binary mode, EOLs are preserved
- - 'crlf': EOLs are ignored when patching and reset to CRLF
- - 'lf': EOLs are ignored when patching and reset to LF
- - None: get it from user settings, default to 'strict'
- 'eolmode' is ignored when using an external patcher program.
-
- Returns whether patch was applied with fuzz factor.
- """
- patcher = ui.config('ui', 'patch')
- args = []
- try:
- if patcher:
- return externalpatch(patcher, args, patchname, ui, strip, cwd,
- files)
- else:
- try:
- return internalpatch(patchname, ui, strip, cwd, files, eolmode)
- except NoHunks:
- patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
- ui.debug(_('no valid hunks found; trying with %r instead\n') %
- patcher)
- if util.needbinarypatch():
- args.append('--binary')
- return externalpatch(patcher, args, patchname, ui, strip, cwd,
- files)
- except PatchError, err:
- s = str(err)
- if s:
- raise util.Abort(s)
- else:
- raise util.Abort(_('patch failed to apply'))
-
-def b85diff(to, tn):
- '''print base85-encoded binary diff'''
- def gitindex(text):
- if not text:
- return '0' * 40
- l = len(text)
- s = util.sha1('blob %d\0' % l)
- s.update(text)
- return s.hexdigest()
-
- def fmtline(line):
- l = len(line)
- if l <= 26:
- l = chr(ord('A') + l - 1)
- else:
- l = chr(l - 26 + ord('a') - 1)
- return '%c%s\n' % (l, base85.b85encode(line, True))
-
- def chunk(text, csize=52):
- l = len(text)
- i = 0
- while i < l:
- yield text[i:i+csize]
- i += csize
-
- tohash = gitindex(to)
- tnhash = gitindex(tn)
- if tohash == tnhash:
- return ""
-
- # TODO: deltas
- ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
- (tohash, tnhash, len(tn))]
- for l in chunk(zlib.compress(tn)):
- ret.append(fmtline(l))
- ret.append('\n')
- return ''.join(ret)
-
-def _addmodehdr(header, omode, nmode):
- if omode != nmode:
- header.append('old mode %s\n' % omode)
- header.append('new mode %s\n' % nmode)
-
-def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
- '''yields diff of changes to files between two nodes, or node and
- working directory.
-
- if node1 is None, use first dirstate parent instead.
- if node2 is None, compare node1 with working directory.'''
-
- if opts is None:
- opts = mdiff.defaultopts
-
- if not node1:
- node1 = repo.dirstate.parents()[0]
-
- def lrugetfilectx():
- cache = {}
- order = []
- def getfilectx(f, ctx):
- fctx = ctx.filectx(f, filelog=cache.get(f))
- if f not in cache:
- if len(cache) > 20:
- del cache[order.pop(0)]
- cache[f] = fctx._filelog
- else:
- order.remove(f)
- order.append(f)
- return fctx
- return getfilectx
- getfilectx = lrugetfilectx()
-
- ctx1 = repo[node1]
- ctx2 = repo[node2]
-
- if not changes:
- changes = repo.status(ctx1, ctx2, match=match)
- modified, added, removed = changes[:3]
-
- if not modified and not added and not removed:
- return
-
- date1 = util.datestr(ctx1.date())
- man1 = ctx1.manifest()
-
- if repo.ui.quiet:
- r = None
- else:
- hexfunc = repo.ui.debugflag and hex or short
- r = [hexfunc(node) for node in [node1, node2] if node]
-
- if opts.git:
- copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
- copy = copy.copy()
- for k, v in copy.items():
- copy[v] = k
-
- gone = set()
- gitmode = {'l': '120000', 'x': '100755', '': '100644'}
-
- for f in sorted(modified + added + removed):
- to = None
- tn = None
- dodiff = True
- header = []
- if f in man1:
- to = getfilectx(f, ctx1).data()
- if f not in removed:
- tn = getfilectx(f, ctx2).data()
- a, b = f, f
- if opts.git:
- if f in added:
- mode = gitmode[ctx2.flags(f)]
- if f in copy:
- a = copy[f]
- omode = gitmode[man1.flags(a)]
- _addmodehdr(header, omode, mode)
- if a in removed and a not in gone:
- op = 'rename'
- gone.add(a)
- else:
- op = 'copy'
- header.append('%s from %s\n' % (op, a))
- header.append('%s to %s\n' % (op, f))
- to = getfilectx(a, ctx1).data()
- else:
- header.append('new file mode %s\n' % mode)
- if util.binary(tn):
- dodiff = 'binary'
- elif f in removed:
- # have we already reported a copy above?
- if f in copy and copy[f] in added and copy[copy[f]] == f:
- dodiff = False
- else:
- header.append('deleted file mode %s\n' %
- gitmode[man1.flags(f)])
- else:
- omode = gitmode[man1.flags(f)]
- nmode = gitmode[ctx2.flags(f)]
- _addmodehdr(header, omode, nmode)
- if util.binary(to) or util.binary(tn):
- dodiff = 'binary'
- r = None
- header.insert(0, mdiff.diffline(r, a, b, opts))
- if dodiff:
- if dodiff == 'binary':
- text = b85diff(to, tn)
- else:
- text = mdiff.unidiff(to, date1,
- # ctx2 date may be dynamic
- tn, util.datestr(ctx2.date()),
- a, b, r, opts=opts)
- if header and (text or len(header) > 1):
- yield ''.join(header)
- if text:
- yield text
-
-def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
- opts=None):
- '''export changesets as hg patches.'''
-
- total = len(revs)
- revwidth = max([len(str(rev)) for rev in revs])
-
- def single(rev, seqno, fp):
- ctx = repo[rev]
- node = ctx.node()
- parents = [p.node() for p in ctx.parents() if p]
- branch = ctx.branch()
- if switch_parent:
- parents.reverse()
- prev = (parents and parents[0]) or nullid
-
- if not fp:
- fp = cmdutil.make_file(repo, template, node, total=total,
- seqno=seqno, revwidth=revwidth,
- mode='ab')
- if fp != sys.stdout and hasattr(fp, 'name'):
- repo.ui.note("%s\n" % fp.name)
-
- fp.write("# HG changeset patch\n")
- fp.write("# User %s\n" % ctx.user())
- fp.write("# Date %d %d\n" % ctx.date())
- if branch and (branch != 'default'):
- fp.write("# Branch %s\n" % branch)
- fp.write("# Node ID %s\n" % hex(node))
- fp.write("# Parent %s\n" % hex(prev))
- if len(parents) > 1:
- fp.write("# Parent %s\n" % hex(parents[1]))
- fp.write(ctx.description().rstrip())
- fp.write("\n\n")
-
- for chunk in diff(repo, prev, node, opts=opts):
- fp.write(chunk)
-
- for seqno, rev in enumerate(revs):
- single(rev, seqno+1, fp)
-
-def diffstatdata(lines):
- filename, adds, removes = None, 0, 0
- for line in lines:
- if line.startswith('diff'):
- if filename:
- yield (filename, adds, removes)
- # set numbers to 0 anyway when starting new file
- adds, removes = 0, 0
- if line.startswith('diff --git'):
- filename = gitre.search(line).group(1)
- else:
- # format: "diff -r ... -r ... filename"
- filename = line.split(None, 5)[-1]
- elif line.startswith('+') and not line.startswith('+++'):
- adds += 1
- elif line.startswith('-') and not line.startswith('---'):
- removes += 1
- if filename:
- yield (filename, adds, removes)
-
-def diffstat(lines, width=80):
- output = []
- stats = list(diffstatdata(lines))
-
- maxtotal, maxname = 0, 0
- totaladds, totalremoves = 0, 0
- for filename, adds, removes in stats:
- totaladds += adds
- totalremoves += removes
- maxname = max(maxname, len(filename))
- maxtotal = max(maxtotal, adds+removes)
-
- countwidth = len(str(maxtotal))
- graphwidth = width - countwidth - maxname - 6
- if graphwidth < 10:
- graphwidth = 10
-
- def scale(i):
- if maxtotal <= graphwidth:
- return i
- # If diffstat runs out of room it doesn't print anything,
- # which isn't very useful, so always print at least one + or -
- # if there were at least some changes.
- return max(i * graphwidth // maxtotal, int(bool(i)))
-
- for filename, adds, removes in stats:
- pluses = '+' * scale(adds)
- minuses = '-' * scale(removes)
- output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth,
- adds+removes, pluses, minuses))
-
- if stats:
- output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
- % (len(stats), totaladds, totalremoves))
-
- return ''.join(output)
diff --git a/sys/src/cmd/hg/mercurial/posix.py b/sys/src/cmd/hg/mercurial/posix.py
deleted file mode 100644
index 09fd7cdc5..000000000
--- a/sys/src/cmd/hg/mercurial/posix.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# posix.py - Posix utility function implementations for Mercurial
-#
-# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import osutil
-import os, sys, errno, stat, getpass, pwd, grp, fcntl
-
-posixfile = open
-nulldev = '/dev/null'
-normpath = os.path.normpath
-samestat = os.path.samestat
-expandglobs = False
-
-umask = os.umask(0)
-os.umask(umask)
-
-def openhardlinks():
- '''return true if it is safe to hold open file handles to hardlinks'''
- return True
-
-def rcfiles(path):
- rcs = [os.path.join(path, 'hgrc')]
- rcdir = os.path.join(path, 'hgrc.d')
- try:
- rcs.extend([os.path.join(rcdir, f)
- for f, kind in osutil.listdir(rcdir)
- if f.endswith(".rc")])
- except OSError:
- pass
- return rcs
-
-def system_rcpath():
- path = []
- # old mod_python does not set sys.argv
- if len(getattr(sys, 'argv', [])) > 0:
- path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
- '/../etc/mercurial'))
- path.extend(rcfiles('/etc/mercurial'))
- return path
-
-def user_rcpath():
- return [os.path.expanduser('~/.hgrc')]
-
-def parse_patch_output(output_line):
- """parses the output produced by patch and returns the filename"""
- pf = output_line[14:]
- if os.sys.platform == 'OpenVMS':
- if pf[0] == '`':
- pf = pf[1:-1] # Remove the quotes
- else:
- if pf.startswith("'") and pf.endswith("'") and " " in pf:
- pf = pf[1:-1] # Remove the quotes
- return pf
-
-def sshargs(sshcmd, host, user, port):
- '''Build argument list for ssh'''
- args = user and ("%s@%s" % (user, host)) or host
- return port and ("%s -p %s" % (args, port)) or args
-
-def is_exec(f):
- """check whether a file is executable"""
- return (os.lstat(f).st_mode & 0100 != 0)
-
-def set_flags(f, l, x):
- s = os.lstat(f).st_mode
- if l:
- if not stat.S_ISLNK(s):
- # switch file to link
- data = open(f).read()
- os.unlink(f)
- try:
- os.symlink(data, f)
- except:
- # failed to make a link, rewrite file
- open(f, "w").write(data)
- # no chmod needed at this point
- return
- if stat.S_ISLNK(s):
- # switch link to file
- data = os.readlink(f)
- os.unlink(f)
- open(f, "w").write(data)
- s = 0666 & ~umask # avoid restatting for chmod
-
- sx = s & 0100
- if x and not sx:
- # Turn on +x for every +r bit when making a file executable
- # and obey umask.
- os.chmod(f, s | (s & 0444) >> 2 & ~umask)
- elif not x and sx:
- # Turn off all +x bits
- os.chmod(f, s & 0666)
-
-def set_binary(fd):
- pass
-
-def pconvert(path):
- return path
-
-def localpath(path):
- return path
-
-if sys.platform == 'darwin':
- def realpath(path):
- '''
- Returns the true, canonical file system path equivalent to the given
- path.
-
- Equivalent means, in this case, resulting in the same, unique
- file system link to the path. Every file system entry, whether a file,
- directory, hard link or symbolic link or special, will have a single
- path preferred by the system, but may allow multiple, differing path
- lookups to point to it.
-
- Most regular UNIX file systems only allow a file system entry to be
- looked up by its distinct path. Obviously, this does not apply to case
- insensitive file systems, whether case preserving or not. The most
- complex issue to deal with is file systems transparently reencoding the
- path, such as the non-standard Unicode normalisation required for HFS+
- and HFSX.
- '''
- # Constants copied from /usr/include/sys/fcntl.h
- F_GETPATH = 50
- O_SYMLINK = 0x200000
-
- try:
- fd = os.open(path, O_SYMLINK)
- except OSError, err:
- if err.errno is errno.ENOENT:
- return path
- raise
-
- try:
- return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
- finally:
- os.close(fd)
-else:
- # Fallback to the likely inadequate Python builtin function.
- realpath = os.path.realpath
-
-def shellquote(s):
- if os.sys.platform == 'OpenVMS':
- return '"%s"' % s
- else:
- return "'%s'" % s.replace("'", "'\\''")
-
-def quotecommand(cmd):
- return cmd
-
-def popen(command, mode='r'):
- return os.popen(command, mode)
-
-def testpid(pid):
- '''return False if pid dead, True if running or not sure'''
- if os.sys.platform == 'OpenVMS':
- return True
- try:
- os.kill(pid, 0)
- return True
- except OSError, inst:
- return inst.errno != errno.ESRCH
-
-def explain_exit(code):
- """return a 2-tuple (desc, code) describing a process's status"""
- if os.WIFEXITED(code):
- val = os.WEXITSTATUS(code)
- return _("exited with status %d") % val, val
- elif os.WIFSIGNALED(code):
- val = os.WTERMSIG(code)
- return _("killed by signal %d") % val, val
- elif os.WIFSTOPPED(code):
- val = os.WSTOPSIG(code)
- return _("stopped by signal %d") % val, val
- raise ValueError(_("invalid exit code"))
-
-def isowner(st):
- """Return True if the stat object st is from the current user."""
- return st.st_uid == os.getuid()
-
-def find_exe(command):
- '''Find executable for command searching like which does.
- If command is a basename then PATH is searched for command.
- PATH isn't searched if command is an absolute or relative path.
- If command isn't found None is returned.'''
- if sys.platform == 'OpenVMS':
- return command
-
- def findexisting(executable):
- 'Will return executable if existing file'
- if os.path.exists(executable):
- return executable
- return None
-
- if os.sep in command:
- return findexisting(command)
-
- for path in os.environ.get('PATH', '').split(os.pathsep):
- executable = findexisting(os.path.join(path, command))
- if executable is not None:
- return executable
- return None
-
-def set_signal_handler():
- pass
-
-def statfiles(files):
- 'Stat each file in files and yield stat or None if file does not exist.'
- lstat = os.lstat
- for nf in files:
- try:
- st = lstat(nf)
- except OSError, err:
- if err.errno not in (errno.ENOENT, errno.ENOTDIR):
- raise
- st = None
- yield st
-
-def getuser():
- '''return name of current user'''
- return getpass.getuser()
-
-def expand_glob(pats):
- '''On Windows, expand the implicit globs in a list of patterns'''
- return list(pats)
-
-def username(uid=None):
- """Return the name of the user with the given uid.
-
- If uid is None, return the name of the current user."""
-
- if uid is None:
- uid = os.getuid()
- try:
- return pwd.getpwuid(uid)[0]
- except KeyError:
- return str(uid)
-
-def groupname(gid=None):
- """Return the name of the group with the given gid.
-
- If gid is None, return the name of the current group."""
-
- if gid is None:
- gid = os.getgid()
- try:
- return grp.getgrgid(gid)[0]
- except KeyError:
- return str(gid)
diff --git a/sys/src/cmd/hg/mercurial/repair.py b/sys/src/cmd/hg/mercurial/repair.py
deleted file mode 100644
index f4d8d2641..000000000
--- a/sys/src/cmd/hg/mercurial/repair.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# repair.py - functions for repository repair for mercurial
-#
-# Copyright 2005, 2006 Chris Mason <mason@suse.com>
-# Copyright 2007 Matt Mackall
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import changegroup
-from node import nullrev, short
-from i18n import _
-import os
-
-def _bundle(repo, bases, heads, node, suffix, extranodes=None):
- """create a bundle with the specified revisions as a backup"""
- cg = repo.changegroupsubset(bases, heads, 'strip', extranodes)
- backupdir = repo.join("strip-backup")
- if not os.path.isdir(backupdir):
- os.mkdir(backupdir)
- name = os.path.join(backupdir, "%s-%s" % (short(node), suffix))
- repo.ui.warn(_("saving bundle to %s\n") % name)
- return changegroup.writebundle(cg, name, "HG10BZ")
-
-def _collectfiles(repo, striprev):
- """find out the filelogs affected by the strip"""
- files = set()
-
- for x in xrange(striprev, len(repo)):
- files.update(repo[x].files())
-
- return sorted(files)
-
-def _collectextranodes(repo, files, link):
- """return the nodes that have to be saved before the strip"""
- def collectone(revlog):
- extra = []
- startrev = count = len(revlog)
- # find the truncation point of the revlog
- for i in xrange(count):
- lrev = revlog.linkrev(i)
- if lrev >= link:
- startrev = i + 1
- break
-
- # see if any revision after that point has a linkrev less than link
- # (we have to manually save these guys)
- for i in xrange(startrev, count):
- node = revlog.node(i)
- lrev = revlog.linkrev(i)
- if lrev < link:
- extra.append((node, cl.node(lrev)))
-
- return extra
-
- extranodes = {}
- cl = repo.changelog
- extra = collectone(repo.manifest)
- if extra:
- extranodes[1] = extra
- for fname in files:
- f = repo.file(fname)
- extra = collectone(f)
- if extra:
- extranodes[fname] = extra
-
- return extranodes
-
-def strip(ui, repo, node, backup="all"):
- cl = repo.changelog
- # TODO delete the undo files, and handle undo of merge sets
- striprev = cl.rev(node)
-
- # Some revisions with rev > striprev may not be descendants of striprev.
- # We have to find these revisions and put them in a bundle, so that
- # we can restore them after the truncations.
- # To create the bundle we use repo.changegroupsubset which requires
- # the list of heads and bases of the set of interesting revisions.
- # (head = revision in the set that has no descendant in the set;
- # base = revision in the set that has no ancestor in the set)
- tostrip = set((striprev,))
- saveheads = set()
- savebases = []
- for r in xrange(striprev + 1, len(cl)):
- parents = cl.parentrevs(r)
- if parents[0] in tostrip or parents[1] in tostrip:
- # r is a descendant of striprev
- tostrip.add(r)
- # if this is a merge and one of the parents does not descend
- # from striprev, mark that parent as a savehead.
- if parents[1] != nullrev:
- for p in parents:
- if p not in tostrip and p > striprev:
- saveheads.add(p)
- else:
- # if no parents of this revision will be stripped, mark it as
- # a savebase
- if parents[0] < striprev and parents[1] < striprev:
- savebases.append(cl.node(r))
-
- saveheads.difference_update(parents)
- saveheads.add(r)
-
- saveheads = [cl.node(r) for r in saveheads]
- files = _collectfiles(repo, striprev)
-
- extranodes = _collectextranodes(repo, files, striprev)
-
- # create a changegroup for all the branches we need to keep
- if backup == "all":
- _bundle(repo, [node], cl.heads(), node, 'backup')
- if saveheads or extranodes:
- chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
- extranodes)
-
- mfst = repo.manifest
-
- tr = repo.transaction()
- offset = len(tr.entries)
-
- tr.startgroup()
- cl.strip(striprev, tr)
- mfst.strip(striprev, tr)
- for fn in files:
- repo.file(fn).strip(striprev, tr)
- tr.endgroup()
-
- try:
- for i in xrange(offset, len(tr.entries)):
- file, troffset, ignore = tr.entries[i]
- repo.sopener(file, 'a').truncate(troffset)
- tr.close()
- except:
- tr.abort()
- raise
-
- if saveheads or extranodes:
- ui.status(_("adding branch\n"))
- f = open(chgrpfile, "rb")
- gen = changegroup.readbundle(f, chgrpfile)
- repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True)
- f.close()
- if backup != "strip":
- os.unlink(chgrpfile)
-
- repo.destroyed()
diff --git a/sys/src/cmd/hg/mercurial/repo.py b/sys/src/cmd/hg/mercurial/repo.py
deleted file mode 100644
index 00cc1cf84..000000000
--- a/sys/src/cmd/hg/mercurial/repo.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# repo.py - repository base classes for mercurial
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import error
-
-class repository(object):
- def capable(self, name):
- '''tell whether repo supports named capability.
- return False if not supported.
- if boolean capability, return True.
- if string capability, return string.'''
- if name in self.capabilities:
- return True
- name_eq = name + '='
- for cap in self.capabilities:
- if cap.startswith(name_eq):
- return cap[len(name_eq):]
- return False
-
- def requirecap(self, name, purpose):
- '''raise an exception if the given capability is not present'''
- if not self.capable(name):
- raise error.CapabilityError(
- _('cannot %s; remote repository does not '
- 'support the %r capability') % (purpose, name))
-
- def local(self):
- return False
-
- def cancopy(self):
- return self.local()
-
- def rjoin(self, path):
- url = self.url()
- if url.endswith('/'):
- return url + path
- return url + '/' + path
diff --git a/sys/src/cmd/hg/mercurial/revlog.py b/sys/src/cmd/hg/mercurial/revlog.py
deleted file mode 100644
index 2b2d4acb9..000000000
--- a/sys/src/cmd/hg/mercurial/revlog.py
+++ /dev/null
@@ -1,1376 +0,0 @@
-# revlog.py - storage back-end for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-"""Storage back-end for Mercurial.
-
-This provides efficient delta storage with O(1) retrieve and append
-and O(changes) merge between branches.
-"""
-
-# import stuff from node for others to import from revlog
-from node import bin, hex, nullid, nullrev, short #@UnusedImport
-from i18n import _
-import changegroup, ancestor, mdiff, parsers, error, util
-import struct, zlib, errno
-
-_pack = struct.pack
-_unpack = struct.unpack
-_compress = zlib.compress
-_decompress = zlib.decompress
-_sha = util.sha1
-
-# revlog flags
-REVLOGV0 = 0
-REVLOGNG = 1
-REVLOGNGINLINEDATA = (1 << 16)
-REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
-REVLOG_DEFAULT_FORMAT = REVLOGNG
-REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
-
-_prereadsize = 1048576
-
-RevlogError = error.RevlogError
-LookupError = error.LookupError
-
-def getoffset(q):
- return int(q >> 16)
-
-def gettype(q):
- return int(q & 0xFFFF)
-
-def offset_type(offset, type):
- return long(long(offset) << 16 | type)
-
-nullhash = _sha(nullid)
-
-def hash(text, p1, p2):
- """generate a hash from the given text and its parent hashes
-
- This hash combines both the current file contents and its history
- in a manner that makes it easy to distinguish nodes with the same
- content in the revision graph.
- """
- # As of now, if one of the parent node is null, p2 is null
- if p2 == nullid:
- # deep copy of a hash is faster than creating one
- s = nullhash.copy()
- s.update(p1)
- else:
- # none of the parent nodes are nullid
- l = [p1, p2]
- l.sort()
- s = _sha(l[0])
- s.update(l[1])
- s.update(text)
- return s.digest()
-
-def compress(text):
- """ generate a possibly-compressed representation of text """
- if not text:
- return ("", text)
- l = len(text)
- bin = None
- if l < 44:
- pass
- elif l > 1000000:
- # zlib makes an internal copy, thus doubling memory usage for
- # large files, so lets do this in pieces
- z = zlib.compressobj()
- p = []
- pos = 0
- while pos < l:
- pos2 = pos + 2**20
- p.append(z.compress(text[pos:pos2]))
- pos = pos2
- p.append(z.flush())
- if sum(map(len, p)) < l:
- bin = "".join(p)
- else:
- bin = _compress(text)
- if bin is None or len(bin) > l:
- if text[0] == '\0':
- return ("", text)
- return ('u', text)
- return ("", bin)
-
-def decompress(bin):
- """ decompress the given input """
- if not bin:
- return bin
- t = bin[0]
- if t == '\0':
- return bin
- if t == 'x':
- return _decompress(bin)
- if t == 'u':
- return bin[1:]
- raise RevlogError(_("unknown compression type %r") % t)
-
-class lazyparser(object):
- """
- this class avoids the need to parse the entirety of large indices
- """
-
- # lazyparser is not safe to use on windows if win32 extensions not
- # available. it keeps file handle open, which make it not possible
- # to break hardlinks on local cloned repos.
-
- def __init__(self, dataf):
- try:
- size = util.fstat(dataf).st_size
- except AttributeError:
- size = 0
- self.dataf = dataf
- self.s = struct.calcsize(indexformatng)
- self.datasize = size
- self.l = size/self.s
- self.index = [None] * self.l
- self.map = {nullid: nullrev}
- self.allmap = 0
- self.all = 0
- self.mapfind_count = 0
-
- def loadmap(self):
- """
- during a commit, we need to make sure the rev being added is
- not a duplicate. This requires loading the entire index,
- which is fairly slow. loadmap can load up just the node map,
- which takes much less time.
- """
- if self.allmap:
- return
- end = self.datasize
- self.allmap = 1
- cur = 0
- count = 0
- blocksize = self.s * 256
- self.dataf.seek(0)
- while cur < end:
- data = self.dataf.read(blocksize)
- off = 0
- for x in xrange(256):
- n = data[off + ngshaoffset:off + ngshaoffset + 20]
- self.map[n] = count
- count += 1
- if count >= self.l:
- break
- off += self.s
- cur += blocksize
-
- def loadblock(self, blockstart, blocksize, data=None):
- if self.all:
- return
- if data is None:
- self.dataf.seek(blockstart)
- if blockstart + blocksize > self.datasize:
- # the revlog may have grown since we've started running,
- # but we don't have space in self.index for more entries.
- # limit blocksize so that we don't get too much data.
- blocksize = max(self.datasize - blockstart, 0)
- data = self.dataf.read(blocksize)
- lend = len(data) / self.s
- i = blockstart / self.s
- off = 0
- # lazyindex supports __delitem__
- if lend > len(self.index) - i:
- lend = len(self.index) - i
- for x in xrange(lend):
- if self.index[i + x] is None:
- b = data[off : off + self.s]
- self.index[i + x] = b
- n = b[ngshaoffset:ngshaoffset + 20]
- self.map[n] = i + x
- off += self.s
-
- def findnode(self, node):
- """search backwards through the index file for a specific node"""
- if self.allmap:
- return None
-
- # hg log will cause many many searches for the manifest
- # nodes. After we get called a few times, just load the whole
- # thing.
- if self.mapfind_count > 8:
- self.loadmap()
- if node in self.map:
- return node
- return None
- self.mapfind_count += 1
- last = self.l - 1
- while self.index[last] != None:
- if last == 0:
- self.all = 1
- self.allmap = 1
- return None
- last -= 1
- end = (last + 1) * self.s
- blocksize = self.s * 256
- while end >= 0:
- start = max(end - blocksize, 0)
- self.dataf.seek(start)
- data = self.dataf.read(end - start)
- findend = end - start
- while True:
- # we're searching backwards, so we have to make sure
- # we don't find a changeset where this node is a parent
- off = data.find(node, 0, findend)
- findend = off
- if off >= 0:
- i = off / self.s
- off = i * self.s
- n = data[off + ngshaoffset:off + ngshaoffset + 20]
- if n == node:
- self.map[n] = i + start / self.s
- return node
- else:
- break
- end -= blocksize
- return None
-
- def loadindex(self, i=None, end=None):
- if self.all:
- return
- all = False
- if i is None:
- blockstart = 0
- blocksize = (65536 / self.s) * self.s
- end = self.datasize
- all = True
- else:
- if end:
- blockstart = i * self.s
- end = end * self.s
- blocksize = end - blockstart
- else:
- blockstart = (i & ~1023) * self.s
- blocksize = self.s * 1024
- end = blockstart + blocksize
- while blockstart < end:
- self.loadblock(blockstart, blocksize)
- blockstart += blocksize
- if all:
- self.all = True
-
-class lazyindex(object):
- """a lazy version of the index array"""
- def __init__(self, parser):
- self.p = parser
- def __len__(self):
- return len(self.p.index)
- def load(self, pos):
- if pos < 0:
- pos += len(self.p.index)
- self.p.loadindex(pos)
- return self.p.index[pos]
- def __getitem__(self, pos):
- return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
- def __setitem__(self, pos, item):
- self.p.index[pos] = _pack(indexformatng, *item)
- def __delitem__(self, pos):
- del self.p.index[pos]
- def insert(self, pos, e):
- self.p.index.insert(pos, _pack(indexformatng, *e))
- def append(self, e):
- self.p.index.append(_pack(indexformatng, *e))
-
-class lazymap(object):
- """a lazy version of the node map"""
- def __init__(self, parser):
- self.p = parser
- def load(self, key):
- n = self.p.findnode(key)
- if n is None:
- raise KeyError(key)
- def __contains__(self, key):
- if key in self.p.map:
- return True
- self.p.loadmap()
- return key in self.p.map
- def __iter__(self):
- yield nullid
- for i in xrange(self.p.l):
- ret = self.p.index[i]
- if not ret:
- self.p.loadindex(i)
- ret = self.p.index[i]
- if isinstance(ret, str):
- ret = _unpack(indexformatng, ret)
- yield ret[7]
- def __getitem__(self, key):
- try:
- return self.p.map[key]
- except KeyError:
- try:
- self.load(key)
- return self.p.map[key]
- except KeyError:
- raise KeyError("node " + hex(key))
- def __setitem__(self, key, val):
- self.p.map[key] = val
- def __delitem__(self, key):
- del self.p.map[key]
-
-indexformatv0 = ">4l20s20s20s"
-v0shaoffset = 56
-
-class revlogoldio(object):
- def __init__(self):
- self.size = struct.calcsize(indexformatv0)
-
- def parseindex(self, fp, data, inline):
- s = self.size
- index = []
- nodemap = {nullid: nullrev}
- n = off = 0
- if len(data) == _prereadsize:
- data += fp.read() # read the rest
- l = len(data)
- while off + s <= l:
- cur = data[off:off + s]
- off += s
- e = _unpack(indexformatv0, cur)
- # transform to revlogv1 format
- e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
- nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
- index.append(e2)
- nodemap[e[6]] = n
- n += 1
-
- return index, nodemap, None
-
- def packentry(self, entry, node, version, rev):
- e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
- node(entry[5]), node(entry[6]), entry[7])
- return _pack(indexformatv0, *e2)
-
-# index ng:
-# 6 bytes offset
-# 2 bytes flags
-# 4 bytes compressed length
-# 4 bytes uncompressed length
-# 4 bytes: base rev
-# 4 bytes link rev
-# 4 bytes parent 1 rev
-# 4 bytes parent 2 rev
-# 32 bytes: nodeid
-indexformatng = ">Qiiiiii20s12x"
-ngshaoffset = 32
-versionformat = ">I"
-
-class revlogio(object):
- def __init__(self):
- self.size = struct.calcsize(indexformatng)
-
- def parseindex(self, fp, data, inline):
- if len(data) == _prereadsize:
- if util.openhardlinks() and not inline:
- # big index, let's parse it on demand
- parser = lazyparser(fp)
- index = lazyindex(parser)
- nodemap = lazymap(parser)
- e = list(index[0])
- type = gettype(e[0])
- e[0] = offset_type(0, type)
- index[0] = e
- return index, nodemap, None
- else:
- data += fp.read()
-
- # call the C implementation to parse the index data
- index, nodemap, cache = parsers.parse_index(data, inline)
- return index, nodemap, cache
-
- def packentry(self, entry, node, version, rev):
- p = _pack(indexformatng, *entry)
- if rev == 0:
- p = _pack(versionformat, version) + p[4:]
- return p
-
-class revlog(object):
- """
- the underlying revision storage object
-
- A revlog consists of two parts, an index and the revision data.
-
- The index is a file with a fixed record size containing
- information on each revision, including its nodeid (hash), the
- nodeids of its parents, the position and offset of its data within
- the data file, and the revision it's based on. Finally, each entry
- contains a linkrev entry that can serve as a pointer to external
- data.
-
- The revision data itself is a linear collection of data chunks.
- Each chunk represents a revision and is usually represented as a
- delta against the previous chunk. To bound lookup time, runs of
- deltas are limited to about 2 times the length of the original
- version data. This makes retrieval of a version proportional to
- its size, or O(1) relative to the number of revisions.
-
- Both pieces of the revlog are written to in an append-only
- fashion, which means we never need to rewrite a file to insert or
- remove data, and can use some simple techniques to avoid the need
- for locking while reading.
- """
- def __init__(self, opener, indexfile):
- """
- create a revlog object
-
- opener is a function that abstracts the file opening operation
- and can be used to implement COW semantics or the like.
- """
- self.indexfile = indexfile
- self.datafile = indexfile[:-2] + ".d"
- self.opener = opener
- self._cache = None
- self._chunkcache = (0, '')
- self.nodemap = {nullid: nullrev}
- self.index = []
-
- v = REVLOG_DEFAULT_VERSION
- if hasattr(opener, "defversion"):
- v = opener.defversion
- if v & REVLOGNG:
- v |= REVLOGNGINLINEDATA
-
- i = ''
- try:
- f = self.opener(self.indexfile)
- i = f.read(_prereadsize)
- if len(i) > 0:
- v = struct.unpack(versionformat, i[:4])[0]
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
-
- self.version = v
- self._inline = v & REVLOGNGINLINEDATA
- flags = v & ~0xFFFF
- fmt = v & 0xFFFF
- if fmt == REVLOGV0 and flags:
- raise RevlogError(_("index %s unknown flags %#04x for format v0")
- % (self.indexfile, flags >> 16))
- elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
- raise RevlogError(_("index %s unknown flags %#04x for revlogng")
- % (self.indexfile, flags >> 16))
- elif fmt > REVLOGNG:
- raise RevlogError(_("index %s unknown format %d")
- % (self.indexfile, fmt))
-
- self._io = revlogio()
- if self.version == REVLOGV0:
- self._io = revlogoldio()
- if i:
- try:
- d = self._io.parseindex(f, i, self._inline)
- except (ValueError, IndexError), e:
- raise RevlogError(_("index %s is corrupted") % (self.indexfile))
- self.index, self.nodemap, self._chunkcache = d
- if not self._chunkcache:
- self._chunkclear()
-
- # add the magic null revision at -1 (if it hasn't been done already)
- if (self.index == [] or isinstance(self.index, lazyindex) or
- self.index[-1][7] != nullid) :
- self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
-
- def _loadindex(self, start, end):
- """load a block of indexes all at once from the lazy parser"""
- if isinstance(self.index, lazyindex):
- self.index.p.loadindex(start, end)
-
- def _loadindexmap(self):
- """loads both the map and the index from the lazy parser"""
- if isinstance(self.index, lazyindex):
- p = self.index.p
- p.loadindex()
- self.nodemap = p.map
-
- def _loadmap(self):
- """loads the map from the lazy parser"""
- if isinstance(self.nodemap, lazymap):
- self.nodemap.p.loadmap()
- self.nodemap = self.nodemap.p.map
-
- def tip(self):
- return self.node(len(self.index) - 2)
- def __len__(self):
- return len(self.index) - 1
- def __iter__(self):
- for i in xrange(len(self)):
- yield i
- def rev(self, node):
- try:
- return self.nodemap[node]
- except KeyError:
- raise LookupError(node, self.indexfile, _('no node'))
- def node(self, rev):
- return self.index[rev][7]
- def linkrev(self, rev):
- return self.index[rev][4]
- def parents(self, node):
- i = self.index
- d = i[self.rev(node)]
- return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
- def parentrevs(self, rev):
- return self.index[rev][5:7]
- def start(self, rev):
- return int(self.index[rev][0] >> 16)
- def end(self, rev):
- return self.start(rev) + self.length(rev)
- def length(self, rev):
- return self.index[rev][1]
- def base(self, rev):
- return self.index[rev][3]
-
- def size(self, rev):
- """return the length of the uncompressed text for a given revision"""
- l = self.index[rev][2]
- if l >= 0:
- return l
-
- t = self.revision(self.node(rev))
- return len(t)
-
- # Alternate implementation. The advantage to this code is it
- # will be faster for a single revision. However, the results
- # are not cached, so finding the size of every revision will
- # be slower.
- #
- # if self.cache and self.cache[1] == rev:
- # return len(self.cache[2])
- #
- # base = self.base(rev)
- # if self.cache and self.cache[1] >= base and self.cache[1] < rev:
- # base = self.cache[1]
- # text = self.cache[2]
- # else:
- # text = self.revision(self.node(base))
- #
- # l = len(text)
- # for x in xrange(base + 1, rev + 1):
- # l = mdiff.patchedsize(l, self._chunk(x))
- # return l
-
- def reachable(self, node, stop=None):
- """return the set of all nodes ancestral to a given node, including
- the node itself, stopping when stop is matched"""
- reachable = set((node,))
- visit = [node]
- if stop:
- stopn = self.rev(stop)
- else:
- stopn = 0
- while visit:
- n = visit.pop(0)
- if n == stop:
- continue
- if n == nullid:
- continue
- for p in self.parents(n):
- if self.rev(p) < stopn:
- continue
- if p not in reachable:
- reachable.add(p)
- visit.append(p)
- return reachable
-
- def ancestors(self, *revs):
- 'Generate the ancestors of revs using a breadth-first visit'
- visit = list(revs)
- seen = set([nullrev])
- while visit:
- for parent in self.parentrevs(visit.pop(0)):
- if parent not in seen:
- visit.append(parent)
- seen.add(parent)
- yield parent
-
- def descendants(self, *revs):
- 'Generate the descendants of revs in topological order'
- seen = set(revs)
- for i in xrange(min(revs) + 1, len(self)):
- for x in self.parentrevs(i):
- if x != nullrev and x in seen:
- seen.add(i)
- yield i
- break
-
- def findmissing(self, common=None, heads=None):
- '''
- returns the topologically sorted list of nodes from the set:
- missing = (ancestors(heads) \ ancestors(common))
-
- where ancestors() is the set of ancestors from heads, heads included
-
- if heads is None, the heads of the revlog are used
- if common is None, nullid is assumed to be a common node
- '''
- if common is None:
- common = [nullid]
- if heads is None:
- heads = self.heads()
-
- common = [self.rev(n) for n in common]
- heads = [self.rev(n) for n in heads]
-
- # we want the ancestors, but inclusive
- has = set(self.ancestors(*common))
- has.add(nullrev)
- has.update(common)
-
- # take all ancestors from heads that aren't in has
- missing = set()
- visit = [r for r in heads if r not in has]
- while visit:
- r = visit.pop(0)
- if r in missing:
- continue
- else:
- missing.add(r)
- for p in self.parentrevs(r):
- if p not in has:
- visit.append(p)
- missing = list(missing)
- missing.sort()
- return [self.node(r) for r in missing]
-
- def nodesbetween(self, roots=None, heads=None):
- """Return a tuple containing three elements. Elements 1 and 2 contain
- a final list bases and heads after all the unreachable ones have been
- pruned. Element 0 contains a topologically sorted list of all
-
- nodes that satisfy these constraints:
- 1. All nodes must be descended from a node in roots (the nodes on
- roots are considered descended from themselves).
- 2. All nodes must also be ancestors of a node in heads (the nodes in
- heads are considered to be their own ancestors).
-
- If roots is unspecified, nullid is assumed as the only root.
- If heads is unspecified, it is taken to be the output of the
- heads method (i.e. a list of all nodes in the repository that
- have no children)."""
- nonodes = ([], [], [])
- if roots is not None:
- roots = list(roots)
- if not roots:
- return nonodes
- lowestrev = min([self.rev(n) for n in roots])
- else:
- roots = [nullid] # Everybody's a descendent of nullid
- lowestrev = nullrev
- if (lowestrev == nullrev) and (heads is None):
- # We want _all_ the nodes!
- return ([self.node(r) for r in self], [nullid], list(self.heads()))
- if heads is None:
- # All nodes are ancestors, so the latest ancestor is the last
- # node.
- highestrev = len(self) - 1
- # Set ancestors to None to signal that every node is an ancestor.
- ancestors = None
- # Set heads to an empty dictionary for later discovery of heads
- heads = {}
- else:
- heads = list(heads)
- if not heads:
- return nonodes
- ancestors = set()
- # Turn heads into a dictionary so we can remove 'fake' heads.
- # Also, later we will be using it to filter out the heads we can't
- # find from roots.
- heads = dict.fromkeys(heads, 0)
- # Start at the top and keep marking parents until we're done.
- nodestotag = set(heads)
- # Remember where the top was so we can use it as a limit later.
- highestrev = max([self.rev(n) for n in nodestotag])
- while nodestotag:
- # grab a node to tag
- n = nodestotag.pop()
- # Never tag nullid
- if n == nullid:
- continue
- # A node's revision number represents its place in a
- # topologically sorted list of nodes.
- r = self.rev(n)
- if r >= lowestrev:
- if n not in ancestors:
- # If we are possibly a descendent of one of the roots
- # and we haven't already been marked as an ancestor
- ancestors.add(n) # Mark as ancestor
- # Add non-nullid parents to list of nodes to tag.
- nodestotag.update([p for p in self.parents(n) if
- p != nullid])
- elif n in heads: # We've seen it before, is it a fake head?
- # So it is, real heads should not be the ancestors of
- # any other heads.
- heads.pop(n)
- if not ancestors:
- return nonodes
- # Now that we have our set of ancestors, we want to remove any
- # roots that are not ancestors.
-
- # If one of the roots was nullid, everything is included anyway.
- if lowestrev > nullrev:
- # But, since we weren't, let's recompute the lowest rev to not
- # include roots that aren't ancestors.
-
- # Filter out roots that aren't ancestors of heads
- roots = [n for n in roots if n in ancestors]
- # Recompute the lowest revision
- if roots:
- lowestrev = min([self.rev(n) for n in roots])
- else:
- # No more roots? Return empty list
- return nonodes
- else:
- # We are descending from nullid, and don't need to care about
- # any other roots.
- lowestrev = nullrev
- roots = [nullid]
- # Transform our roots list into a set.
- descendents = set(roots)
- # Also, keep the original roots so we can filter out roots that aren't
- # 'real' roots (i.e. are descended from other roots).
- roots = descendents.copy()
- # Our topologically sorted list of output nodes.
- orderedout = []
- # Don't start at nullid since we don't want nullid in our output list,
- # and if nullid shows up in descedents, empty parents will look like
- # they're descendents.
- for r in xrange(max(lowestrev, 0), highestrev + 1):
- n = self.node(r)
- isdescendent = False
- if lowestrev == nullrev: # Everybody is a descendent of nullid
- isdescendent = True
- elif n in descendents:
- # n is already a descendent
- isdescendent = True
- # This check only needs to be done here because all the roots
- # will start being marked is descendents before the loop.
- if n in roots:
- # If n was a root, check if it's a 'real' root.
- p = tuple(self.parents(n))
- # If any of its parents are descendents, it's not a root.
- if (p[0] in descendents) or (p[1] in descendents):
- roots.remove(n)
- else:
- p = tuple(self.parents(n))
- # A node is a descendent if either of its parents are
- # descendents. (We seeded the dependents list with the roots
- # up there, remember?)
- if (p[0] in descendents) or (p[1] in descendents):
- descendents.add(n)
- isdescendent = True
- if isdescendent and ((ancestors is None) or (n in ancestors)):
- # Only include nodes that are both descendents and ancestors.
- orderedout.append(n)
- if (ancestors is not None) and (n in heads):
- # We're trying to figure out which heads are reachable
- # from roots.
- # Mark this head as having been reached
- heads[n] = 1
- elif ancestors is None:
- # Otherwise, we're trying to discover the heads.
- # Assume this is a head because if it isn't, the next step
- # will eventually remove it.
- heads[n] = 1
- # But, obviously its parents aren't.
- for p in self.parents(n):
- heads.pop(p, None)
- heads = [n for n in heads.iterkeys() if heads[n] != 0]
- roots = list(roots)
- assert orderedout
- assert roots
- assert heads
- return (orderedout, roots, heads)
-
- def heads(self, start=None, stop=None):
- """return the list of all nodes that have no children
-
- if start is specified, only heads that are descendants of
- start will be returned
- if stop is specified, it will consider all the revs from stop
- as if they had no children
- """
- if start is None and stop is None:
- count = len(self)
- if not count:
- return [nullid]
- ishead = [1] * (count + 1)
- index = self.index
- for r in xrange(count):
- e = index[r]
- ishead[e[5]] = ishead[e[6]] = 0
- return [self.node(r) for r in xrange(count) if ishead[r]]
-
- if start is None:
- start = nullid
- if stop is None:
- stop = []
- stoprevs = set([self.rev(n) for n in stop])
- startrev = self.rev(start)
- reachable = set((startrev,))
- heads = set((startrev,))
-
- parentrevs = self.parentrevs
- for r in xrange(startrev + 1, len(self)):
- for p in parentrevs(r):
- if p in reachable:
- if r not in stoprevs:
- reachable.add(r)
- heads.add(r)
- if p in heads and p not in stoprevs:
- heads.remove(p)
-
- return [self.node(r) for r in heads]
-
- def children(self, node):
- """find the children of a given node"""
- c = []
- p = self.rev(node)
- for r in range(p + 1, len(self)):
- prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
- if prevs:
- for pr in prevs:
- if pr == p:
- c.append(self.node(r))
- elif p == nullrev:
- c.append(self.node(r))
- return c
-
- def _match(self, id):
- if isinstance(id, (long, int)):
- # rev
- return self.node(id)
- if len(id) == 20:
- # possibly a binary node
- # odds of a binary node being all hex in ASCII are 1 in 10**25
- try:
- node = id
- self.rev(node) # quick search the index
- return node
- except LookupError:
- pass # may be partial hex id
- try:
- # str(rev)
- rev = int(id)
- if str(rev) != id:
- raise ValueError
- if rev < 0:
- rev = len(self) + rev
- if rev < 0 or rev >= len(self):
- raise ValueError
- return self.node(rev)
- except (ValueError, OverflowError):
- pass
- if len(id) == 40:
- try:
- # a full hex nodeid?
- node = bin(id)
- self.rev(node)
- return node
- except (TypeError, LookupError):
- pass
-
- def _partialmatch(self, id):
- if len(id) < 40:
- try:
- # hex(node)[:...]
- l = len(id) // 2 # grab an even number of digits
- bin_id = bin(id[:l*2])
- nl = [n for n in self.nodemap if n[:l] == bin_id]
- nl = [n for n in nl if hex(n).startswith(id)]
- if len(nl) > 0:
- if len(nl) == 1:
- return nl[0]
- raise LookupError(id, self.indexfile,
- _('ambiguous identifier'))
- return None
- except TypeError:
- pass
-
- def lookup(self, id):
- """locate a node based on:
- - revision number or str(revision number)
- - nodeid or subset of hex nodeid
- """
- n = self._match(id)
- if n is not None:
- return n
- n = self._partialmatch(id)
- if n:
- return n
-
- raise LookupError(id, self.indexfile, _('no match found'))
-
- def cmp(self, node, text):
- """compare text with a given file revision"""
- p1, p2 = self.parents(node)
- return hash(text, p1, p2) != node
-
- def _addchunk(self, offset, data):
- o, d = self._chunkcache
- # try to add to existing cache
- if o + len(d) == offset and len(d) + len(data) < _prereadsize:
- self._chunkcache = o, d + data
- else:
- self._chunkcache = offset, data
-
- def _loadchunk(self, offset, length):
- if self._inline:
- df = self.opener(self.indexfile)
- else:
- df = self.opener(self.datafile)
-
- readahead = max(65536, length)
- df.seek(offset)
- d = df.read(readahead)
- self._addchunk(offset, d)
- if readahead > length:
- return d[:length]
- return d
-
- def _getchunk(self, offset, length):
- o, d = self._chunkcache
- l = len(d)
-
- # is it in the cache?
- cachestart = offset - o
- cacheend = cachestart + length
- if cachestart >= 0 and cacheend <= l:
- if cachestart == 0 and cacheend == l:
- return d # avoid a copy
- return d[cachestart:cacheend]
-
- return self._loadchunk(offset, length)
-
- def _chunkraw(self, startrev, endrev):
- start = self.start(startrev)
- length = self.end(endrev) - start
- if self._inline:
- start += (startrev + 1) * self._io.size
- return self._getchunk(start, length)
-
- def _chunk(self, rev):
- return decompress(self._chunkraw(rev, rev))
-
- def _chunkclear(self):
- self._chunkcache = (0, '')
-
- def revdiff(self, rev1, rev2):
- """return or calculate a delta between two revisions"""
- if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
- return self._chunk(rev2)
-
- return mdiff.textdiff(self.revision(self.node(rev1)),
- self.revision(self.node(rev2)))
-
- def revision(self, node):
- """return an uncompressed revision of a given node"""
- if node == nullid:
- return ""
- if self._cache and self._cache[0] == node:
- return str(self._cache[2])
-
- # look up what we need to read
- text = None
- rev = self.rev(node)
- base = self.base(rev)
-
- # check rev flags
- if self.index[rev][0] & 0xFFFF:
- raise RevlogError(_('incompatible revision flag %x') %
- (self.index[rev][0] & 0xFFFF))
-
- # do we have useful data cached?
- if self._cache and self._cache[1] >= base and self._cache[1] < rev:
- base = self._cache[1]
- text = str(self._cache[2])
-
- self._loadindex(base, rev + 1)
- self._chunkraw(base, rev)
- if text is None:
- text = self._chunk(base)
-
- bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
- text = mdiff.patches(text, bins)
- p1, p2 = self.parents(node)
- if node != hash(text, p1, p2):
- raise RevlogError(_("integrity check failed on %s:%d")
- % (self.indexfile, rev))
-
- self._cache = (node, rev, text)
- return text
-
- def checkinlinesize(self, tr, fp=None):
- if not self._inline or (self.start(-2) + self.length(-2)) < 131072:
- return
-
- trinfo = tr.find(self.indexfile)
- if trinfo is None:
- raise RevlogError(_("%s not found in the transaction")
- % self.indexfile)
-
- trindex = trinfo[2]
- dataoff = self.start(trindex)
-
- tr.add(self.datafile, dataoff)
-
- if fp:
- fp.flush()
- fp.close()
-
- df = self.opener(self.datafile, 'w')
- try:
- for r in self:
- df.write(self._chunkraw(r, r))
- finally:
- df.close()
-
- fp = self.opener(self.indexfile, 'w', atomictemp=True)
- self.version &= ~(REVLOGNGINLINEDATA)
- self._inline = False
- for i in self:
- e = self._io.packentry(self.index[i], self.node, self.version, i)
- fp.write(e)
-
- # if we don't call rename, the temp file will never replace the
- # real index
- fp.rename()
-
- tr.replace(self.indexfile, trindex * self._io.size)
- self._chunkclear()
-
- def addrevision(self, text, transaction, link, p1, p2, d=None):
- """add a revision to the log
-
- text - the revision data to add
- transaction - the transaction object used for rollback
- link - the linkrev data to add
- p1, p2 - the parent nodeids of the revision
- d - an optional precomputed delta
- """
- dfh = None
- if not self._inline:
- dfh = self.opener(self.datafile, "a")
- ifh = self.opener(self.indexfile, "a+")
- try:
- return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
- finally:
- if dfh:
- dfh.close()
- ifh.close()
-
- def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
- node = hash(text, p1, p2)
- if node in self.nodemap:
- return node
-
- curr = len(self)
- prev = curr - 1
- base = self.base(prev)
- offset = self.end(prev)
-
- if curr:
- if not d:
- ptext = self.revision(self.node(prev))
- d = mdiff.textdiff(ptext, text)
- data = compress(d)
- l = len(data[1]) + len(data[0])
- dist = l + offset - self.start(base)
-
- # full versions are inserted when the needed deltas
- # become comparable to the uncompressed text
- if not curr or dist > len(text) * 2:
- data = compress(text)
- l = len(data[1]) + len(data[0])
- base = curr
-
- e = (offset_type(offset, 0), l, len(text),
- base, link, self.rev(p1), self.rev(p2), node)
- self.index.insert(-1, e)
- self.nodemap[node] = curr
-
- entry = self._io.packentry(e, self.node, self.version, curr)
- if not self._inline:
- transaction.add(self.datafile, offset)
- transaction.add(self.indexfile, curr * len(entry))
- if data[0]:
- dfh.write(data[0])
- dfh.write(data[1])
- dfh.flush()
- ifh.write(entry)
- else:
- offset += curr * self._io.size
- transaction.add(self.indexfile, offset, curr)
- ifh.write(entry)
- ifh.write(data[0])
- ifh.write(data[1])
- self.checkinlinesize(transaction, ifh)
-
- self._cache = (node, curr, text)
- return node
-
- def ancestor(self, a, b):
- """calculate the least common ancestor of nodes a and b"""
-
- def parents(rev):
- return [p for p in self.parentrevs(rev) if p != nullrev]
-
- c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
- if c is None:
- return nullid
-
- return self.node(c)
-
- def group(self, nodelist, lookup, infocollect=None):
- """calculate a delta group
-
- Given a list of changeset revs, return a set of deltas and
- metadata corresponding to nodes. the first delta is
- parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
- have this parent as it has all history before these
- changesets. parent is parent[0]
- """
-
- revs = [self.rev(n) for n in nodelist]
-
- # if we don't have any revisions touched by these changesets, bail
- if not revs:
- yield changegroup.closechunk()
- return
-
- # add the parent of the first rev
- p = self.parentrevs(revs[0])[0]
- revs.insert(0, p)
-
- # build deltas
- for d in xrange(len(revs) - 1):
- a, b = revs[d], revs[d + 1]
- nb = self.node(b)
-
- if infocollect is not None:
- infocollect(nb)
-
- p = self.parents(nb)
- meta = nb + p[0] + p[1] + lookup(nb)
- if a == -1:
- d = self.revision(nb)
- meta += mdiff.trivialdiffheader(len(d))
- else:
- d = self.revdiff(a, b)
- yield changegroup.chunkheader(len(meta) + len(d))
- yield meta
- if len(d) > 2**20:
- pos = 0
- while pos < len(d):
- pos2 = pos + 2 ** 18
- yield d[pos:pos2]
- pos = pos2
- else:
- yield d
-
- yield changegroup.closechunk()
-
- def addgroup(self, revs, linkmapper, transaction):
- """
- add a delta group
-
- given a set of deltas, add them to the revision log. the
- first delta is against its parent, which should be in our
- log, the rest are against the previous delta.
- """
-
- #track the base of the current delta log
- r = len(self)
- t = r - 1
- node = None
-
- base = prev = nullrev
- start = end = textlen = 0
- if r:
- end = self.end(t)
-
- ifh = self.opener(self.indexfile, "a+")
- isize = r * self._io.size
- if self._inline:
- transaction.add(self.indexfile, end + isize, r)
- dfh = None
- else:
- transaction.add(self.indexfile, isize, r)
- transaction.add(self.datafile, end)
- dfh = self.opener(self.datafile, "a")
-
- try:
- # loop through our set of deltas
- chain = None
- for chunk in revs:
- node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
- link = linkmapper(cs)
- if node in self.nodemap:
- # this can happen if two branches make the same change
- chain = node
- continue
- delta = buffer(chunk, 80)
- del chunk
-
- for p in (p1, p2):
- if not p in self.nodemap:
- raise LookupError(p, self.indexfile, _('unknown parent'))
-
- if not chain:
- # retrieve the parent revision of the delta chain
- chain = p1
- if not chain in self.nodemap:
- raise LookupError(chain, self.indexfile, _('unknown base'))
-
- # full versions are inserted when the needed deltas become
- # comparable to the uncompressed text or when the previous
- # version is not the one we have a delta against. We use
- # the size of the previous full rev as a proxy for the
- # current size.
-
- if chain == prev:
- cdelta = compress(delta)
- cdeltalen = len(cdelta[0]) + len(cdelta[1])
- textlen = mdiff.patchedsize(textlen, delta)
-
- if chain != prev or (end - start + cdeltalen) > textlen * 2:
- # flush our writes here so we can read it in revision
- if dfh:
- dfh.flush()
- ifh.flush()
- text = self.revision(chain)
- if len(text) == 0:
- # skip over trivial delta header
- text = buffer(delta, 12)
- else:
- text = mdiff.patches(text, [delta])
- del delta
- chk = self._addrevision(text, transaction, link, p1, p2, None,
- ifh, dfh)
- if not dfh and not self._inline:
- # addrevision switched from inline to conventional
- # reopen the index
- dfh = self.opener(self.datafile, "a")
- ifh = self.opener(self.indexfile, "a")
- if chk != node:
- raise RevlogError(_("consistency error adding group"))
- textlen = len(text)
- else:
- e = (offset_type(end, 0), cdeltalen, textlen, base,
- link, self.rev(p1), self.rev(p2), node)
- self.index.insert(-1, e)
- self.nodemap[node] = r
- entry = self._io.packentry(e, self.node, self.version, r)
- if self._inline:
- ifh.write(entry)
- ifh.write(cdelta[0])
- ifh.write(cdelta[1])
- self.checkinlinesize(transaction, ifh)
- if not self._inline:
- dfh = self.opener(self.datafile, "a")
- ifh = self.opener(self.indexfile, "a")
- else:
- dfh.write(cdelta[0])
- dfh.write(cdelta[1])
- ifh.write(entry)
-
- t, r, chain, prev = r, r + 1, node, node
- base = self.base(t)
- start = self.start(base)
- end = self.end(t)
- finally:
- if dfh:
- dfh.close()
- ifh.close()
-
- return node
-
- def strip(self, minlink, transaction):
- """truncate the revlog on the first revision with a linkrev >= minlink
-
- This function is called when we're stripping revision minlink and
- its descendants from the repository.
-
- We have to remove all revisions with linkrev >= minlink, because
- the equivalent changelog revisions will be renumbered after the
- strip.
-
- So we truncate the revlog on the first of these revisions, and
- trust that the caller has saved the revisions that shouldn't be
- removed and that it'll readd them after this truncation.
- """
- if len(self) == 0:
- return
-
- if isinstance(self.index, lazyindex):
- self._loadindexmap()
-
- for rev in self:
- if self.index[rev][4] >= minlink:
- break
- else:
- return
-
- # first truncate the files on disk
- end = self.start(rev)
- if not self._inline:
- transaction.add(self.datafile, end)
- end = rev * self._io.size
- else:
- end += rev * self._io.size
-
- transaction.add(self.indexfile, end)
-
- # then reset internal state in memory to forget those revisions
- self._cache = None
- self._chunkclear()
- for x in xrange(rev, len(self)):
- del self.nodemap[self.node(x)]
-
- del self.index[rev:-1]
-
- def checksize(self):
- expected = 0
- if len(self):
- expected = max(0, self.end(len(self) - 1))
-
- try:
- f = self.opener(self.datafile)
- f.seek(0, 2)
- actual = f.tell()
- dd = actual - expected
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
- dd = 0
-
- try:
- f = self.opener(self.indexfile)
- f.seek(0, 2)
- actual = f.tell()
- s = self._io.size
- i = max(0, actual // s)
- di = actual - (i * s)
- if self._inline:
- databytes = 0
- for r in self:
- databytes += max(0, self.length(r))
- dd = 0
- di = actual - len(self) * s - databytes
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
- di = 0
-
- return (dd, di)
-
- def files(self):
- res = [ self.indexfile ]
- if not self._inline:
- res.append(self.datafile)
- return res
diff --git a/sys/src/cmd/hg/mercurial/simplemerge.py b/sys/src/cmd/hg/mercurial/simplemerge.py
deleted file mode 100644
index d876b47b9..000000000
--- a/sys/src/cmd/hg/mercurial/simplemerge.py
+++ /dev/null
@@ -1,451 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) 2004, 2005 Canonical Ltd
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-# mbp: "you know that thing where cvs gives you conflict markers?"
-# s: "i hate that."
-
-from i18n import _
-import util, mdiff
-import sys, os
-
-class CantReprocessAndShowBase(Exception):
- pass
-
-def intersect(ra, rb):
- """Given two ranges return the range where they intersect or None.
-
- >>> intersect((0, 10), (0, 6))
- (0, 6)
- >>> intersect((0, 10), (5, 15))
- (5, 10)
- >>> intersect((0, 10), (10, 15))
- >>> intersect((0, 9), (10, 15))
- >>> intersect((0, 9), (7, 15))
- (7, 9)
- """
- assert ra[0] <= ra[1]
- assert rb[0] <= rb[1]
-
- sa = max(ra[0], rb[0])
- sb = min(ra[1], rb[1])
- if sa < sb:
- return sa, sb
- else:
- return None
-
-def compare_range(a, astart, aend, b, bstart, bend):
- """Compare a[astart:aend] == b[bstart:bend], without slicing.
- """
- if (aend-astart) != (bend-bstart):
- return False
- for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
- if a[ia] != b[ib]:
- return False
- else:
- return True
-
-class Merge3Text(object):
- """3-way merge of texts.
-
- Given strings BASE, OTHER, THIS, tries to produce a combined text
- incorporating the changes from both BASE->OTHER and BASE->THIS."""
- def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
- self.basetext = basetext
- self.atext = atext
- self.btext = btext
- if base is None:
- base = mdiff.splitnewlines(basetext)
- if a is None:
- a = mdiff.splitnewlines(atext)
- if b is None:
- b = mdiff.splitnewlines(btext)
- self.base = base
- self.a = a
- self.b = b
-
- def merge_lines(self,
- name_a=None,
- name_b=None,
- name_base=None,
- start_marker='<<<<<<<',
- mid_marker='=======',
- end_marker='>>>>>>>',
- base_marker=None,
- reprocess=False):
- """Return merge in cvs-like form.
- """
- self.conflicts = False
- newline = '\n'
- if len(self.a) > 0:
- if self.a[0].endswith('\r\n'):
- newline = '\r\n'
- elif self.a[0].endswith('\r'):
- newline = '\r'
- if base_marker and reprocess:
- raise CantReprocessAndShowBase()
- if name_a:
- start_marker = start_marker + ' ' + name_a
- if name_b:
- end_marker = end_marker + ' ' + name_b
- if name_base and base_marker:
- base_marker = base_marker + ' ' + name_base
- merge_regions = self.merge_regions()
- if reprocess is True:
- merge_regions = self.reprocess_merge_regions(merge_regions)
- for t in merge_regions:
- what = t[0]
- if what == 'unchanged':
- for i in range(t[1], t[2]):
- yield self.base[i]
- elif what == 'a' or what == 'same':
- for i in range(t[1], t[2]):
- yield self.a[i]
- elif what == 'b':
- for i in range(t[1], t[2]):
- yield self.b[i]
- elif what == 'conflict':
- self.conflicts = True
- yield start_marker + newline
- for i in range(t[3], t[4]):
- yield self.a[i]
- if base_marker is not None:
- yield base_marker + newline
- for i in range(t[1], t[2]):
- yield self.base[i]
- yield mid_marker + newline
- for i in range(t[5], t[6]):
- yield self.b[i]
- yield end_marker + newline
- else:
- raise ValueError(what)
-
- def merge_annotated(self):
- """Return merge with conflicts, showing origin of lines.
-
- Most useful for debugging merge.
- """
- for t in self.merge_regions():
- what = t[0]
- if what == 'unchanged':
- for i in range(t[1], t[2]):
- yield 'u | ' + self.base[i]
- elif what == 'a' or what == 'same':
- for i in range(t[1], t[2]):
- yield what[0] + ' | ' + self.a[i]
- elif what == 'b':
- for i in range(t[1], t[2]):
- yield 'b | ' + self.b[i]
- elif what == 'conflict':
- yield '<<<<\n'
- for i in range(t[3], t[4]):
- yield 'A | ' + self.a[i]
- yield '----\n'
- for i in range(t[5], t[6]):
- yield 'B | ' + self.b[i]
- yield '>>>>\n'
- else:
- raise ValueError(what)
-
- def merge_groups(self):
- """Yield sequence of line groups. Each one is a tuple:
-
- 'unchanged', lines
- Lines unchanged from base
-
- 'a', lines
- Lines taken from a
-
- 'same', lines
- Lines taken from a (and equal to b)
-
- 'b', lines
- Lines taken from b
-
- 'conflict', base_lines, a_lines, b_lines
- Lines from base were changed to either a or b and conflict.
- """
- for t in self.merge_regions():
- what = t[0]
- if what == 'unchanged':
- yield what, self.base[t[1]:t[2]]
- elif what == 'a' or what == 'same':
- yield what, self.a[t[1]:t[2]]
- elif what == 'b':
- yield what, self.b[t[1]:t[2]]
- elif what == 'conflict':
- yield (what,
- self.base[t[1]:t[2]],
- self.a[t[3]:t[4]],
- self.b[t[5]:t[6]])
- else:
- raise ValueError(what)
-
- def merge_regions(self):
- """Return sequences of matching and conflicting regions.
-
- This returns tuples, where the first value says what kind we
- have:
-
- 'unchanged', start, end
- Take a region of base[start:end]
-
- 'same', astart, aend
- b and a are different from base but give the same result
-
- 'a', start, end
- Non-clashing insertion from a[start:end]
-
- Method is as follows:
-
- The two sequences align only on regions which match the base
- and both descendents. These are found by doing a two-way diff
- of each one against the base, and then finding the
- intersections between those regions. These "sync regions"
- are by definition unchanged in both and easily dealt with.
-
- The regions in between can be in any of three cases:
- conflicted, or changed on only one side.
- """
-
- # section a[0:ia] has been disposed of, etc
- iz = ia = ib = 0
-
- for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
- #print 'match base [%d:%d]' % (zmatch, zend)
-
- matchlen = zend - zmatch
- assert matchlen >= 0
- assert matchlen == (aend - amatch)
- assert matchlen == (bend - bmatch)
-
- len_a = amatch - ia
- len_b = bmatch - ib
- len_base = zmatch - iz
- assert len_a >= 0
- assert len_b >= 0
- assert len_base >= 0
-
- #print 'unmatched a=%d, b=%d' % (len_a, len_b)
-
- if len_a or len_b:
- # try to avoid actually slicing the lists
- equal_a = compare_range(self.a, ia, amatch,
- self.base, iz, zmatch)
- equal_b = compare_range(self.b, ib, bmatch,
- self.base, iz, zmatch)
- same = compare_range(self.a, ia, amatch,
- self.b, ib, bmatch)
-
- if same:
- yield 'same', ia, amatch
- elif equal_a and not equal_b:
- yield 'b', ib, bmatch
- elif equal_b and not equal_a:
- yield 'a', ia, amatch
- elif not equal_a and not equal_b:
- yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
- else:
- raise AssertionError("can't handle a=b=base but unmatched")
-
- ia = amatch
- ib = bmatch
- iz = zmatch
-
- # if the same part of the base was deleted on both sides
- # that's OK, we can just skip it.
-
-
- if matchlen > 0:
- assert ia == amatch
- assert ib == bmatch
- assert iz == zmatch
-
- yield 'unchanged', zmatch, zend
- iz = zend
- ia = aend
- ib = bend
-
- def reprocess_merge_regions(self, merge_regions):
- """Where there are conflict regions, remove the agreed lines.
-
- Lines where both A and B have made the same changes are
- eliminated.
- """
- for region in merge_regions:
- if region[0] != "conflict":
- yield region
- continue
- type, iz, zmatch, ia, amatch, ib, bmatch = region
- a_region = self.a[ia:amatch]
- b_region = self.b[ib:bmatch]
- matches = mdiff.get_matching_blocks(''.join(a_region),
- ''.join(b_region))
- next_a = ia
- next_b = ib
- for region_ia, region_ib, region_len in matches[:-1]:
- region_ia += ia
- region_ib += ib
- reg = self.mismatch_region(next_a, region_ia, next_b,
- region_ib)
- if reg is not None:
- yield reg
- yield 'same', region_ia, region_len+region_ia
- next_a = region_ia + region_len
- next_b = region_ib + region_len
- reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
- if reg is not None:
- yield reg
-
- def mismatch_region(next_a, region_ia, next_b, region_ib):
- if next_a < region_ia or next_b < region_ib:
- return 'conflict', None, None, next_a, region_ia, next_b, region_ib
- mismatch_region = staticmethod(mismatch_region)
-
- def find_sync_regions(self):
- """Return a list of sync regions, where both descendents match the base.
-
- Generates a list of (base1, base2, a1, a2, b1, b2). There is
- always a zero-length sync region at the end of all the files.
- """
-
- ia = ib = 0
- amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
- bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
- len_a = len(amatches)
- len_b = len(bmatches)
-
- sl = []
-
- while ia < len_a and ib < len_b:
- abase, amatch, alen = amatches[ia]
- bbase, bmatch, blen = bmatches[ib]
-
- # there is an unconflicted block at i; how long does it
- # extend? until whichever one ends earlier.
- i = intersect((abase, abase+alen), (bbase, bbase+blen))
- if i:
- intbase = i[0]
- intend = i[1]
- intlen = intend - intbase
-
- # found a match of base[i[0], i[1]]; this may be less than
- # the region that matches in either one
- assert intlen <= alen
- assert intlen <= blen
- assert abase <= intbase
- assert bbase <= intbase
-
- asub = amatch + (intbase - abase)
- bsub = bmatch + (intbase - bbase)
- aend = asub + intlen
- bend = bsub + intlen
-
- assert self.base[intbase:intend] == self.a[asub:aend], \
- (self.base[intbase:intend], self.a[asub:aend])
-
- assert self.base[intbase:intend] == self.b[bsub:bend]
-
- sl.append((intbase, intend,
- asub, aend,
- bsub, bend))
-
- # advance whichever one ends first in the base text
- if (abase + alen) < (bbase + blen):
- ia += 1
- else:
- ib += 1
-
- intbase = len(self.base)
- abase = len(self.a)
- bbase = len(self.b)
- sl.append((intbase, intbase, abase, abase, bbase, bbase))
-
- return sl
-
- def find_unconflicted(self):
- """Return a list of ranges in base that are not conflicted."""
- am = mdiff.get_matching_blocks(self.basetext, self.atext)
- bm = mdiff.get_matching_blocks(self.basetext, self.btext)
-
- unc = []
-
- while am and bm:
- # there is an unconflicted block at i; how long does it
- # extend? until whichever one ends earlier.
- a1 = am[0][0]
- a2 = a1 + am[0][2]
- b1 = bm[0][0]
- b2 = b1 + bm[0][2]
- i = intersect((a1, a2), (b1, b2))
- if i:
- unc.append(i)
-
- if a2 < b2:
- del am[0]
- else:
- del bm[0]
-
- return unc
-
-def simplemerge(ui, local, base, other, **opts):
- def readfile(filename):
- f = open(filename, "rb")
- text = f.read()
- f.close()
- if util.binary(text):
- msg = _("%s looks like a binary file.") % filename
- if not opts.get('text'):
- raise util.Abort(msg)
- elif not opts.get('quiet'):
- ui.warn(_('warning: %s\n') % msg)
- return text
-
- name_a = local
- name_b = other
- labels = opts.get('label', [])
- if labels:
- name_a = labels.pop(0)
- if labels:
- name_b = labels.pop(0)
- if labels:
- raise util.Abort(_("can only specify two labels."))
-
- localtext = readfile(local)
- basetext = readfile(base)
- othertext = readfile(other)
-
- local = os.path.realpath(local)
- if not opts.get('print'):
- opener = util.opener(os.path.dirname(local))
- out = opener(os.path.basename(local), "w", atomictemp=True)
- else:
- out = sys.stdout
-
- reprocess = not opts.get('no_minimal')
-
- m3 = Merge3Text(basetext, localtext, othertext)
- for line in m3.merge_lines(name_a=name_a, name_b=name_b,
- reprocess=reprocess):
- out.write(line)
-
- if not opts.get('print'):
- out.rename()
-
- if m3.conflicts:
- if not opts.get('quiet'):
- ui.warn(_("warning: conflicts during merge.\n"))
- return 1
diff --git a/sys/src/cmd/hg/mercurial/sshrepo.py b/sys/src/cmd/hg/mercurial/sshrepo.py
deleted file mode 100644
index c6915bf65..000000000
--- a/sys/src/cmd/hg/mercurial/sshrepo.py
+++ /dev/null
@@ -1,260 +0,0 @@
-# sshrepo.py - ssh repository proxy class for mercurial
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import bin, hex
-from i18n import _
-import repo, util, error
-import re, urllib
-
-class remotelock(object):
- def __init__(self, repo):
- self.repo = repo
- def release(self):
- self.repo.unlock()
- self.repo = None
- def __del__(self):
- if self.repo:
- self.release()
-
-class sshrepository(repo.repository):
- def __init__(self, ui, path, create=0):
- self._url = path
- self.ui = ui
-
- m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
- if not m:
- self.abort(error.RepoError(_("couldn't parse location %s") % path))
-
- self.user = m.group(2)
- self.host = m.group(3)
- self.port = m.group(5)
- self.path = m.group(7) or "."
-
- sshcmd = self.ui.config("ui", "ssh", "ssh")
- remotecmd = self.ui.config("ui", "remotecmd", "hg")
-
- args = util.sshargs(sshcmd, self.host, self.user, self.port)
-
- if create:
- cmd = '%s %s "%s init %s"'
- cmd = cmd % (sshcmd, args, remotecmd, self.path)
-
- ui.note(_('running %s\n') % cmd)
- res = util.system(cmd)
- if res != 0:
- self.abort(error.RepoError(_("could not create remote repo")))
-
- self.validate_repo(ui, sshcmd, args, remotecmd)
-
- def url(self):
- return self._url
-
- def validate_repo(self, ui, sshcmd, args, remotecmd):
- # cleanup up previous run
- self.cleanup()
-
- cmd = '%s %s "%s -R %s serve --stdio"'
- cmd = cmd % (sshcmd, args, remotecmd, self.path)
-
- cmd = util.quotecommand(cmd)
- ui.note(_('running %s\n') % cmd)
- self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
-
- # skip any noise generated by remote shell
- self.do_cmd("hello")
- r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
- lines = ["", "dummy"]
- max_noise = 500
- while lines[-1] and max_noise:
- l = r.readline()
- self.readerr()
- if lines[-1] == "1\n" and l == "\n":
- break
- if l:
- ui.debug(_("remote: "), l)
- lines.append(l)
- max_noise -= 1
- else:
- self.abort(error.RepoError(_("no suitable response from remote hg")))
-
- self.capabilities = set()
- for l in reversed(lines):
- if l.startswith("capabilities:"):
- self.capabilities.update(l[:-1].split(":")[1].split())
- break
-
- def readerr(self):
- while 1:
- size = util.fstat(self.pipee).st_size
- if size == 0: break
- l = self.pipee.readline()
- if not l: break
- self.ui.status(_("remote: "), l)
-
- def abort(self, exception):
- self.cleanup()
- raise exception
-
- def cleanup(self):
- try:
- self.pipeo.close()
- self.pipei.close()
- # read the error descriptor until EOF
- for l in self.pipee:
- self.ui.status(_("remote: "), l)
- self.pipee.close()
- except:
- pass
-
- __del__ = cleanup
-
- def do_cmd(self, cmd, **args):
- self.ui.debug(_("sending %s command\n") % cmd)
- self.pipeo.write("%s\n" % cmd)
- for k, v in args.iteritems():
- self.pipeo.write("%s %d\n" % (k, len(v)))
- self.pipeo.write(v)
- self.pipeo.flush()
-
- return self.pipei
-
- def call(self, cmd, **args):
- self.do_cmd(cmd, **args)
- return self._recv()
-
- def _recv(self):
- l = self.pipei.readline()
- self.readerr()
- try:
- l = int(l)
- except:
- self.abort(error.ResponseError(_("unexpected response:"), l))
- return self.pipei.read(l)
-
- def _send(self, data, flush=False):
- self.pipeo.write("%d\n" % len(data))
- if data:
- self.pipeo.write(data)
- if flush:
- self.pipeo.flush()
- self.readerr()
-
- def lock(self):
- self.call("lock")
- return remotelock(self)
-
- def unlock(self):
- self.call("unlock")
-
- def lookup(self, key):
- self.requirecap('lookup', _('look up remote revision'))
- d = self.call("lookup", key=key)
- success, data = d[:-1].split(" ", 1)
- if int(success):
- return bin(data)
- else:
- self.abort(error.RepoError(data))
-
- def heads(self):
- d = self.call("heads")
- try:
- return map(bin, d[:-1].split(" "))
- except:
- self.abort(error.ResponseError(_("unexpected response:"), d))
-
- def branchmap(self):
- d = self.call("branchmap")
- try:
- branchmap = {}
- for branchpart in d.splitlines():
- branchheads = branchpart.split(' ')
- branchname = urllib.unquote(branchheads[0])
- branchheads = [bin(x) for x in branchheads[1:]]
- branchmap[branchname] = branchheads
- return branchmap
- except:
- raise error.ResponseError(_("unexpected response:"), d)
-
- def branches(self, nodes):
- n = " ".join(map(hex, nodes))
- d = self.call("branches", nodes=n)
- try:
- br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
- return br
- except:
- self.abort(error.ResponseError(_("unexpected response:"), d))
-
- def between(self, pairs):
- n = " ".join(["-".join(map(hex, p)) for p in pairs])
- d = self.call("between", pairs=n)
- try:
- p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
- return p
- except:
- self.abort(error.ResponseError(_("unexpected response:"), d))
-
- def changegroup(self, nodes, kind):
- n = " ".join(map(hex, nodes))
- return self.do_cmd("changegroup", roots=n)
-
- def changegroupsubset(self, bases, heads, kind):
- self.requirecap('changegroupsubset', _('look up remote changes'))
- bases = " ".join(map(hex, bases))
- heads = " ".join(map(hex, heads))
- return self.do_cmd("changegroupsubset", bases=bases, heads=heads)
-
- def unbundle(self, cg, heads, source):
- d = self.call("unbundle", heads=' '.join(map(hex, heads)))
- if d:
- # remote may send "unsynced changes"
- self.abort(error.RepoError(_("push refused: %s") % d))
-
- while 1:
- d = cg.read(4096)
- if not d:
- break
- self._send(d)
-
- self._send("", flush=True)
-
- r = self._recv()
- if r:
- # remote may send "unsynced changes"
- self.abort(error.RepoError(_("push failed: %s") % r))
-
- r = self._recv()
- try:
- return int(r)
- except:
- self.abort(error.ResponseError(_("unexpected response:"), r))
-
- def addchangegroup(self, cg, source, url):
- d = self.call("addchangegroup")
- if d:
- self.abort(error.RepoError(_("push refused: %s") % d))
- while 1:
- d = cg.read(4096)
- if not d:
- break
- self.pipeo.write(d)
- self.readerr()
-
- self.pipeo.flush()
-
- self.readerr()
- r = self._recv()
- if not r:
- return 1
- try:
- return int(r)
- except:
- self.abort(error.ResponseError(_("unexpected response:"), r))
-
- def stream_out(self):
- return self.do_cmd('stream_out')
-
-instance = sshrepository
diff --git a/sys/src/cmd/hg/mercurial/sshserver.py b/sys/src/cmd/hg/mercurial/sshserver.py
deleted file mode 100644
index d5fccbc43..000000000
--- a/sys/src/cmd/hg/mercurial/sshserver.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# sshserver.py - ssh protocol server support for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-from node import bin, hex
-import streamclone, util, hook
-import os, sys, tempfile, urllib
-
-class sshserver(object):
- def __init__(self, ui, repo):
- self.ui = ui
- self.repo = repo
- self.lock = None
- self.fin = sys.stdin
- self.fout = sys.stdout
-
- hook.redirect(True)
- sys.stdout = sys.stderr
-
- # Prevent insertion/deletion of CRs
- util.set_binary(self.fin)
- util.set_binary(self.fout)
-
- def getarg(self):
- argline = self.fin.readline()[:-1]
- arg, l = argline.split()
- val = self.fin.read(int(l))
- return arg, val
-
- def respond(self, v):
- self.fout.write("%d\n" % len(v))
- self.fout.write(v)
- self.fout.flush()
-
- def serve_forever(self):
- try:
- while self.serve_one(): pass
- finally:
- if self.lock is not None:
- self.lock.release()
- sys.exit(0)
-
- def serve_one(self):
- cmd = self.fin.readline()[:-1]
- if cmd:
- impl = getattr(self, 'do_' + cmd, None)
- if impl: impl()
- else: self.respond("")
- return cmd != ''
-
- def do_lookup(self):
- arg, key = self.getarg()
- assert arg == 'key'
- try:
- r = hex(self.repo.lookup(key))
- success = 1
- except Exception, inst:
- r = str(inst)
- success = 0
- self.respond("%s %s\n" % (success, r))
-
- def do_branchmap(self):
- branchmap = self.repo.branchmap()
- heads = []
- for branch, nodes in branchmap.iteritems():
- branchname = urllib.quote(branch)
- branchnodes = [hex(node) for node in nodes]
- heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
- self.respond('\n'.join(heads))
-
- def do_heads(self):
- h = self.repo.heads()
- self.respond(" ".join(map(hex, h)) + "\n")
-
- def do_hello(self):
- '''the hello command returns a set of lines describing various
- interesting things about the server, in an RFC822-like format.
- Currently the only one defined is "capabilities", which
- consists of a line in the form:
-
- capabilities: space separated list of tokens
- '''
-
- caps = ['unbundle', 'lookup', 'changegroupsubset', 'branchmap']
- if self.ui.configbool('server', 'uncompressed'):
- caps.append('stream=%d' % self.repo.changelog.version)
- self.respond("capabilities: %s\n" % (' '.join(caps),))
-
- def do_lock(self):
- '''DEPRECATED - allowing remote client to lock repo is not safe'''
-
- self.lock = self.repo.lock()
- self.respond("")
-
- def do_unlock(self):
- '''DEPRECATED'''
-
- if self.lock:
- self.lock.release()
- self.lock = None
- self.respond("")
-
- def do_branches(self):
- arg, nodes = self.getarg()
- nodes = map(bin, nodes.split(" "))
- r = []
- for b in self.repo.branches(nodes):
- r.append(" ".join(map(hex, b)) + "\n")
- self.respond("".join(r))
-
- def do_between(self):
- arg, pairs = self.getarg()
- pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
- r = []
- for b in self.repo.between(pairs):
- r.append(" ".join(map(hex, b)) + "\n")
- self.respond("".join(r))
-
- def do_changegroup(self):
- nodes = []
- arg, roots = self.getarg()
- nodes = map(bin, roots.split(" "))
-
- cg = self.repo.changegroup(nodes, 'serve')
- while True:
- d = cg.read(4096)
- if not d:
- break
- self.fout.write(d)
-
- self.fout.flush()
-
- def do_changegroupsubset(self):
- argmap = dict([self.getarg(), self.getarg()])
- bases = [bin(n) for n in argmap['bases'].split(' ')]
- heads = [bin(n) for n in argmap['heads'].split(' ')]
-
- cg = self.repo.changegroupsubset(bases, heads, 'serve')
- while True:
- d = cg.read(4096)
- if not d:
- break
- self.fout.write(d)
-
- self.fout.flush()
-
- def do_addchangegroup(self):
- '''DEPRECATED'''
-
- if not self.lock:
- self.respond("not locked")
- return
-
- self.respond("")
- r = self.repo.addchangegroup(self.fin, 'serve', self.client_url())
- self.respond(str(r))
-
- def client_url(self):
- client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
- return 'remote:ssh:' + client
-
- def do_unbundle(self):
- their_heads = self.getarg()[1].split()
-
- def check_heads():
- heads = map(hex, self.repo.heads())
- return their_heads == [hex('force')] or their_heads == heads
-
- # fail early if possible
- if not check_heads():
- self.respond(_('unsynced changes'))
- return
-
- self.respond('')
-
- # write bundle data to temporary file because it can be big
- tempname = fp = None
- try:
- fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
- fp = os.fdopen(fd, 'wb+')
-
- count = int(self.fin.readline())
- while count:
- fp.write(self.fin.read(count))
- count = int(self.fin.readline())
-
- was_locked = self.lock is not None
- if not was_locked:
- self.lock = self.repo.lock()
- try:
- if not check_heads():
- # someone else committed/pushed/unbundled while we
- # were transferring data
- self.respond(_('unsynced changes'))
- return
- self.respond('')
-
- # push can proceed
-
- fp.seek(0)
- r = self.repo.addchangegroup(fp, 'serve', self.client_url())
- self.respond(str(r))
- finally:
- if not was_locked:
- self.lock.release()
- self.lock = None
- finally:
- if fp is not None:
- fp.close()
- if tempname is not None:
- os.unlink(tempname)
-
- def do_stream_out(self):
- try:
- for chunk in streamclone.stream_out(self.repo):
- self.fout.write(chunk)
- self.fout.flush()
- except streamclone.StreamException, inst:
- self.fout.write(str(inst))
- self.fout.flush()
diff --git a/sys/src/cmd/hg/mercurial/statichttprepo.py b/sys/src/cmd/hg/mercurial/statichttprepo.py
deleted file mode 100644
index 0913d2fbb..000000000
--- a/sys/src/cmd/hg/mercurial/statichttprepo.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# statichttprepo.py - simple http repository class for mercurial
-#
-# This provides read-only repo access to repositories exported via static http
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import changelog, byterange, url, error
-import localrepo, manifest, util, store
-import urllib, urllib2, errno
-
-class httprangereader(object):
- def __init__(self, url, opener):
- # we assume opener has HTTPRangeHandler
- self.url = url
- self.pos = 0
- self.opener = opener
- def seek(self, pos):
- self.pos = pos
- def read(self, bytes=None):
- req = urllib2.Request(self.url)
- end = ''
- if bytes:
- end = self.pos + bytes - 1
- req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
-
- try:
- f = self.opener.open(req)
- data = f.read()
- if hasattr(f, 'getcode'):
- # python 2.6+
- code = f.getcode()
- elif hasattr(f, 'code'):
- # undocumented attribute, seems to be set in 2.4 and 2.5
- code = f.code
- else:
- # Don't know how to check, hope for the best.
- code = 206
- except urllib2.HTTPError, inst:
- num = inst.code == 404 and errno.ENOENT or None
- raise IOError(num, inst)
- except urllib2.URLError, inst:
- raise IOError(None, inst.reason[1])
-
- if code == 200:
- # HTTPRangeHandler does nothing if remote does not support
- # Range headers and returns the full entity. Let's slice it.
- if bytes:
- data = data[self.pos:self.pos + bytes]
- else:
- data = data[self.pos:]
- elif bytes:
- data = data[:bytes]
- self.pos += len(data)
- return data
-
-def build_opener(ui, authinfo):
- # urllib cannot handle URLs with embedded user or passwd
- urlopener = url.opener(ui, authinfo)
- urlopener.add_handler(byterange.HTTPRangeHandler())
-
- def opener(base):
- """return a function that opens files over http"""
- p = base
- def o(path, mode="r"):
- f = "/".join((p, urllib.quote(path)))
- return httprangereader(f, urlopener)
- return o
-
- return opener
-
-class statichttprepository(localrepo.localrepository):
- def __init__(self, ui, path):
- self._url = path
- self.ui = ui
-
- self.path, authinfo = url.getauthinfo(path.rstrip('/') + "/.hg")
-
- opener = build_opener(ui, authinfo)
- self.opener = opener(self.path)
-
- # find requirements
- try:
- requirements = self.opener("requires").read().splitlines()
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
- # check if it is a non-empty old-style repository
- try:
- self.opener("00changelog.i").read(1)
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
- # we do not care about empty old-style repositories here
- msg = _("'%s' does not appear to be an hg repository") % path
- raise error.RepoError(msg)
- requirements = []
-
- # check them
- for r in requirements:
- if r not in self.supported:
- raise error.RepoError(_("requirement '%s' not supported") % r)
-
- # setup store
- def pjoin(a, b):
- return a + '/' + b
- self.store = store.store(requirements, self.path, opener, pjoin)
- self.spath = self.store.path
- self.sopener = self.store.opener
- self.sjoin = self.store.join
-
- self.manifest = manifest.manifest(self.sopener)
- self.changelog = changelog.changelog(self.sopener)
- self._tags = None
- self.nodetagscache = None
- self.encodepats = None
- self.decodepats = None
-
- def url(self):
- return self._url
-
- def local(self):
- return False
-
- def lock(self, wait=True):
- raise util.Abort(_('cannot lock static-http repository'))
-
-def instance(ui, path, create):
- if create:
- raise util.Abort(_('cannot create new static-http repository'))
- return statichttprepository(ui, path[7:])
diff --git a/sys/src/cmd/hg/mercurial/store.py b/sys/src/cmd/hg/mercurial/store.py
deleted file mode 100644
index eec9dd519..000000000
--- a/sys/src/cmd/hg/mercurial/store.py
+++ /dev/null
@@ -1,333 +0,0 @@
-# store.py - repository store handling for Mercurial
-#
-# Copyright 2008 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import osutil, util
-import os, stat
-
-_sha = util.sha1
-
-# This avoids a collision between a file named foo and a dir named
-# foo.i or foo.d
-def encodedir(path):
- if not path.startswith('data/'):
- return path
- return (path
- .replace(".hg/", ".hg.hg/")
- .replace(".i/", ".i.hg/")
- .replace(".d/", ".d.hg/"))
-
-def decodedir(path):
- if not path.startswith('data/'):
- return path
- return (path
- .replace(".d.hg/", ".d/")
- .replace(".i.hg/", ".i/")
- .replace(".hg.hg/", ".hg/"))
-
-def _buildencodefun():
- e = '_'
- win_reserved = [ord(x) for x in '\\:*?"<>|']
- cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
- for x in (range(32) + range(126, 256) + win_reserved):
- cmap[chr(x)] = "~%02x" % x
- for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
- cmap[chr(x)] = e + chr(x).lower()
- dmap = {}
- for k, v in cmap.iteritems():
- dmap[v] = k
- def decode(s):
- i = 0
- while i < len(s):
- for l in xrange(1, 4):
- try:
- yield dmap[s[i:i+l]]
- i += l
- break
- except KeyError:
- pass
- else:
- raise KeyError
- return (lambda s: "".join([cmap[c] for c in encodedir(s)]),
- lambda s: decodedir("".join(list(decode(s)))))
-
-encodefilename, decodefilename = _buildencodefun()
-
-def _build_lower_encodefun():
- win_reserved = [ord(x) for x in '\\:*?"<>|']
- cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
- for x in (range(32) + range(126, 256) + win_reserved):
- cmap[chr(x)] = "~%02x" % x
- for x in range(ord("A"), ord("Z")+1):
- cmap[chr(x)] = chr(x).lower()
- return lambda s: "".join([cmap[c] for c in s])
-
-lowerencode = _build_lower_encodefun()
-
-_windows_reserved_filenames = '''con prn aux nul
- com1 com2 com3 com4 com5 com6 com7 com8 com9
- lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
-def auxencode(path):
- res = []
- for n in path.split('/'):
- if n:
- base = n.split('.')[0]
- if base and (base in _windows_reserved_filenames):
- # encode third letter ('aux' -> 'au~78')
- ec = "~%02x" % ord(n[2])
- n = n[0:2] + ec + n[3:]
- if n[-1] in '. ':
- # encode last period or space ('foo...' -> 'foo..~2e')
- n = n[:-1] + "~%02x" % ord(n[-1])
- res.append(n)
- return '/'.join(res)
-
-MAX_PATH_LEN_IN_HGSTORE = 120
-DIR_PREFIX_LEN = 8
-_MAX_SHORTENED_DIRS_LEN = 8 * (DIR_PREFIX_LEN + 1) - 4
-def hybridencode(path):
- '''encodes path with a length limit
-
- Encodes all paths that begin with 'data/', according to the following.
-
- Default encoding (reversible):
-
- Encodes all uppercase letters 'X' as '_x'. All reserved or illegal
- characters are encoded as '~xx', where xx is the two digit hex code
- of the character (see encodefilename).
- Relevant path components consisting of Windows reserved filenames are
- masked by encoding the third character ('aux' -> 'au~78', see auxencode).
-
- Hashed encoding (not reversible):
-
- If the default-encoded path is longer than MAX_PATH_LEN_IN_HGSTORE, a
- non-reversible hybrid hashing of the path is done instead.
- This encoding uses up to DIR_PREFIX_LEN characters of all directory
- levels of the lowerencoded path, but not more levels than can fit into
- _MAX_SHORTENED_DIRS_LEN.
- Then follows the filler followed by the sha digest of the full path.
- The filler is the beginning of the basename of the lowerencoded path
- (the basename is everything after the last path separator). The filler
- is as long as possible, filling in characters from the basename until
- the encoded path has MAX_PATH_LEN_IN_HGSTORE characters (or all chars
- of the basename have been taken).
- The extension (e.g. '.i' or '.d') is preserved.
-
- The string 'data/' at the beginning is replaced with 'dh/', if the hashed
- encoding was used.
- '''
- if not path.startswith('data/'):
- return path
- # escape directories ending with .i and .d
- path = encodedir(path)
- ndpath = path[len('data/'):]
- res = 'data/' + auxencode(encodefilename(ndpath))
- if len(res) > MAX_PATH_LEN_IN_HGSTORE:
- digest = _sha(path).hexdigest()
- aep = auxencode(lowerencode(ndpath))
- _root, ext = os.path.splitext(aep)
- parts = aep.split('/')
- basename = parts[-1]
- sdirs = []
- for p in parts[:-1]:
- d = p[:DIR_PREFIX_LEN]
- if d[-1] in '. ':
- # Windows can't access dirs ending in period or space
- d = d[:-1] + '_'
- t = '/'.join(sdirs) + '/' + d
- if len(t) > _MAX_SHORTENED_DIRS_LEN:
- break
- sdirs.append(d)
- dirs = '/'.join(sdirs)
- if len(dirs) > 0:
- dirs += '/'
- res = 'dh/' + dirs + digest + ext
- space_left = MAX_PATH_LEN_IN_HGSTORE - len(res)
- if space_left > 0:
- filler = basename[:space_left]
- res = 'dh/' + dirs + filler + digest + ext
- return res
-
-def _calcmode(path):
- try:
- # files in .hg/ will be created using this mode
- mode = os.stat(path).st_mode
- # avoid some useless chmods
- if (0777 & ~util.umask) == (0777 & mode):
- mode = None
- except OSError:
- mode = None
- return mode
-
-_data = 'data 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
-
-class basicstore(object):
- '''base class for local repository stores'''
- def __init__(self, path, opener, pathjoiner):
- self.pathjoiner = pathjoiner
- self.path = path
- self.createmode = _calcmode(path)
- op = opener(self.path)
- op.createmode = self.createmode
- self.opener = lambda f, *args, **kw: op(encodedir(f), *args, **kw)
-
- def join(self, f):
- return self.pathjoiner(self.path, encodedir(f))
-
- def _walk(self, relpath, recurse):
- '''yields (unencoded, encoded, size)'''
- path = self.pathjoiner(self.path, relpath)
- striplen = len(self.path) + len(os.sep)
- l = []
- if os.path.isdir(path):
- visit = [path]
- while visit:
- p = visit.pop()
- for f, kind, st in osutil.listdir(p, stat=True):
- fp = self.pathjoiner(p, f)
- if kind == stat.S_IFREG and f[-2:] in ('.d', '.i'):
- n = util.pconvert(fp[striplen:])
- l.append((decodedir(n), n, st.st_size))
- elif kind == stat.S_IFDIR and recurse:
- visit.append(fp)
- return sorted(l)
-
- def datafiles(self):
- return self._walk('data', True)
-
- def walk(self):
- '''yields (unencoded, encoded, size)'''
- # yield data files first
- for x in self.datafiles():
- yield x
- # yield manifest before changelog
- for x in reversed(self._walk('', False)):
- yield x
-
- def copylist(self):
- return ['requires'] + _data.split()
-
-class encodedstore(basicstore):
- def __init__(self, path, opener, pathjoiner):
- self.pathjoiner = pathjoiner
- self.path = self.pathjoiner(path, 'store')
- self.createmode = _calcmode(self.path)
- op = opener(self.path)
- op.createmode = self.createmode
- self.opener = lambda f, *args, **kw: op(encodefilename(f), *args, **kw)
-
- def datafiles(self):
- for a, b, size in self._walk('data', True):
- try:
- a = decodefilename(a)
- except KeyError:
- a = None
- yield a, b, size
-
- def join(self, f):
- return self.pathjoiner(self.path, encodefilename(f))
-
- def copylist(self):
- return (['requires', '00changelog.i'] +
- [self.pathjoiner('store', f) for f in _data.split()])
-
-class fncache(object):
- # the filename used to be partially encoded
- # hence the encodedir/decodedir dance
- def __init__(self, opener):
- self.opener = opener
- self.entries = None
-
- def _load(self):
- '''fill the entries from the fncache file'''
- self.entries = set()
- try:
- fp = self.opener('fncache', mode='rb')
- except IOError:
- # skip nonexistent file
- return
- for n, line in enumerate(fp):
- if (len(line) < 2) or (line[-1] != '\n'):
- t = _('invalid entry in fncache, line %s') % (n + 1)
- raise util.Abort(t)
- self.entries.add(decodedir(line[:-1]))
- fp.close()
-
- def rewrite(self, files):
- fp = self.opener('fncache', mode='wb')
- for p in files:
- fp.write(encodedir(p) + '\n')
- fp.close()
- self.entries = set(files)
-
- def add(self, fn):
- if self.entries is None:
- self._load()
- self.opener('fncache', 'ab').write(encodedir(fn) + '\n')
-
- def __contains__(self, fn):
- if self.entries is None:
- self._load()
- return fn in self.entries
-
- def __iter__(self):
- if self.entries is None:
- self._load()
- return iter(self.entries)
-
-class fncachestore(basicstore):
- def __init__(self, path, opener, pathjoiner):
- self.pathjoiner = pathjoiner
- self.path = self.pathjoiner(path, 'store')
- self.createmode = _calcmode(self.path)
- op = opener(self.path)
- op.createmode = self.createmode
- fnc = fncache(op)
- self.fncache = fnc
-
- def fncacheopener(path, mode='r', *args, **kw):
- if (mode not in ('r', 'rb')
- and path.startswith('data/')
- and path not in fnc):
- fnc.add(path)
- return op(hybridencode(path), mode, *args, **kw)
- self.opener = fncacheopener
-
- def join(self, f):
- return self.pathjoiner(self.path, hybridencode(f))
-
- def datafiles(self):
- rewrite = False
- existing = []
- pjoin = self.pathjoiner
- spath = self.path
- for f in self.fncache:
- ef = hybridencode(f)
- try:
- st = os.stat(pjoin(spath, ef))
- yield f, ef, st.st_size
- existing.append(f)
- except OSError:
- # nonexistent entry
- rewrite = True
- if rewrite:
- # rewrite fncache to remove nonexistent entries
- # (may be caused by rollback / strip)
- self.fncache.rewrite(existing)
-
- def copylist(self):
- d = _data + ' dh fncache'
- return (['requires', '00changelog.i'] +
- [self.pathjoiner('store', f) for f in d.split()])
-
-def store(requirements, path, opener, pathjoiner=None):
- pathjoiner = pathjoiner or os.path.join
- if 'store' in requirements:
- if 'fncache' in requirements:
- return fncachestore(path, opener, pathjoiner)
- return encodedstore(path, opener, pathjoiner)
- return basicstore(path, opener, pathjoiner)
diff --git a/sys/src/cmd/hg/mercurial/streamclone.py b/sys/src/cmd/hg/mercurial/streamclone.py
deleted file mode 100644
index 82cd2f730..000000000
--- a/sys/src/cmd/hg/mercurial/streamclone.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# streamclone.py - streaming clone server support for mercurial
-#
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import util, error
-from i18n import _
-
-from mercurial import store
-
-class StreamException(Exception):
- def __init__(self, code):
- Exception.__init__(self)
- self.code = code
- def __str__(self):
- return '%i\n' % self.code
-
-# if server supports streaming clone, it advertises "stream"
-# capability with value that is version+flags of repo it is serving.
-# client only streams if it can read that repo format.
-
-# stream file format is simple.
-#
-# server writes out line that says how many files, how many total
-# bytes. separator is ascii space, byte counts are strings.
-#
-# then for each file:
-#
-# server writes out line that says filename, how many bytes in
-# file. separator is ascii nul, byte count is string.
-#
-# server writes out raw file data.
-
-def stream_out(repo, untrusted=False):
- '''stream out all metadata files in repository.
- writes to file-like object, must support write() and optional flush().'''
-
- if not repo.ui.configbool('server', 'uncompressed', untrusted=untrusted):
- raise StreamException(1)
-
- entries = []
- total_bytes = 0
- try:
- # get consistent snapshot of repo, lock during scan
- lock = repo.lock()
- try:
- repo.ui.debug(_('scanning\n'))
- for name, ename, size in repo.store.walk():
- # for backwards compat, name was partially encoded
- entries.append((store.encodedir(name), size))
- total_bytes += size
- finally:
- lock.release()
- except error.LockError:
- raise StreamException(2)
-
- yield '0\n'
- repo.ui.debug(_('%d files, %d bytes to transfer\n') %
- (len(entries), total_bytes))
- yield '%d %d\n' % (len(entries), total_bytes)
- for name, size in entries:
- repo.ui.debug(_('sending %s (%d bytes)\n') % (name, size))
- yield '%s\0%d\n' % (name, size)
- for chunk in util.filechunkiter(repo.sopener(name), limit=size):
- yield chunk
diff --git a/sys/src/cmd/hg/mercurial/strutil.py b/sys/src/cmd/hg/mercurial/strutil.py
deleted file mode 100644
index fab37c419..000000000
--- a/sys/src/cmd/hg/mercurial/strutil.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# strutil.py - string utilities for Mercurial
-#
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-def findall(haystack, needle, start=0, end=None):
- if end is None:
- end = len(haystack)
- if end < 0:
- end += len(haystack)
- if start < 0:
- start += len(haystack)
- while start < end:
- c = haystack.find(needle, start, end)
- if c == -1:
- break
- yield c
- start = c + 1
-
-def rfindall(haystack, needle, start=0, end=None):
- if end is None:
- end = len(haystack)
- if end < 0:
- end += len(haystack)
- if start < 0:
- start += len(haystack)
- while end >= 0:
- c = haystack.rfind(needle, start, end)
- if c == -1:
- break
- yield c
- end = c - 1
diff --git a/sys/src/cmd/hg/mercurial/subrepo.py b/sys/src/cmd/hg/mercurial/subrepo.py
deleted file mode 100644
index 0eb313fe0..000000000
--- a/sys/src/cmd/hg/mercurial/subrepo.py
+++ /dev/null
@@ -1,197 +0,0 @@
-# subrepo.py - sub-repository handling for Mercurial
-#
-# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import errno, os
-from i18n import _
-import config, util, node, error
-hg = None
-
-nullstate = ('', '')
-
-def state(ctx):
- p = config.config()
- def read(f, sections=None, remap=None):
- if f in ctx:
- try:
- p.parse(f, ctx[f].data(), sections, remap)
- except IOError, err:
- if err.errno != errno.ENOENT:
- raise
- read('.hgsub')
-
- rev = {}
- if '.hgsubstate' in ctx:
- try:
- for l in ctx['.hgsubstate'].data().splitlines():
- revision, path = l.split()
- rev[path] = revision
- except IOError, err:
- if err.errno != errno.ENOENT:
- raise
-
- state = {}
- for path, src in p[''].items():
- state[path] = (src, rev.get(path, ''))
-
- return state
-
-def writestate(repo, state):
- repo.wwrite('.hgsubstate',
- ''.join(['%s %s\n' % (state[s][1], s)
- for s in sorted(state)]), '')
-
-def submerge(repo, wctx, mctx, actx):
- if mctx == actx: # backwards?
- actx = wctx.p1()
- s1 = wctx.substate
- s2 = mctx.substate
- sa = actx.substate
- sm = {}
-
- for s, l in s1.items():
- a = sa.get(s, nullstate)
- if s in s2:
- r = s2[s]
- if l == r or r == a: # no change or local is newer
- sm[s] = l
- continue
- elif l == a: # other side changed
- wctx.sub(s).get(r)
- sm[s] = r
- elif l[0] != r[0]: # sources differ
- if repo.ui.promptchoice(
- _(' subrepository sources for %s differ\n'
- 'use (l)ocal source (%s) or (r)emote source (%s)?')
- % (s, l[0], r[0]),
- (_('&Local'), _('&Remote')), 0):
- wctx.sub(s).get(r)
- sm[s] = r
- elif l[1] == a[1]: # local side is unchanged
- wctx.sub(s).get(r)
- sm[s] = r
- else:
- wctx.sub(s).merge(r)
- sm[s] = l
- elif l == a: # remote removed, local unchanged
- wctx.sub(s).remove()
- else:
- if repo.ui.promptchoice(
- _(' local changed subrepository %s which remote removed\n'
- 'use (c)hanged version or (d)elete?') % s,
- (_('&Changed'), _('&Delete')), 0):
- wctx.sub(s).remove()
-
- for s, r in s2.items():
- if s in s1:
- continue
- elif s not in sa:
- wctx.sub(s).get(r)
- sm[s] = r
- elif r != sa[s]:
- if repo.ui.promptchoice(
- _(' remote changed subrepository %s which local removed\n'
- 'use (c)hanged version or (d)elete?') % s,
- (_('&Changed'), _('&Delete')), 0) == 0:
- wctx.sub(s).get(r)
- sm[s] = r
-
- # record merged .hgsubstate
- writestate(repo, sm)
-
-def _abssource(repo, push=False):
- if hasattr(repo, '_subparent'):
- source = repo._subsource
- if source.startswith('/') or '://' in source:
- return source
- parent = _abssource(repo._subparent)
- if '://' in parent:
- if parent[-1] == '/':
- parent = parent[:-1]
- return parent + '/' + source
- return os.path.join(parent, repo._subsource)
- if push and repo.ui.config('paths', 'default-push'):
- return repo.ui.config('paths', 'default-push', repo.root)
- return repo.ui.config('paths', 'default', repo.root)
-
-def subrepo(ctx, path):
- # subrepo inherently violates our import layering rules
- # because it wants to make repo objects from deep inside the stack
- # so we manually delay the circular imports to not break
- # scripts that don't use our demand-loading
- global hg
- import hg as h
- hg = h
-
- util.path_auditor(ctx._repo.root)(path)
- state = ctx.substate.get(path, nullstate)
- if state[0].startswith('['): # future expansion
- raise error.Abort('unknown subrepo source %s' % state[0])
- return hgsubrepo(ctx, path, state)
-
-class hgsubrepo(object):
- def __init__(self, ctx, path, state):
- self._path = path
- self._state = state
- r = ctx._repo
- root = r.wjoin(path)
- if os.path.exists(os.path.join(root, '.hg')):
- self._repo = hg.repository(r.ui, root)
- else:
- util.makedirs(root)
- self._repo = hg.repository(r.ui, root, create=True)
- self._repo._subparent = r
- self._repo._subsource = state[0]
-
- def dirty(self):
- r = self._state[1]
- if r == '':
- return True
- w = self._repo[None]
- if w.p1() != self._repo[r]: # version checked out changed
- return True
- return w.dirty() # working directory changed
-
- def commit(self, text, user, date):
- n = self._repo.commit(text, user, date)
- if not n:
- return self._repo['.'].hex() # different version checked out
- return node.hex(n)
-
- def remove(self):
- # we can't fully delete the repository as it may contain
- # local-only history
- self._repo.ui.note(_('removing subrepo %s\n') % self._path)
- hg.clean(self._repo, node.nullid, False)
-
- def get(self, state):
- source, revision = state
- try:
- self._repo.lookup(revision)
- except error.RepoError:
- self._repo._subsource = source
- self._repo.ui.status(_('pulling subrepo %s\n') % self._path)
- srcurl = _abssource(self._repo)
- other = hg.repository(self._repo.ui, srcurl)
- self._repo.pull(other)
-
- hg.clean(self._repo, revision, False)
-
- def merge(self, state):
- hg.merge(self._repo, state[1], remind=False)
-
- def push(self, force):
- # push subrepos depth-first for coherent ordering
- c = self._repo['']
- subs = c.substate # only repos that are committed
- for s in sorted(subs):
- c.sub(s).push(force)
-
- self._repo.ui.status(_('pushing subrepo %s\n') % self._path)
- dsturl = _abssource(self._repo, True)
- other = hg.repository(self._repo.ui, dsturl)
- self._repo.push(other, force)
-
diff --git a/sys/src/cmd/hg/mercurial/tags.py b/sys/src/cmd/hg/mercurial/tags.py
deleted file mode 100644
index 41a2ddc9a..000000000
--- a/sys/src/cmd/hg/mercurial/tags.py
+++ /dev/null
@@ -1,338 +0,0 @@
-# tags.py - read tag info from local repository
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com>
-# Copyright 2009 Greg Ward <greg@gerg.ca>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-# Currently this module only deals with reading and caching tags.
-# Eventually, it could take care of updating (adding/removing/moving)
-# tags too.
-
-import os
-from node import nullid, bin, hex, short
-from i18n import _
-import encoding
-import error
-
-def _debugalways(ui, *msg):
- ui.write(*msg)
-
-def _debugconditional(ui, *msg):
- ui.debug(*msg)
-
-def _debugnever(ui, *msg):
- pass
-
-_debug = _debugalways
-_debug = _debugnever
-
-def findglobaltags1(ui, repo, alltags, tagtypes):
- '''Find global tags in repo by reading .hgtags from every head that
- has a distinct version of it. Updates the dicts alltags, tagtypes
- in place: alltags maps tag name to (node, hist) pair (see _readtags()
- below), and tagtypes maps tag name to tag type ('global' in this
- case).'''
-
- seen = set()
- fctx = None
- ctxs = [] # list of filectx
- for node in repo.heads():
- try:
- fnode = repo[node].filenode('.hgtags')
- except error.LookupError:
- continue
- if fnode not in seen:
- seen.add(fnode)
- if not fctx:
- fctx = repo.filectx('.hgtags', fileid=fnode)
- else:
- fctx = fctx.filectx(fnode)
- ctxs.append(fctx)
-
- # read the tags file from each head, ending with the tip
- for fctx in reversed(ctxs):
- filetags = _readtags(
- ui, repo, fctx.data().splitlines(), fctx)
- _updatetags(filetags, "global", alltags, tagtypes)
-
-def findglobaltags2(ui, repo, alltags, tagtypes):
- '''Same as findglobaltags1(), but with caching.'''
- # This is so we can be lazy and assume alltags contains only global
- # tags when we pass it to _writetagcache().
- assert len(alltags) == len(tagtypes) == 0, \
- "findglobaltags() should be called first"
-
- (heads, tagfnode, cachetags, shouldwrite) = _readtagcache(ui, repo)
- if cachetags is not None:
- assert not shouldwrite
- # XXX is this really 100% correct? are there oddball special
- # cases where a global tag should outrank a local tag but won't,
- # because cachetags does not contain rank info?
- _updatetags(cachetags, 'global', alltags, tagtypes)
- return
-
- _debug(ui, "reading tags from %d head(s): %s\n"
- % (len(heads), map(short, reversed(heads))))
- seen = set() # set of fnode
- fctx = None
- for head in reversed(heads): # oldest to newest
- assert head in repo.changelog.nodemap, \
- "tag cache returned bogus head %s" % short(head)
-
- fnode = tagfnode.get(head)
- if fnode and fnode not in seen:
- seen.add(fnode)
- if not fctx:
- fctx = repo.filectx('.hgtags', fileid=fnode)
- else:
- fctx = fctx.filectx(fnode)
-
- filetags = _readtags(ui, repo, fctx.data().splitlines(), fctx)
- _updatetags(filetags, 'global', alltags, tagtypes)
-
- # and update the cache (if necessary)
- if shouldwrite:
- _writetagcache(ui, repo, heads, tagfnode, alltags)
-
-# Set this to findglobaltags1 to disable tag caching.
-findglobaltags = findglobaltags1
-
-def readlocaltags(ui, repo, alltags, tagtypes):
- '''Read local tags in repo. Update alltags and tagtypes.'''
- try:
- # localtags is in the local encoding; re-encode to UTF-8 on
- # input for consistency with the rest of this module.
- data = repo.opener("localtags").read()
- filetags = _readtags(
- ui, repo, data.splitlines(), "localtags",
- recode=encoding.fromlocal)
- _updatetags(filetags, "local", alltags, tagtypes)
- except IOError:
- pass
-
-def _readtags(ui, repo, lines, fn, recode=None):
- '''Read tag definitions from a file (or any source of lines).
- Return a mapping from tag name to (node, hist): node is the node id
- from the last line read for that name, and hist is the list of node
- ids previously associated with it (in file order). All node ids are
- binary, not hex.'''
-
- filetags = {} # map tag name to (node, hist)
- count = 0
-
- def warn(msg):
- ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
-
- for line in lines:
- count += 1
- if not line:
- continue
- try:
- (nodehex, name) = line.split(" ", 1)
- except ValueError:
- warn(_("cannot parse entry"))
- continue
- name = name.strip()
- if recode:
- name = recode(name)
- try:
- nodebin = bin(nodehex)
- except TypeError:
- warn(_("node '%s' is not well formed") % nodehex)
- continue
- if nodebin not in repo.changelog.nodemap:
- # silently ignore as pull -r might cause this
- continue
-
- # update filetags
- hist = []
- if name in filetags:
- n, hist = filetags[name]
- hist.append(n)
- filetags[name] = (nodebin, hist)
- return filetags
-
-def _updatetags(filetags, tagtype, alltags, tagtypes):
- '''Incorporate the tag info read from one file into the two
- dictionaries, alltags and tagtypes, that contain all tag
- info (global across all heads plus local).'''
-
- for name, nodehist in filetags.iteritems():
- if name not in alltags:
- alltags[name] = nodehist
- tagtypes[name] = tagtype
- continue
-
- # we prefer alltags[name] if:
- # it supercedes us OR
- # mutual supercedes and it has a higher rank
- # otherwise we win because we're tip-most
- anode, ahist = nodehist
- bnode, bhist = alltags[name]
- if (bnode != anode and anode in bhist and
- (bnode not in ahist or len(bhist) > len(ahist))):
- anode = bnode
- ahist.extend([n for n in bhist if n not in ahist])
- alltags[name] = anode, ahist
- tagtypes[name] = tagtype
-
-
-# The tag cache only stores info about heads, not the tag contents
-# from each head. I.e. it doesn't try to squeeze out the maximum
-# performance, but is simpler has a better chance of actually
-# working correctly. And this gives the biggest performance win: it
-# avoids looking up .hgtags in the manifest for every head, and it
-# can avoid calling heads() at all if there have been no changes to
-# the repo.
-
-def _readtagcache(ui, repo):
- '''Read the tag cache and return a tuple (heads, fnodes, cachetags,
- shouldwrite). If the cache is completely up-to-date, cachetags is a
- dict of the form returned by _readtags(); otherwise, it is None and
- heads and fnodes are set. In that case, heads is the list of all
- heads currently in the repository (ordered from tip to oldest) and
- fnodes is a mapping from head to .hgtags filenode. If those two are
- set, caller is responsible for reading tag info from each head.'''
-
- try:
- cachefile = repo.opener('tags.cache', 'r')
- _debug(ui, 'reading tag cache from %s\n' % cachefile.name)
- except IOError:
- cachefile = None
-
- # The cache file consists of lines like
- # <headrev> <headnode> [<tagnode>]
- # where <headrev> and <headnode> redundantly identify a repository
- # head from the time the cache was written, and <tagnode> is the
- # filenode of .hgtags on that head. Heads with no .hgtags file will
- # have no <tagnode>. The cache is ordered from tip to oldest (which
- # is part of why <headrev> is there: a quick visual check is all
- # that's required to ensure correct order).
- #
- # This information is enough to let us avoid the most expensive part
- # of finding global tags, which is looking up <tagnode> in the
- # manifest for each head.
- cacherevs = [] # list of headrev
- cacheheads = [] # list of headnode
- cachefnode = {} # map headnode to filenode
- if cachefile:
- for line in cachefile:
- if line == "\n":
- break
- line = line.rstrip().split()
- cacherevs.append(int(line[0]))
- headnode = bin(line[1])
- cacheheads.append(headnode)
- if len(line) == 3:
- fnode = bin(line[2])
- cachefnode[headnode] = fnode
-
- tipnode = repo.changelog.tip()
- tiprev = len(repo.changelog) - 1
-
- # Case 1 (common): tip is the same, so nothing has changed.
- # (Unchanged tip trivially means no changesets have been added.
- # But, thanks to localrepository.destroyed(), it also means none
- # have been destroyed by strip or rollback.)
- if cacheheads and cacheheads[0] == tipnode and cacherevs[0] == tiprev:
- _debug(ui, "tag cache: tip unchanged\n")
- tags = _readtags(ui, repo, cachefile, cachefile.name)
- cachefile.close()
- return (None, None, tags, False)
- if cachefile:
- cachefile.close() # ignore rest of file
-
- repoheads = repo.heads()
- # Case 2 (uncommon): empty repo; get out quickly and don't bother
- # writing an empty cache.
- if repoheads == [nullid]:
- return ([], {}, {}, False)
-
- # Case 3 (uncommon): cache file missing or empty.
- if not cacheheads:
- _debug(ui, 'tag cache: cache file missing or empty\n')
-
- # Case 4 (uncommon): tip rev decreased. This should only happen
- # when we're called from localrepository.destroyed(). Refresh the
- # cache so future invocations will not see disappeared heads in the
- # cache.
- elif cacheheads and tiprev < cacherevs[0]:
- _debug(ui,
- 'tag cache: tip rev decremented (from %d to %d), '
- 'so we must be destroying nodes\n'
- % (cacherevs[0], tiprev))
-
- # Case 5 (common): tip has changed, so we've added/replaced heads.
- else:
- _debug(ui,
- 'tag cache: tip has changed (%d:%s); must find new heads\n'
- % (tiprev, short(tipnode)))
-
- # Luckily, the code to handle cases 3, 4, 5 is the same. So the
- # above if/elif/else can disappear once we're confident this thing
- # actually works and we don't need the debug output.
-
- # N.B. in case 4 (nodes destroyed), "new head" really means "newly
- # exposed".
- newheads = [head
- for head in repoheads
- if head not in set(cacheheads)]
- _debug(ui, 'tag cache: found %d head(s) not in cache: %s\n'
- % (len(newheads), map(short, newheads)))
-
- # Now we have to lookup the .hgtags filenode for every new head.
- # This is the most expensive part of finding tags, so performance
- # depends primarily on the size of newheads. Worst case: no cache
- # file, so newheads == repoheads.
- for head in newheads:
- cctx = repo[head]
- try:
- fnode = cctx.filenode('.hgtags')
- cachefnode[head] = fnode
- except error.LookupError:
- # no .hgtags file on this head
- pass
-
- # Caller has to iterate over all heads, but can use the filenodes in
- # cachefnode to get to each .hgtags revision quickly.
- return (repoheads, cachefnode, None, True)
-
-def _writetagcache(ui, repo, heads, tagfnode, cachetags):
-
- cachefile = repo.opener('tags.cache', 'w', atomictemp=True)
- _debug(ui, 'writing cache file %s\n' % cachefile.name)
-
- realheads = repo.heads() # for sanity checks below
- for head in heads:
- # temporary sanity checks; these can probably be removed
- # once this code has been in crew for a few weeks
- assert head in repo.changelog.nodemap, \
- 'trying to write non-existent node %s to tag cache' % short(head)
- assert head in realheads, \
- 'trying to write non-head %s to tag cache' % short(head)
- assert head != nullid, \
- 'trying to write nullid to tag cache'
-
- # This can't fail because of the first assert above. When/if we
- # remove that assert, we might want to catch LookupError here
- # and downgrade it to a warning.
- rev = repo.changelog.rev(head)
-
- fnode = tagfnode.get(head)
- if fnode:
- cachefile.write('%d %s %s\n' % (rev, hex(head), hex(fnode)))
- else:
- cachefile.write('%d %s\n' % (rev, hex(head)))
-
- # Tag names in the cache are in UTF-8 -- which is the whole reason
- # we keep them in UTF-8 throughout this module. If we converted
- # them local encoding on input, we would lose info writing them to
- # the cache.
- cachefile.write('\n')
- for (name, (node, hist)) in cachetags.iteritems():
- cachefile.write("%s %s\n" % (hex(node), name))
-
- cachefile.rename()
- cachefile.close()
diff --git a/sys/src/cmd/hg/mercurial/templatefilters.py b/sys/src/cmd/hg/mercurial/templatefilters.py
deleted file mode 100644
index 34358d26b..000000000
--- a/sys/src/cmd/hg/mercurial/templatefilters.py
+++ /dev/null
@@ -1,211 +0,0 @@
-# template-filters.py - common template expansion filters
-#
-# Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import cgi, re, os, time, urllib, textwrap
-import util, encoding
-
-def stringify(thing):
- '''turn nested template iterator into string.'''
- if hasattr(thing, '__iter__') and not isinstance(thing, str):
- return "".join([stringify(t) for t in thing if t is not None])
- return str(thing)
-
-agescales = [("second", 1),
- ("minute", 60),
- ("hour", 3600),
- ("day", 3600 * 24),
- ("week", 3600 * 24 * 7),
- ("month", 3600 * 24 * 30),
- ("year", 3600 * 24 * 365)]
-
-agescales.reverse()
-
-def age(date):
- '''turn a (timestamp, tzoff) tuple into an age string.'''
-
- def plural(t, c):
- if c == 1:
- return t
- return t + "s"
- def fmt(t, c):
- return "%d %s" % (c, plural(t, c))
-
- now = time.time()
- then = date[0]
- if then > now:
- return 'in the future'
-
- delta = max(1, int(now - then))
- for t, s in agescales:
- n = delta // s
- if n >= 2 or s == 1:
- return fmt(t, n)
-
-para_re = None
-space_re = None
-
-def fill(text, width):
- '''fill many paragraphs.'''
- global para_re, space_re
- if para_re is None:
- para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
- space_re = re.compile(r' +')
-
- def findparas():
- start = 0
- while True:
- m = para_re.search(text, start)
- if not m:
- w = len(text)
- while w > start and text[w-1].isspace(): w -= 1
- yield text[start:w], text[w:]
- break
- yield text[start:m.start(0)], m.group(1)
- start = m.end(1)
-
- return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
- for para, rest in findparas()])
-
-def firstline(text):
- '''return the first line of text'''
- try:
- return text.splitlines(True)[0].rstrip('\r\n')
- except IndexError:
- return ''
-
-def nl2br(text):
- '''replace raw newlines with xhtml line breaks.'''
- return text.replace('\n', '<br/>\n')
-
-def obfuscate(text):
- text = unicode(text, encoding.encoding, 'replace')
- return ''.join(['&#%d;' % ord(c) for c in text])
-
-def domain(author):
- '''get domain of author, or empty string if none.'''
- f = author.find('@')
- if f == -1: return ''
- author = author[f+1:]
- f = author.find('>')
- if f >= 0: author = author[:f]
- return author
-
-def person(author):
- '''get name of author, or else username.'''
- if not '@' in author: return author
- f = author.find('<')
- if f == -1: return util.shortuser(author)
- return author[:f].rstrip()
-
-def indent(text, prefix):
- '''indent each non-empty line of text after first with prefix.'''
- lines = text.splitlines()
- num_lines = len(lines)
- def indenter():
- for i in xrange(num_lines):
- l = lines[i]
- if i and l.strip():
- yield prefix
- yield l
- if i < num_lines - 1 or text.endswith('\n'):
- yield '\n'
- return "".join(indenter())
-
-def permissions(flags):
- if "l" in flags:
- return "lrwxrwxrwx"
- if "x" in flags:
- return "-rwxr-xr-x"
- return "-rw-r--r--"
-
-def xmlescape(text):
- text = (text
- .replace('&', '&amp;')
- .replace('<', '&lt;')
- .replace('>', '&gt;')
- .replace('"', '&quot;')
- .replace("'", '&#39;')) # &apos; invalid in HTML
- return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
-
-_escapes = [
- ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
- ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
-]
-
-def jsonescape(s):
- for k, v in _escapes:
- s = s.replace(k, v)
- return s
-
-def json(obj):
- if obj is None or obj is False or obj is True:
- return {None: 'null', False: 'false', True: 'true'}[obj]
- elif isinstance(obj, int) or isinstance(obj, float):
- return str(obj)
- elif isinstance(obj, str):
- return '"%s"' % jsonescape(obj)
- elif isinstance(obj, unicode):
- return json(obj.encode('utf-8'))
- elif hasattr(obj, 'keys'):
- out = []
- for k, v in obj.iteritems():
- s = '%s: %s' % (json(k), json(v))
- out.append(s)
- return '{' + ', '.join(out) + '}'
- elif hasattr(obj, '__iter__'):
- out = []
- for i in obj:
- out.append(json(i))
- return '[' + ', '.join(out) + ']'
- else:
- raise TypeError('cannot encode type %s' % obj.__class__.__name__)
-
-def stripdir(text):
- '''Treat the text as path and strip a directory level, if possible.'''
- dir = os.path.dirname(text)
- if dir == "":
- return os.path.basename(text)
- else:
- return dir
-
-def nonempty(str):
- return str or "(none)"
-
-filters = {
- "addbreaks": nl2br,
- "basename": os.path.basename,
- "stripdir": stripdir,
- "age": age,
- "date": lambda x: util.datestr(x),
- "domain": domain,
- "email": util.email,
- "escape": lambda x: cgi.escape(x, True),
- "fill68": lambda x: fill(x, width=68),
- "fill76": lambda x: fill(x, width=76),
- "firstline": firstline,
- "tabindent": lambda x: indent(x, '\t'),
- "hgdate": lambda x: "%d %d" % x,
- "isodate": lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2'),
- "isodatesec": lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2'),
- "json": json,
- "jsonescape": jsonescape,
- "localdate": lambda x: (x[0], util.makedate()[1]),
- "nonempty": nonempty,
- "obfuscate": obfuscate,
- "permissions": permissions,
- "person": person,
- "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2"),
- "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2"),
- "short": lambda x: x[:12],
- "shortdate": util.shortdate,
- "stringify": stringify,
- "strip": lambda x: x.strip(),
- "urlescape": lambda x: urllib.quote(x),
- "user": lambda x: util.shortuser(x),
- "stringescape": lambda x: x.encode('string_escape'),
- "xmlescape": xmlescape,
-}
diff --git a/sys/src/cmd/hg/mercurial/templater.py b/sys/src/cmd/hg/mercurial/templater.py
deleted file mode 100644
index 86a674fbb..000000000
--- a/sys/src/cmd/hg/mercurial/templater.py
+++ /dev/null
@@ -1,245 +0,0 @@
-# templater.py - template expansion for output
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import re, sys, os
-import util, config, templatefilters
-
-path = ['templates', '../templates']
-stringify = templatefilters.stringify
-
-def parsestring(s, quoted=True):
- '''parse a string using simple c-like syntax.
- string must be in quotes if quoted is True.'''
- if quoted:
- if len(s) < 2 or s[0] != s[-1]:
- raise SyntaxError(_('unmatched quotes'))
- return s[1:-1].decode('string_escape')
-
- return s.decode('string_escape')
-
-class engine(object):
- '''template expansion engine.
-
- template expansion works like this. a map file contains key=value
- pairs. if value is quoted, it is treated as string. otherwise, it
- is treated as name of template file.
-
- templater is asked to expand a key in map. it looks up key, and
- looks for strings like this: {foo}. it expands {foo} by looking up
- foo in map, and substituting it. expansion is recursive: it stops
- when there is no more {foo} to replace.
-
- expansion also allows formatting and filtering.
-
- format uses key to expand each item in list. syntax is
- {key%format}.
-
- filter uses function to transform value. syntax is
- {key|filter1|filter2|...}.'''
-
- template_re = re.compile(r'{([\w\|%]+)}|#([\w\|%]+)#')
-
- def __init__(self, loader, filters={}, defaults={}):
- self.loader = loader
- self.filters = filters
- self.defaults = defaults
- self.cache = {}
-
- def process(self, t, map):
- '''Perform expansion. t is name of map element to expand. map contains
- added elements for use during expansion. Is a generator.'''
- tmpl = self.loader(t)
- iters = [self._process(tmpl, map)]
- while iters:
- try:
- item = iters[0].next()
- except StopIteration:
- iters.pop(0)
- continue
- if isinstance(item, str):
- yield item
- elif item is None:
- yield ''
- elif hasattr(item, '__iter__'):
- iters.insert(0, iter(item))
- else:
- yield str(item)
-
- def _format(self, expr, get, map):
- key, format = expr.split('%')
- v = get(key)
- if not hasattr(v, '__iter__'):
- raise SyntaxError(_("error expanding '%s%%%s'") % (key, format))
- lm = map.copy()
- for i in v:
- lm.update(i)
- yield self.process(format, lm)
-
- def _filter(self, expr, get, map):
- if expr not in self.cache:
- parts = expr.split('|')
- val = parts[0]
- try:
- filters = [self.filters[f] for f in parts[1:]]
- except KeyError, i:
- raise SyntaxError(_("unknown filter '%s'") % i[0])
- def apply(get):
- x = get(val)
- for f in filters:
- x = f(x)
- return x
- self.cache[expr] = apply
- return self.cache[expr](get)
-
- def _process(self, tmpl, map):
- '''Render a template. Returns a generator.'''
-
- def get(key):
- v = map.get(key)
- if v is None:
- v = self.defaults.get(key, '')
- if hasattr(v, '__call__'):
- v = v(**map)
- return v
-
- while tmpl:
- m = self.template_re.search(tmpl)
- if not m:
- yield tmpl
- break
-
- start, end = m.span(0)
- variants = m.groups()
- expr = variants[0] or variants[1]
-
- if start:
- yield tmpl[:start]
- tmpl = tmpl[end:]
-
- if '%' in expr:
- yield self._format(expr, get, map)
- elif '|' in expr:
- yield self._filter(expr, get, map)
- else:
- yield get(expr)
-
-engines = {'default': engine}
-
-class templater(object):
-
- def __init__(self, mapfile, filters={}, defaults={}, cache={},
- minchunk=1024, maxchunk=65536):
- '''set up template engine.
- mapfile is name of file to read map definitions from.
- filters is dict of functions. each transforms a value into another.
- defaults is dict of default map definitions.'''
- self.mapfile = mapfile or 'template'
- self.cache = cache.copy()
- self.map = {}
- self.base = (mapfile and os.path.dirname(mapfile)) or ''
- self.filters = templatefilters.filters.copy()
- self.filters.update(filters)
- self.defaults = defaults
- self.minchunk, self.maxchunk = minchunk, maxchunk
- self.engines = {}
-
- if not mapfile:
- return
- if not os.path.exists(mapfile):
- raise util.Abort(_('style not found: %s') % mapfile)
-
- conf = config.config()
- conf.read(mapfile)
-
- for key, val in conf[''].items():
- if val[0] in "'\"":
- try:
- self.cache[key] = parsestring(val)
- except SyntaxError, inst:
- raise SyntaxError('%s: %s' %
- (conf.source('', key), inst.args[0]))
- else:
- val = 'default', val
- if ':' in val[1]:
- val = val[1].split(':', 1)
- self.map[key] = val[0], os.path.join(self.base, val[1])
-
- def __contains__(self, key):
- return key in self.cache or key in self.map
-
- def load(self, t):
- '''Get the template for the given template name. Use a local cache.'''
- if not t in self.cache:
- try:
- self.cache[t] = open(self.map[t][1]).read()
- except IOError, inst:
- raise IOError(inst.args[0], _('template file %s: %s') %
- (self.map[t][1], inst.args[1]))
- return self.cache[t]
-
- def __call__(self, t, **map):
- ttype = t in self.map and self.map[t][0] or 'default'
- proc = self.engines.get(ttype)
- if proc is None:
- proc = engines[ttype](self.load, self.filters, self.defaults)
- self.engines[ttype] = proc
-
- stream = proc.process(t, map)
- if self.minchunk:
- stream = util.increasingchunks(stream, min=self.minchunk,
- max=self.maxchunk)
- return stream
-
-def templatepath(name=None):
- '''return location of template file or directory (if no name).
- returns None if not found.'''
- normpaths = []
-
- # executable version (py2exe) doesn't support __file__
- if hasattr(sys, 'frozen'):
- module = sys.executable
- else:
- module = __file__
- for f in path:
- if f.startswith('/'):
- p = f
- else:
- fl = f.split('/')
- p = os.path.join(os.path.dirname(module), *fl)
- if name:
- p = os.path.join(p, name)
- if name and os.path.exists(p):
- return os.path.normpath(p)
- elif os.path.isdir(p):
- normpaths.append(os.path.normpath(p))
-
- return normpaths
-
-def stylemap(style, paths=None):
- """Return path to mapfile for a given style.
-
- Searches mapfile in the following locations:
- 1. templatepath/style/map
- 2. templatepath/map-style
- 3. templatepath/map
- """
-
- if paths is None:
- paths = templatepath()
- elif isinstance(paths, str):
- paths = [paths]
-
- locations = style and [os.path.join(style, "map"), "map-" + style] or []
- locations.append("map")
- for path in paths:
- for location in locations:
- mapfile = os.path.join(path, location)
- if os.path.isfile(mapfile):
- return mapfile
-
- raise RuntimeError("No hgweb templates found in %r" % paths)
diff --git a/sys/src/cmd/hg/mercurial/transaction.py b/sys/src/cmd/hg/mercurial/transaction.py
deleted file mode 100644
index 8eabacc54..000000000
--- a/sys/src/cmd/hg/mercurial/transaction.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# transaction.py - simple journalling scheme for mercurial
-#
-# This transaction scheme is intended to gracefully handle program
-# errors and interruptions. More serious failures like system crashes
-# can be recovered with an fsck-like tool. As the whole repository is
-# effectively log-structured, this should amount to simply truncating
-# anything that isn't referenced in the changelog.
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import os, errno
-import error
-
-def active(func):
- def _active(self, *args, **kwds):
- if self.count == 0:
- raise error.Abort(_(
- 'cannot use transaction when it is already committed/aborted'))
- return func(self, *args, **kwds)
- return _active
-
-def _playback(journal, report, opener, entries, unlink=True):
- for f, o, ignore in entries:
- if o or not unlink:
- try:
- opener(f, 'a').truncate(o)
- except:
- report(_("failed to truncate %s\n") % f)
- raise
- else:
- try:
- fn = opener(f).name
- os.unlink(fn)
- except IOError, inst:
- if inst.errno != errno.ENOENT:
- raise
- os.unlink(journal)
-
-class transaction(object):
- def __init__(self, report, opener, journal, after=None, createmode=None):
- self.journal = None
-
- self.count = 1
- self.report = report
- self.opener = opener
- self.after = after
- self.entries = []
- self.map = {}
- self.journal = journal
- self._queue = []
-
- self.file = open(self.journal, "w")
- if createmode is not None:
- os.chmod(self.journal, createmode & 0666)
-
- def __del__(self):
- if self.journal:
- if self.entries: self._abort()
- self.file.close()
-
- @active
- def startgroup(self):
- self._queue.append([])
-
- @active
- def endgroup(self):
- q = self._queue.pop()
- d = ''.join(['%s\0%d\n' % (x[0], x[1]) for x in q])
- self.entries.extend(q)
- self.file.write(d)
- self.file.flush()
-
- @active
- def add(self, file, offset, data=None):
- if file in self.map: return
-
- if self._queue:
- self._queue[-1].append((file, offset, data))
- return
-
- self.entries.append((file, offset, data))
- self.map[file] = len(self.entries) - 1
- # add enough data to the journal to do the truncate
- self.file.write("%s\0%d\n" % (file, offset))
- self.file.flush()
-
- @active
- def find(self, file):
- if file in self.map:
- return self.entries[self.map[file]]
- return None
-
- @active
- def replace(self, file, offset, data=None):
- '''
- replace can only replace already committed entries
- that are not pending in the queue
- '''
-
- if file not in self.map:
- raise KeyError(file)
- index = self.map[file]
- self.entries[index] = (file, offset, data)
- self.file.write("%s\0%d\n" % (file, offset))
- self.file.flush()
-
- @active
- def nest(self):
- self.count += 1
- return self
-
- def running(self):
- return self.count > 0
-
- @active
- def close(self):
- '''commit the transaction'''
- self.count -= 1
- if self.count != 0:
- return
- self.file.close()
- self.entries = []
- if self.after:
- self.after()
- else:
- os.unlink(self.journal)
- self.journal = None
-
- @active
- def abort(self):
- '''abort the transaction (generally called on error, or when the
- transaction is not explicitly committed before going out of
- scope)'''
- self._abort()
-
- def _abort(self):
- self.count = 0
- self.file.close()
-
- if not self.entries: return
-
- self.report(_("transaction abort!\n"))
-
- try:
- try:
- _playback(self.journal, self.report, self.opener, self.entries, False)
- self.report(_("rollback completed\n"))
- except:
- self.report(_("rollback failed - please run hg recover\n"))
- finally:
- self.journal = None
-
-
-def rollback(opener, file, report):
- entries = []
-
- for l in open(file).readlines():
- f, o = l.split('\0')
- entries.append((f, int(o), None))
-
- _playback(file, report, opener, entries)
diff --git a/sys/src/cmd/hg/mercurial/ui.py b/sys/src/cmd/hg/mercurial/ui.py
deleted file mode 100644
index bd122f74a..000000000
--- a/sys/src/cmd/hg/mercurial/ui.py
+++ /dev/null
@@ -1,381 +0,0 @@
-# ui.py - user interface bits for mercurial
-#
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import errno, getpass, os, socket, sys, tempfile, traceback
-import config, util, error
-
-_booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
- '0': False, 'no': False, 'false': False, 'off': False}
-
-class ui(object):
- def __init__(self, src=None):
- self._buffers = []
- self.quiet = self.verbose = self.debugflag = self._traceback = False
- self._reportuntrusted = True
- self._ocfg = config.config() # overlay
- self._tcfg = config.config() # trusted
- self._ucfg = config.config() # untrusted
- self._trustusers = set()
- self._trustgroups = set()
-
- if src:
- self._tcfg = src._tcfg.copy()
- self._ucfg = src._ucfg.copy()
- self._ocfg = src._ocfg.copy()
- self._trustusers = src._trustusers.copy()
- self._trustgroups = src._trustgroups.copy()
- self.fixconfig()
- else:
- # we always trust global config files
- for f in util.rcpath():
- self.readconfig(f, trust=True)
-
- def copy(self):
- return self.__class__(self)
-
- def _is_trusted(self, fp, f):
- st = util.fstat(fp)
- if util.isowner(st):
- return True
-
- tusers, tgroups = self._trustusers, self._trustgroups
- if '*' in tusers or '*' in tgroups:
- return True
-
- user = util.username(st.st_uid)
- group = util.groupname(st.st_gid)
- if user in tusers or group in tgroups or user == util.username():
- return True
-
- if self._reportuntrusted:
- self.warn(_('Not trusting file %s from untrusted '
- 'user %s, group %s\n') % (f, user, group))
- return False
-
- def readconfig(self, filename, root=None, trust=False,
- sections=None, remap=None):
- try:
- fp = open(filename)
- except IOError:
- if not sections: # ignore unless we were looking for something
- return
- raise
-
- cfg = config.config()
- trusted = sections or trust or self._is_trusted(fp, filename)
-
- try:
- cfg.read(filename, fp, sections=sections, remap=remap)
- except error.ConfigError, inst:
- if trusted:
- raise
- self.warn(_("Ignored: %s\n") % str(inst))
-
- if trusted:
- self._tcfg.update(cfg)
- self._tcfg.update(self._ocfg)
- self._ucfg.update(cfg)
- self._ucfg.update(self._ocfg)
-
- if root is None:
- root = os.path.expanduser('~')
- self.fixconfig(root=root)
-
- def fixconfig(self, root=None):
- # translate paths relative to root (or home) into absolute paths
- root = root or os.getcwd()
- for c in self._tcfg, self._ucfg, self._ocfg:
- for n, p in c.items('paths'):
- if p and "://" not in p and not os.path.isabs(p):
- c.set("paths", n, os.path.normpath(os.path.join(root, p)))
-
- # update ui options
- self.debugflag = self.configbool('ui', 'debug')
- self.verbose = self.debugflag or self.configbool('ui', 'verbose')
- self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
- if self.verbose and self.quiet:
- self.quiet = self.verbose = False
- self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
- self._traceback = self.configbool('ui', 'traceback', False)
-
- # update trust information
- self._trustusers.update(self.configlist('trusted', 'users'))
- self._trustgroups.update(self.configlist('trusted', 'groups'))
-
- def setconfig(self, section, name, value):
- for cfg in (self._ocfg, self._tcfg, self._ucfg):
- cfg.set(section, name, value)
- self.fixconfig()
-
- def _data(self, untrusted):
- return untrusted and self._ucfg or self._tcfg
-
- def configsource(self, section, name, untrusted=False):
- return self._data(untrusted).source(section, name) or 'none'
-
- def config(self, section, name, default=None, untrusted=False):
- value = self._data(untrusted).get(section, name, default)
- if self.debugflag and not untrusted and self._reportuntrusted:
- uvalue = self._ucfg.get(section, name)
- if uvalue is not None and uvalue != value:
- self.debug(_("ignoring untrusted configuration option "
- "%s.%s = %s\n") % (section, name, uvalue))
- return value
-
- def configbool(self, section, name, default=False, untrusted=False):
- v = self.config(section, name, None, untrusted)
- if v is None:
- return default
- if v.lower() not in _booleans:
- raise error.ConfigError(_("%s.%s not a boolean ('%s')")
- % (section, name, v))
- return _booleans[v.lower()]
-
- def configlist(self, section, name, default=None, untrusted=False):
- """Return a list of comma/space separated strings"""
- result = self.config(section, name, untrusted=untrusted)
- if result is None:
- result = default or []
- if isinstance(result, basestring):
- result = result.replace(",", " ").split()
- return result
-
- def has_section(self, section, untrusted=False):
- '''tell whether section exists in config.'''
- return section in self._data(untrusted)
-
- def configitems(self, section, untrusted=False):
- items = self._data(untrusted).items(section)
- if self.debugflag and not untrusted and self._reportuntrusted:
- for k, v in self._ucfg.items(section):
- if self._tcfg.get(section, k) != v:
- self.debug(_("ignoring untrusted configuration option "
- "%s.%s = %s\n") % (section, k, v))
- return items
-
- def walkconfig(self, untrusted=False):
- cfg = self._data(untrusted)
- for section in cfg.sections():
- for name, value in self.configitems(section, untrusted):
- yield section, name, str(value).replace('\n', '\\n')
-
- def username(self):
- """Return default username to be used in commits.
-
- Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
- and stop searching if one of these is set.
- If not found and ui.askusername is True, ask the user, else use
- ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
- """
- user = os.environ.get("HGUSER")
- if user is None:
- user = self.config("ui", "username")
- if user is None:
- user = os.environ.get("EMAIL")
- if user is None and self.configbool("ui", "askusername"):
- user = self.prompt(_("enter a commit username:"), default=None)
- if user is None:
- try:
- user = '%s@%s' % (util.getuser(), socket.getfqdn())
- self.warn(_("No username found, using '%s' instead\n") % user)
- except KeyError:
- pass
- if not user:
- raise util.Abort(_("Please specify a username."))
- if "\n" in user:
- raise util.Abort(_("username %s contains a newline\n") % repr(user))
- return user
-
- def shortuser(self, user):
- """Return a short representation of a user name or email address."""
- if not self.verbose: user = util.shortuser(user)
- return user
-
- def _path(self, loc):
- p = self.config('paths', loc)
- if p and '%%' in p:
- self.warn('(deprecated \'%%\' in path %s=%s from %s)\n' %
- (loc, p, self.configsource('paths', loc)))
- p = p.replace('%%', '%')
- return p
-
- def expandpath(self, loc, default=None):
- """Return repository location relative to cwd or from [paths]"""
- if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
- return loc
-
- path = self._path(loc)
- if not path and default is not None:
- path = self._path(default)
- return path or loc
-
- def pushbuffer(self):
- self._buffers.append([])
-
- def popbuffer(self):
- return "".join(self._buffers.pop())
-
- def write(self, *args):
- if self._buffers:
- self._buffers[-1].extend([str(a) for a in args])
- else:
- for a in args:
- sys.stdout.write(str(a))
-
- def write_err(self, *args):
- try:
- if not sys.stdout.closed: sys.stdout.flush()
- for a in args:
- sys.stderr.write(str(a))
- # stderr may be buffered under win32 when redirected to files,
- # including stdout.
- if not sys.stderr.closed: sys.stderr.flush()
- except IOError, inst:
- if inst.errno != errno.EPIPE:
- raise
-
- def flush(self):
- try: sys.stdout.flush()
- except: pass
- try: sys.stderr.flush()
- except: pass
-
- def interactive(self):
- i = self.configbool("ui", "interactive", None)
- if i is None:
- return sys.stdin.isatty()
- return i
-
- def _readline(self, prompt=''):
- if sys.stdin.isatty():
- try:
- # magically add command line editing support, where
- # available
- import readline
- # force demandimport to really load the module
- readline.read_history_file
- # windows sometimes raises something other than ImportError
- except Exception:
- pass
- line = raw_input(prompt)
- # When stdin is in binary mode on Windows, it can cause
- # raw_input() to emit an extra trailing carriage return
- if os.linesep == '\r\n' and line and line[-1] == '\r':
- line = line[:-1]
- return line
-
- def prompt(self, msg, default="y"):
- """Prompt user with msg, read response.
- If ui is not interactive, the default is returned.
- """
- if not self.interactive():
- self.write(msg, ' ', default, "\n")
- return default
- try:
- r = self._readline(msg + ' ')
- if not r:
- return default
- return r
- except EOFError:
- raise util.Abort(_('response expected'))
-
- def promptchoice(self, msg, choices, default=0):
- """Prompt user with msg, read response, and ensure it matches
- one of the provided choices. The index of the choice is returned.
- choices is a sequence of acceptable responses with the format:
- ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
- If ui is not interactive, the default is returned.
- """
- resps = [s[s.index('&')+1].lower() for s in choices]
- while True:
- r = self.prompt(msg, resps[default])
- if r.lower() in resps:
- return resps.index(r.lower())
- self.write(_("unrecognized response\n"))
-
-
- def getpass(self, prompt=None, default=None):
- if not self.interactive(): return default
- try:
- return getpass.getpass(prompt or _('password: '))
- except EOFError:
- raise util.Abort(_('response expected'))
- def status(self, *msg):
- if not self.quiet: self.write(*msg)
- def warn(self, *msg):
- self.write_err(*msg)
- def note(self, *msg):
- if self.verbose: self.write(*msg)
- def debug(self, *msg):
- if self.debugflag: self.write(*msg)
- def edit(self, text, user):
- (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
- text=True)
- try:
- f = os.fdopen(fd, "w")
- f.write(text)
- f.close()
-
- editor = self.geteditor()
-
- util.system("%s \"%s\"" % (editor, name),
- environ={'HGUSER': user},
- onerr=util.Abort, errprefix=_("edit failed"))
-
- f = open(name)
- t = f.read()
- f.close()
- finally:
- os.unlink(name)
-
- return t
-
- def traceback(self):
- '''print exception traceback if traceback printing enabled.
- only to call in exception handler. returns true if traceback
- printed.'''
- if self._traceback:
- traceback.print_exc()
- return self._traceback
-
- def geteditor(self):
- '''return editor to use'''
- return (os.environ.get("HGEDITOR") or
- self.config("ui", "editor") or
- os.environ.get("VISUAL") or
- os.environ.get("EDITOR", "vi"))
-
- def progress(self, topic, pos, item="", unit="", total=None):
- '''show a progress message
-
- With stock hg, this is simply a debug message that is hidden
- by default, but with extensions or GUI tools it may be
- visible. 'topic' is the current operation, 'item' is a
- non-numeric marker of the current position (ie the currently
- in-process file), 'pos' is the current numeric position (ie
- revision, bytes, etc.), units is a corresponding unit label,
- and total is the highest expected pos.
-
- Multiple nested topics may be active at a time. All topics
- should be marked closed by setting pos to None at termination.
- '''
-
- if pos == None or not self.debugflag:
- return
-
- if units:
- units = ' ' + units
- if item:
- item = ' ' + item
-
- if total:
- pct = 100.0 * pos / total
- ui.debug('%s:%s %s/%s%s (%4.2g%%)\n'
- % (topic, item, pos, total, units, pct))
- else:
- ui.debug('%s:%s %s%s\n' % (topic, item, pos, units))
diff --git a/sys/src/cmd/hg/mercurial/url.py b/sys/src/cmd/hg/mercurial/url.py
deleted file mode 100644
index 131d95550..000000000
--- a/sys/src/cmd/hg/mercurial/url.py
+++ /dev/null
@@ -1,533 +0,0 @@
-# url.py - HTTP handling for mercurial
-#
-# Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
-# Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
-from i18n import _
-import keepalive, util
-
-def hidepassword(url):
- '''hide user credential in a url string'''
- scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
- netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
- return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
-
-def removeauth(url):
- '''remove all authentication information from a url string'''
- scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
- netloc = netloc[netloc.find('@')+1:]
- return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
-
-def netlocsplit(netloc):
- '''split [user[:passwd]@]host[:port] into 4-tuple.'''
-
- a = netloc.find('@')
- if a == -1:
- user, passwd = None, None
- else:
- userpass, netloc = netloc[:a], netloc[a+1:]
- c = userpass.find(':')
- if c == -1:
- user, passwd = urllib.unquote(userpass), None
- else:
- user = urllib.unquote(userpass[:c])
- passwd = urllib.unquote(userpass[c+1:])
- c = netloc.find(':')
- if c == -1:
- host, port = netloc, None
- else:
- host, port = netloc[:c], netloc[c+1:]
- return host, port, user, passwd
-
-def netlocunsplit(host, port, user=None, passwd=None):
- '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
- if port:
- hostport = host + ':' + port
- else:
- hostport = host
- if user:
- if passwd:
- userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
- else:
- userpass = urllib.quote(user)
- return userpass + '@' + hostport
- return hostport
-
-_safe = ('abcdefghijklmnopqrstuvwxyz'
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- '0123456789' '_.-/')
-_safeset = None
-_hex = None
-def quotepath(path):
- '''quote the path part of a URL
-
- This is similar to urllib.quote, but it also tries to avoid
- quoting things twice (inspired by wget):
-
- >>> quotepath('abc def')
- 'abc%20def'
- >>> quotepath('abc%20def')
- 'abc%20def'
- >>> quotepath('abc%20 def')
- 'abc%20%20def'
- >>> quotepath('abc def%20')
- 'abc%20def%20'
- >>> quotepath('abc def%2')
- 'abc%20def%252'
- >>> quotepath('abc def%')
- 'abc%20def%25'
- '''
- global _safeset, _hex
- if _safeset is None:
- _safeset = set(_safe)
- _hex = set('abcdefABCDEF0123456789')
- l = list(path)
- for i in xrange(len(l)):
- c = l[i]
- if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
- pass
- elif c not in _safeset:
- l[i] = '%%%02X' % ord(c)
- return ''.join(l)
-
-class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
- def __init__(self, ui):
- urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
- self.ui = ui
-
- def find_user_password(self, realm, authuri):
- authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
- self, realm, authuri)
- user, passwd = authinfo
- if user and passwd:
- self._writedebug(user, passwd)
- return (user, passwd)
-
- if not user:
- auth = self.readauthtoken(authuri)
- if auth:
- user, passwd = auth.get('username'), auth.get('password')
- if not user or not passwd:
- if not self.ui.interactive():
- raise util.Abort(_('http authorization required'))
-
- self.ui.write(_("http authorization required\n"))
- self.ui.status(_("realm: %s\n") % realm)
- if user:
- self.ui.status(_("user: %s\n") % user)
- else:
- user = self.ui.prompt(_("user:"), default=None)
-
- if not passwd:
- passwd = self.ui.getpass()
-
- self.add_password(realm, authuri, user, passwd)
- self._writedebug(user, passwd)
- return (user, passwd)
-
- def _writedebug(self, user, passwd):
- msg = _('http auth: user %s, password %s\n')
- self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
-
- def readauthtoken(self, uri):
- # Read configuration
- config = dict()
- for key, val in self.ui.configitems('auth'):
- group, setting = key.split('.', 1)
- gdict = config.setdefault(group, dict())
- gdict[setting] = val
-
- # Find the best match
- scheme, hostpath = uri.split('://', 1)
- bestlen = 0
- bestauth = None
- for auth in config.itervalues():
- prefix = auth.get('prefix')
- if not prefix: continue
- p = prefix.split('://', 1)
- if len(p) > 1:
- schemes, prefix = [p[0]], p[1]
- else:
- schemes = (auth.get('schemes') or 'https').split()
- if (prefix == '*' or hostpath.startswith(prefix)) and \
- len(prefix) > bestlen and scheme in schemes:
- bestlen = len(prefix)
- bestauth = auth
- return bestauth
-
-class proxyhandler(urllib2.ProxyHandler):
- def __init__(self, ui):
- proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
- # XXX proxyauthinfo = None
-
- if proxyurl:
- # proxy can be proper url or host[:port]
- if not (proxyurl.startswith('http:') or
- proxyurl.startswith('https:')):
- proxyurl = 'http://' + proxyurl + '/'
- snpqf = urlparse.urlsplit(proxyurl)
- proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
- hpup = netlocsplit(proxynetloc)
-
- proxyhost, proxyport, proxyuser, proxypasswd = hpup
- if not proxyuser:
- proxyuser = ui.config("http_proxy", "user")
- proxypasswd = ui.config("http_proxy", "passwd")
-
- # see if we should use a proxy for this url
- no_list = [ "localhost", "127.0.0.1" ]
- no_list.extend([p.lower() for
- p in ui.configlist("http_proxy", "no")])
- no_list.extend([p.strip().lower() for
- p in os.getenv("no_proxy", '').split(',')
- if p.strip()])
- # "http_proxy.always" config is for running tests on localhost
- if ui.configbool("http_proxy", "always"):
- self.no_list = []
- else:
- self.no_list = no_list
-
- proxyurl = urlparse.urlunsplit((
- proxyscheme, netlocunsplit(proxyhost, proxyport,
- proxyuser, proxypasswd or ''),
- proxypath, proxyquery, proxyfrag))
- proxies = {'http': proxyurl, 'https': proxyurl}
- ui.debug(_('proxying through http://%s:%s\n') %
- (proxyhost, proxyport))
- else:
- proxies = {}
-
- # urllib2 takes proxy values from the environment and those
- # will take precedence if found, so drop them
- for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
- try:
- if env in os.environ:
- del os.environ[env]
- except OSError:
- pass
-
- urllib2.ProxyHandler.__init__(self, proxies)
- self.ui = ui
-
- def proxy_open(self, req, proxy, type_):
- host = req.get_host().split(':')[0]
- if host in self.no_list:
- return None
-
- # work around a bug in Python < 2.4.2
- # (it leaves a "\n" at the end of Proxy-authorization headers)
- baseclass = req.__class__
- class _request(baseclass):
- def add_header(self, key, val):
- if key.lower() == 'proxy-authorization':
- val = val.strip()
- return baseclass.add_header(self, key, val)
- req.__class__ = _request
-
- return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
-
-class httpsendfile(file):
- def __len__(self):
- return os.fstat(self.fileno()).st_size
-
-def _gen_sendfile(connection):
- def _sendfile(self, data):
- # send a file
- if isinstance(data, httpsendfile):
- # if auth required, some data sent twice, so rewind here
- data.seek(0)
- for chunk in util.filechunkiter(data):
- connection.send(self, chunk)
- else:
- connection.send(self, data)
- return _sendfile
-
-has_https = hasattr(urllib2, 'HTTPSHandler')
-if has_https:
- try:
- # avoid using deprecated/broken FakeSocket in python 2.6
- import ssl
- _ssl_wrap_socket = ssl.wrap_socket
- except ImportError:
- def _ssl_wrap_socket(sock, key_file, cert_file):
- ssl = socket.ssl(sock, key_file, cert_file)
- return httplib.FakeSocket(sock, ssl)
-
-class httpconnection(keepalive.HTTPConnection):
- # must be able to send big bundle as stream.
- send = _gen_sendfile(keepalive.HTTPConnection)
-
- def _proxytunnel(self):
- proxyheaders = dict(
- [(x, self.headers[x]) for x in self.headers
- if x.lower().startswith('proxy-')])
- self._set_hostport(self.host, self.port)
- self.send('CONNECT %s:%d HTTP/1.0\r\n' % (self.realhost, self.realport))
- for header in proxyheaders.iteritems():
- self.send('%s: %s\r\n' % header)
- self.send('\r\n')
-
- # majority of the following code is duplicated from
- # httplib.HTTPConnection as there are no adequate places to
- # override functions to provide the needed functionality
- res = self.response_class(self.sock,
- strict=self.strict,
- method=self._method)
-
- while True:
- version, status, reason = res._read_status()
- if status != httplib.CONTINUE:
- break
- while True:
- skip = res.fp.readline().strip()
- if not skip:
- break
- res.status = status
- res.reason = reason.strip()
-
- if res.status == 200:
- while True:
- line = res.fp.readline()
- if line == '\r\n':
- break
- return True
-
- if version == 'HTTP/1.0':
- res.version = 10
- elif version.startswith('HTTP/1.'):
- res.version = 11
- elif version == 'HTTP/0.9':
- res.version = 9
- else:
- raise httplib.UnknownProtocol(version)
-
- if res.version == 9:
- res.length = None
- res.chunked = 0
- res.will_close = 1
- res.msg = httplib.HTTPMessage(cStringIO.StringIO())
- return False
-
- res.msg = httplib.HTTPMessage(res.fp)
- res.msg.fp = None
-
- # are we using the chunked-style of transfer encoding?
- trenc = res.msg.getheader('transfer-encoding')
- if trenc and trenc.lower() == "chunked":
- res.chunked = 1
- res.chunk_left = None
- else:
- res.chunked = 0
-
- # will the connection close at the end of the response?
- res.will_close = res._check_close()
-
- # do we have a Content-Length?
- # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
- length = res.msg.getheader('content-length')
- if length and not res.chunked:
- try:
- res.length = int(length)
- except ValueError:
- res.length = None
- else:
- if res.length < 0: # ignore nonsensical negative lengths
- res.length = None
- else:
- res.length = None
-
- # does the body have a fixed length? (of zero)
- if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
- 100 <= status < 200 or # 1xx codes
- res._method == 'HEAD'):
- res.length = 0
-
- # if the connection remains open, and we aren't using chunked, and
- # a content-length was not provided, then assume that the connection
- # WILL close.
- if (not res.will_close and
- not res.chunked and
- res.length is None):
- res.will_close = 1
-
- self.proxyres = res
-
- return False
-
- def connect(self):
- if has_https and self.realhost: # use CONNECT proxy
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sock.connect((self.host, self.port))
- if self._proxytunnel():
- # we do not support client x509 certificates
- self.sock = _ssl_wrap_socket(self.sock, None, None)
- else:
- keepalive.HTTPConnection.connect(self)
-
- def getresponse(self):
- proxyres = getattr(self, 'proxyres', None)
- if proxyres:
- if proxyres.will_close:
- self.close()
- self.proxyres = None
- return proxyres
- return keepalive.HTTPConnection.getresponse(self)
-
-class httphandler(keepalive.HTTPHandler):
- def http_open(self, req):
- return self.do_open(httpconnection, req)
-
- def _start_transaction(self, h, req):
- if req.get_selector() == req.get_full_url(): # has proxy
- urlparts = urlparse.urlparse(req.get_selector())
- if urlparts[0] == 'https': # only use CONNECT for HTTPS
- if ':' in urlparts[1]:
- realhost, realport = urlparts[1].split(':')
- realport = int(realport)
- else:
- realhost = urlparts[1]
- realport = 443
-
- h.realhost = realhost
- h.realport = realport
- h.headers = req.headers.copy()
- h.headers.update(self.parent.addheaders)
- return keepalive.HTTPHandler._start_transaction(self, h, req)
-
- h.realhost = None
- h.realport = None
- h.headers = None
- return keepalive.HTTPHandler._start_transaction(self, h, req)
-
- def __del__(self):
- self.close_all()
-
-if has_https:
- class httpsconnection(httplib.HTTPSConnection):
- response_class = keepalive.HTTPResponse
- # must be able to send big bundle as stream.
- send = _gen_sendfile(httplib.HTTPSConnection)
-
- class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
- def __init__(self, ui):
- keepalive.KeepAliveHandler.__init__(self)
- urllib2.HTTPSHandler.__init__(self)
- self.ui = ui
- self.pwmgr = passwordmgr(self.ui)
-
- def https_open(self, req):
- self.auth = self.pwmgr.readauthtoken(req.get_full_url())
- return self.do_open(self._makeconnection, req)
-
- def _makeconnection(self, host, port=443, *args, **kwargs):
- keyfile = None
- certfile = None
-
- if args: # key_file
- keyfile = args.pop(0)
- if args: # cert_file
- certfile = args.pop(0)
-
- # if the user has specified different key/cert files in
- # hgrc, we prefer these
- if self.auth and 'key' in self.auth and 'cert' in self.auth:
- keyfile = self.auth['key']
- certfile = self.auth['cert']
-
- # let host port take precedence
- if ':' in host and '[' not in host or ']:' in host:
- host, port = host.rsplit(':', 1)
- port = int(port)
- if '[' in host:
- host = host[1:-1]
-
- return httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
-
-# In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
-# it doesn't know about the auth type requested. This can happen if
-# somebody is using BasicAuth and types a bad password.
-class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
- def http_error_auth_reqed(self, auth_header, host, req, headers):
- try:
- return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
- self, auth_header, host, req, headers)
- except ValueError, inst:
- arg = inst.args[0]
- if arg.startswith("AbstractDigestAuthHandler doesn't know "):
- return
- raise
-
-def getauthinfo(path):
- scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
- if not urlpath:
- urlpath = '/'
- if scheme != 'file':
- # XXX: why are we quoting the path again with some smart
- # heuristic here? Anyway, it cannot be done with file://
- # urls since path encoding is os/fs dependent (see
- # urllib.pathname2url() for details).
- urlpath = quotepath(urlpath)
- host, port, user, passwd = netlocsplit(netloc)
-
- # urllib cannot handle URLs with embedded user or passwd
- url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
- urlpath, query, frag))
- if user:
- netloc = host
- if port:
- netloc += ':' + port
- # Python < 2.4.3 uses only the netloc to search for a password
- authinfo = (None, (url, netloc), user, passwd or '')
- else:
- authinfo = None
- return url, authinfo
-
-handlerfuncs = []
-
-def opener(ui, authinfo=None):
- '''
- construct an opener suitable for urllib2
- authinfo will be added to the password manager
- '''
- handlers = [httphandler()]
- if has_https:
- handlers.append(httpshandler(ui))
-
- handlers.append(proxyhandler(ui))
-
- passmgr = passwordmgr(ui)
- if authinfo is not None:
- passmgr.add_password(*authinfo)
- user, passwd = authinfo[2:4]
- ui.debug(_('http auth: user %s, password %s\n') %
- (user, passwd and '*' * len(passwd) or 'not set'))
-
- handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
- httpdigestauthhandler(passmgr)))
- handlers.extend([h(ui, passmgr) for h in handlerfuncs])
- opener = urllib2.build_opener(*handlers)
-
- # 1.0 here is the _protocol_ version
- opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
- opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
- return opener
-
-scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
-
-def open(ui, url, data=None):
- scheme = None
- m = scheme_re.search(url)
- if m:
- scheme = m.group(1).lower()
- if not scheme:
- path = util.normpath(os.path.abspath(url))
- url = 'file://' + urllib.pathname2url(path)
- authinfo = None
- else:
- url, authinfo = getauthinfo(url)
- return opener(ui, authinfo).open(url, data)
diff --git a/sys/src/cmd/hg/mercurial/util.py b/sys/src/cmd/hg/mercurial/util.py
deleted file mode 100644
index 02ff43d7f..000000000
--- a/sys/src/cmd/hg/mercurial/util.py
+++ /dev/null
@@ -1,1284 +0,0 @@
-# util.py - Mercurial utility functions and platform specfic implementations
-#
-# Copyright 2005 K. Thananchayan <thananck@yahoo.com>
-# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-"""Mercurial utility functions and platform specfic implementations.
-
-This contains helper routines that are independent of the SCM core and
-hide platform-specific details from the core.
-"""
-
-from i18n import _
-import error, osutil
-import cStringIO, errno, re, shutil, sys, tempfile, traceback
-import os, stat, time, calendar, random, textwrap
-import imp
-
-# Python compatibility
-
-def sha1(s):
- return _fastsha1(s)
-
-def _fastsha1(s):
- # This function will import sha1 from hashlib or sha (whichever is
- # available) and overwrite itself with it on the first call.
- # Subsequent calls will go directly to the imported function.
- try:
- from hashlib import sha1 as _sha1
- except ImportError:
- from sha import sha as _sha1
- global _fastsha1, sha1
- _fastsha1 = sha1 = _sha1
- return _sha1(s)
-
-import subprocess
-closefds = os.name == 'posix'
-def popen2(cmd):
- # Setting bufsize to -1 lets the system decide the buffer size.
- # The default for bufsize is 0, meaning unbuffered. This leads to
- # poor performance on Mac OS X: http://bugs.python.org/issue4194
- p = subprocess.Popen(cmd, shell=True, bufsize=-1,
- close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- return p.stdin, p.stdout
-def popen3(cmd):
- p = subprocess.Popen(cmd, shell=True, bufsize=-1,
- close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- return p.stdin, p.stdout, p.stderr
-
-def version():
- """Return version information if available."""
- try:
- import __version__
- return __version__.version
- except ImportError:
- return 'unknown'
-
-# used by parsedate
-defaultdateformats = (
- '%Y-%m-%d %H:%M:%S',
- '%Y-%m-%d %I:%M:%S%p',
- '%Y-%m-%d %H:%M',
- '%Y-%m-%d %I:%M%p',
- '%Y-%m-%d',
- '%m-%d',
- '%m/%d',
- '%m/%d/%y',
- '%m/%d/%Y',
- '%a %b %d %H:%M:%S %Y',
- '%a %b %d %I:%M:%S%p %Y',
- '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
- '%b %d %H:%M:%S %Y',
- '%b %d %I:%M:%S%p %Y',
- '%b %d %H:%M:%S',
- '%b %d %I:%M:%S%p',
- '%b %d %H:%M',
- '%b %d %I:%M%p',
- '%b %d %Y',
- '%b %d',
- '%H:%M:%S',
- '%I:%M:%SP',
- '%H:%M',
- '%I:%M%p',
-)
-
-extendeddateformats = defaultdateformats + (
- "%Y",
- "%Y-%m",
- "%b",
- "%b %Y",
- )
-
-def cachefunc(func):
- '''cache the result of function calls'''
- # XXX doesn't handle keywords args
- cache = {}
- if func.func_code.co_argcount == 1:
- # we gain a small amount of time because
- # we don't need to pack/unpack the list
- def f(arg):
- if arg not in cache:
- cache[arg] = func(arg)
- return cache[arg]
- else:
- def f(*args):
- if args not in cache:
- cache[args] = func(*args)
- return cache[args]
-
- return f
-
-def lrucachefunc(func):
- '''cache most recent results of function calls'''
- cache = {}
- order = []
- if func.func_code.co_argcount == 1:
- def f(arg):
- if arg not in cache:
- if len(cache) > 20:
- del cache[order.pop(0)]
- cache[arg] = func(arg)
- else:
- order.remove(arg)
- order.append(arg)
- return cache[arg]
- else:
- def f(*args):
- if args not in cache:
- if len(cache) > 20:
- del cache[order.pop(0)]
- cache[args] = func(*args)
- else:
- order.remove(args)
- order.append(args)
- return cache[args]
-
- return f
-
-class propertycache(object):
- def __init__(self, func):
- self.func = func
- self.name = func.__name__
- def __get__(self, obj, type=None):
- result = self.func(obj)
- setattr(obj, self.name, result)
- return result
-
-def pipefilter(s, cmd):
- '''filter string S through command CMD, returning its output'''
- p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- pout, perr = p.communicate(s)
- return pout
-
-def tempfilter(s, cmd):
- '''filter string S through a pair of temporary files with CMD.
- CMD is used as a template to create the real command to be run,
- with the strings INFILE and OUTFILE replaced by the real names of
- the temporary files generated.'''
- inname, outname = None, None
- try:
- infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
- fp = os.fdopen(infd, 'wb')
- fp.write(s)
- fp.close()
- outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
- os.close(outfd)
- cmd = cmd.replace('INFILE', inname)
- cmd = cmd.replace('OUTFILE', outname)
- code = os.system(cmd)
- if sys.platform == 'OpenVMS' and code & 1:
- code = 0
- if code: raise Abort(_("command '%s' failed: %s") %
- (cmd, explain_exit(code)))
- return open(outname, 'rb').read()
- finally:
- try:
- if inname: os.unlink(inname)
- except: pass
- try:
- if outname: os.unlink(outname)
- except: pass
-
-filtertable = {
- 'tempfile:': tempfilter,
- 'pipe:': pipefilter,
- }
-
-def filter(s, cmd):
- "filter a string through a command that transforms its input to its output"
- for name, fn in filtertable.iteritems():
- if cmd.startswith(name):
- return fn(s, cmd[len(name):].lstrip())
- return pipefilter(s, cmd)
-
-def binary(s):
- """return true if a string is binary data"""
- return bool(s and '\0' in s)
-
-def increasingchunks(source, min=1024, max=65536):
- '''return no less than min bytes per chunk while data remains,
- doubling min after each chunk until it reaches max'''
- def log2(x):
- if not x:
- return 0
- i = 0
- while x:
- x >>= 1
- i += 1
- return i - 1
-
- buf = []
- blen = 0
- for chunk in source:
- buf.append(chunk)
- blen += len(chunk)
- if blen >= min:
- if min < max:
- min = min << 1
- nmin = 1 << log2(blen)
- if nmin > min:
- min = nmin
- if min > max:
- min = max
- yield ''.join(buf)
- blen = 0
- buf = []
- if buf:
- yield ''.join(buf)
-
-Abort = error.Abort
-
-def always(fn): return True
-def never(fn): return False
-
-def pathto(root, n1, n2):
- '''return the relative path from one place to another.
- root should use os.sep to separate directories
- n1 should use os.sep to separate directories
- n2 should use "/" to separate directories
- returns an os.sep-separated path.
-
- If n1 is a relative path, it's assumed it's
- relative to root.
- n2 should always be relative to root.
- '''
- if not n1: return localpath(n2)
- if os.path.isabs(n1):
- if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
- return os.path.join(root, localpath(n2))
- n2 = '/'.join((pconvert(root), n2))
- a, b = splitpath(n1), n2.split('/')
- a.reverse()
- b.reverse()
- while a and b and a[-1] == b[-1]:
- a.pop()
- b.pop()
- b.reverse()
- return os.sep.join((['..'] * len(a)) + b) or '.'
-
-def canonpath(root, cwd, myname):
- """return the canonical path of myname, given cwd and root"""
- if root == os.sep:
- rootsep = os.sep
- elif endswithsep(root):
- rootsep = root
- else:
- rootsep = root + os.sep
- name = myname
- if not os.path.isabs(name):
- name = os.path.join(root, cwd, name)
- name = os.path.normpath(name)
- audit_path = path_auditor(root)
- if name != rootsep and name.startswith(rootsep):
- name = name[len(rootsep):]
- audit_path(name)
- return pconvert(name)
- elif name == root:
- return ''
- else:
- # Determine whether `name' is in the hierarchy at or beneath `root',
- # by iterating name=dirname(name) until that causes no change (can't
- # check name == '/', because that doesn't work on windows). For each
- # `name', compare dev/inode numbers. If they match, the list `rel'
- # holds the reversed list of components making up the relative file
- # name we want.
- root_st = os.stat(root)
- rel = []
- while True:
- try:
- name_st = os.stat(name)
- except OSError:
- break
- if samestat(name_st, root_st):
- if not rel:
- # name was actually the same as root (maybe a symlink)
- return ''
- rel.reverse()
- name = os.path.join(*rel)
- audit_path(name)
- return pconvert(name)
- dirname, basename = os.path.split(name)
- rel.append(basename)
- if dirname == name:
- break
- name = dirname
-
- raise Abort('%s not under root' % myname)
-
-_hgexecutable = None
-
-def main_is_frozen():
- """return True if we are a frozen executable.
-
- The code supports py2exe (most common, Windows only) and tools/freeze
- (portable, not much used).
- """
- return (hasattr(sys, "frozen") or # new py2exe
- hasattr(sys, "importers") or # old py2exe
- imp.is_frozen("__main__")) # tools/freeze
-
-def hgexecutable():
- """return location of the 'hg' executable.
-
- Defaults to $HG or 'hg' in the search path.
- """
- if _hgexecutable is None:
- hg = os.environ.get('HG')
- if hg:
- set_hgexecutable(hg)
- elif main_is_frozen():
- set_hgexecutable(sys.executable)
- else:
- set_hgexecutable(find_exe('hg') or 'hg')
- return _hgexecutable
-
-def set_hgexecutable(path):
- """set location of the 'hg' executable"""
- global _hgexecutable
- _hgexecutable = path
-
-def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
- '''enhanced shell command execution.
- run with environment maybe modified, maybe in different dir.
-
- if command fails and onerr is None, return status. if ui object,
- print error message and return status, else raise onerr object as
- exception.'''
- def py2shell(val):
- 'convert python object into string that is useful to shell'
- if val is None or val is False:
- return '0'
- if val is True:
- return '1'
- return str(val)
- oldenv = {}
- for k in environ:
- oldenv[k] = os.environ.get(k)
- if cwd is not None:
- oldcwd = os.getcwd()
- origcmd = cmd
- if os.name == 'nt':
- cmd = '"%s"' % cmd
- try:
- for k, v in environ.iteritems():
- os.environ[k] = py2shell(v)
- os.environ['HG'] = hgexecutable()
- if cwd is not None and oldcwd != cwd:
- os.chdir(cwd)
- rc = os.system(cmd)
- if sys.platform == 'OpenVMS' and rc & 1:
- rc = 0
- if rc and onerr:
- errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
- explain_exit(rc)[0])
- if errprefix:
- errmsg = '%s: %s' % (errprefix, errmsg)
- try:
- onerr.warn(errmsg + '\n')
- except AttributeError:
- raise onerr(errmsg)
- return rc
- finally:
- for k, v in oldenv.iteritems():
- if v is None:
- del os.environ[k]
- else:
- os.environ[k] = v
- if cwd is not None and oldcwd != cwd:
- os.chdir(oldcwd)
-
-def checksignature(func):
- '''wrap a function with code to check for calling errors'''
- def check(*args, **kwargs):
- try:
- return func(*args, **kwargs)
- except TypeError:
- if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
- raise error.SignatureError
- raise
-
- return check
-
-# os.path.lexists is not available on python2.3
-def lexists(filename):
- "test whether a file with this name exists. does not follow symlinks"
- try:
- os.lstat(filename)
- except:
- return False
- return True
-
-def rename(src, dst):
- """forcibly rename a file"""
- try:
- os.rename(src, dst)
- except OSError, err: # FIXME: check err (EEXIST ?)
-
- # On windows, rename to existing file is not allowed, so we
- # must delete destination first. But if a file is open, unlink
- # schedules it for delete but does not delete it. Rename
- # happens immediately even for open files, so we rename
- # destination to a temporary name, then delete that. Then
- # rename is safe to do.
- # The temporary name is chosen at random to avoid the situation
- # where a file is left lying around from a previous aborted run.
- # The usual race condition this introduces can't be avoided as
- # we need the name to rename into, and not the file itself. Due
- # to the nature of the operation however, any races will at worst
- # lead to the rename failing and the current operation aborting.
-
- def tempname(prefix):
- for tries in xrange(10):
- temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
- if not os.path.exists(temp):
- return temp
- raise IOError, (errno.EEXIST, "No usable temporary filename found")
-
- temp = tempname(dst)
- os.rename(dst, temp)
- os.unlink(temp)
- os.rename(src, dst)
-
-def unlink(f):
- """unlink and remove the directory if it is empty"""
- os.unlink(f)
- # try removing directories that might now be empty
- try:
- os.removedirs(os.path.dirname(f))
- except OSError:
- pass
-
-def copyfile(src, dest):
- "copy a file, preserving mode and atime/mtime"
- if os.path.islink(src):
- try:
- os.unlink(dest)
- except:
- pass
- os.symlink(os.readlink(src), dest)
- else:
- try:
- shutil.copyfile(src, dest)
- shutil.copystat(src, dest)
- except shutil.Error, inst:
- raise Abort(str(inst))
-
-def copyfiles(src, dst, hardlink=None):
- """Copy a directory tree using hardlinks if possible"""
-
- if hardlink is None:
- hardlink = (os.stat(src).st_dev ==
- os.stat(os.path.dirname(dst)).st_dev)
-
- if os.path.isdir(src):
- os.mkdir(dst)
- for name, kind in osutil.listdir(src):
- srcname = os.path.join(src, name)
- dstname = os.path.join(dst, name)
- copyfiles(srcname, dstname, hardlink)
- else:
- if hardlink:
- try:
- os_link(src, dst)
- except (IOError, OSError):
- hardlink = False
- shutil.copy(src, dst)
- else:
- shutil.copy(src, dst)
-
-class path_auditor(object):
- '''ensure that a filesystem path contains no banned components.
- the following properties of a path are checked:
-
- - under top-level .hg
- - starts at the root of a windows drive
- - contains ".."
- - traverses a symlink (e.g. a/symlink_here/b)
- - inside a nested repository'''
-
- def __init__(self, root):
- self.audited = set()
- self.auditeddir = set()
- self.root = root
-
- def __call__(self, path):
- if path in self.audited:
- return
- normpath = os.path.normcase(path)
- parts = splitpath(normpath)
- if (os.path.splitdrive(path)[0]
- or parts[0].lower() in ('.hg', '.hg.', '')
- or os.pardir in parts):
- raise Abort(_("path contains illegal component: %s") % path)
- if '.hg' in path.lower():
- lparts = [p.lower() for p in parts]
- for p in '.hg', '.hg.':
- if p in lparts[1:]:
- pos = lparts.index(p)
- base = os.path.join(*parts[:pos])
- raise Abort(_('path %r is inside repo %r') % (path, base))
- def check(prefix):
- curpath = os.path.join(self.root, prefix)
- try:
- st = os.lstat(curpath)
- except OSError, err:
- # EINVAL can be raised as invalid path syntax under win32.
- # They must be ignored for patterns can be checked too.
- if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
- raise
- else:
- if stat.S_ISLNK(st.st_mode):
- raise Abort(_('path %r traverses symbolic link %r') %
- (path, prefix))
- elif (stat.S_ISDIR(st.st_mode) and
- os.path.isdir(os.path.join(curpath, '.hg'))):
- raise Abort(_('path %r is inside repo %r') %
- (path, prefix))
- parts.pop()
- prefixes = []
- while parts:
- prefix = os.sep.join(parts)
- if prefix in self.auditeddir:
- break
- check(prefix)
- prefixes.append(prefix)
- parts.pop()
-
- self.audited.add(path)
- # only add prefixes to the cache after checking everything: we don't
- # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
- self.auditeddir.update(prefixes)
-
-def nlinks(pathname):
- """Return number of hardlinks for the given file."""
- return os.lstat(pathname).st_nlink
-
-if hasattr(os, 'link'):
- os_link = os.link
-else:
- def os_link(src, dst):
- raise OSError(0, _("Hardlinks not supported"))
-
-def lookup_reg(key, name=None, scope=None):
- return None
-
-if os.name == 'nt':
- from windows import *
-else:
- from posix import *
-
-def makelock(info, pathname):
- try:
- return os.symlink(info, pathname)
- except OSError, why:
- if why.errno == errno.EEXIST:
- raise
- except AttributeError: # no symlink in os
- pass
-
- ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
- os.write(ld, info)
- os.close(ld)
-
-def readlock(pathname):
- try:
- return os.readlink(pathname)
- except OSError, why:
- if why.errno not in (errno.EINVAL, errno.ENOSYS):
- raise
- except AttributeError: # no symlink in os
- pass
- return posixfile(pathname).read()
-
-def fstat(fp):
- '''stat file object that may not have fileno method.'''
- try:
- return os.fstat(fp.fileno())
- except AttributeError:
- return os.stat(fp.name)
-
-# File system features
-
-def checkcase(path):
- """
- Check whether the given path is on a case-sensitive filesystem
-
- Requires a path (like /foo/.hg) ending with a foldable final
- directory component.
- """
- s1 = os.stat(path)
- d, b = os.path.split(path)
- p2 = os.path.join(d, b.upper())
- if path == p2:
- p2 = os.path.join(d, b.lower())
- try:
- s2 = os.stat(p2)
- if s2 == s1:
- return False
- return True
- except:
- return True
-
-_fspathcache = {}
-def fspath(name, root):
- '''Get name in the case stored in the filesystem
-
- The name is either relative to root, or it is an absolute path starting
- with root. Note that this function is unnecessary, and should not be
- called, for case-sensitive filesystems (simply because it's expensive).
- '''
- # If name is absolute, make it relative
- if name.lower().startswith(root.lower()):
- l = len(root)
- if name[l] == os.sep or name[l] == os.altsep:
- l = l + 1
- name = name[l:]
-
- if not os.path.exists(os.path.join(root, name)):
- return None
-
- seps = os.sep
- if os.altsep:
- seps = seps + os.altsep
- # Protect backslashes. This gets silly very quickly.
- seps.replace('\\','\\\\')
- pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
- dir = os.path.normcase(os.path.normpath(root))
- result = []
- for part, sep in pattern.findall(name):
- if sep:
- result.append(sep)
- continue
-
- if dir not in _fspathcache:
- _fspathcache[dir] = os.listdir(dir)
- contents = _fspathcache[dir]
-
- lpart = part.lower()
- for n in contents:
- if n.lower() == lpart:
- result.append(n)
- break
- else:
- # Cannot happen, as the file exists!
- result.append(part)
- dir = os.path.join(dir, lpart)
-
- return ''.join(result)
-
-def checkexec(path):
- """
- Check whether the given path is on a filesystem with UNIX-like exec flags
-
- Requires a directory (like /foo/.hg)
- """
-
- # VFAT on some Linux versions can flip mode but it doesn't persist
- # a FS remount. Frequently we can detect it if files are created
- # with exec bit on.
-
- try:
- EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
- fh, fn = tempfile.mkstemp("", "", path)
- try:
- os.close(fh)
- m = os.stat(fn).st_mode & 0777
- new_file_has_exec = m & EXECFLAGS
- os.chmod(fn, m ^ EXECFLAGS)
- exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
- finally:
- os.unlink(fn)
- except (IOError, OSError):
- # we don't care, the user probably won't be able to commit anyway
- return False
- return not (new_file_has_exec or exec_flags_cannot_flip)
-
-def checklink(path):
- """check whether the given path is on a symlink-capable filesystem"""
- # mktemp is not racy because symlink creation will fail if the
- # file already exists
- name = tempfile.mktemp(dir=path)
- try:
- os.symlink(".", name)
- os.unlink(name)
- return True
- except (OSError, AttributeError):
- return False
-
-def needbinarypatch():
- """return True if patches should be applied in binary mode by default."""
- return os.name == 'nt'
-
-def endswithsep(path):
- '''Check path ends with os.sep or os.altsep.'''
- return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
-
-def splitpath(path):
- '''Split path by os.sep.
- Note that this function does not use os.altsep because this is
- an alternative of simple "xxx.split(os.sep)".
- It is recommended to use os.path.normpath() before using this
- function if need.'''
- return path.split(os.sep)
-
-def gui():
- '''Are we running in a GUI?'''
- return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
-
-def mktempcopy(name, emptyok=False, createmode=None):
- """Create a temporary file with the same contents from name
-
- The permission bits are copied from the original file.
-
- If the temporary file is going to be truncated immediately, you
- can use emptyok=True as an optimization.
-
- Returns the name of the temporary file.
- """
- d, fn = os.path.split(name)
- fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
- os.close(fd)
- # Temporary files are created with mode 0600, which is usually not
- # what we want. If the original file already exists, just copy
- # its mode. Otherwise, manually obey umask.
- try:
- st_mode = os.lstat(name).st_mode & 0777
- except OSError, inst:
- if inst.errno != errno.ENOENT:
- raise
- st_mode = createmode
- if st_mode is None:
- st_mode = ~umask
- st_mode &= 0666
- os.chmod(temp, st_mode)
- if emptyok:
- return temp
- try:
- try:
- ifp = posixfile(name, "rb")
- except IOError, inst:
- if inst.errno == errno.ENOENT:
- return temp
- if not getattr(inst, 'filename', None):
- inst.filename = name
- raise
- ofp = posixfile(temp, "wb")
- for chunk in filechunkiter(ifp):
- ofp.write(chunk)
- ifp.close()
- ofp.close()
- except:
- try: os.unlink(temp)
- except: pass
- raise
- return temp
-
-class atomictempfile(object):
- """file-like object that atomically updates a file
-
- All writes will be redirected to a temporary copy of the original
- file. When rename is called, the copy is renamed to the original
- name, making the changes visible.
- """
- def __init__(self, name, mode, createmode):
- self.__name = name
- self._fp = None
- self.temp = mktempcopy(name, emptyok=('w' in mode),
- createmode=createmode)
- self._fp = posixfile(self.temp, mode)
-
- def __getattr__(self, name):
- return getattr(self._fp, name)
-
- def rename(self):
- if not self._fp.closed:
- self._fp.close()
- rename(self.temp, localpath(self.__name))
-
- def __del__(self):
- if not self._fp:
- return
- if not self._fp.closed:
- try:
- os.unlink(self.temp)
- except: pass
- self._fp.close()
-
-def makedirs(name, mode=None):
- """recursive directory creation with parent mode inheritance"""
- try:
- os.mkdir(name)
- if mode is not None:
- os.chmod(name, mode)
- return
- except OSError, err:
- if err.errno == errno.EEXIST:
- return
- if err.errno != errno.ENOENT:
- raise
- parent = os.path.abspath(os.path.dirname(name))
- makedirs(parent, mode)
- makedirs(name, mode)
-
-class opener(object):
- """Open files relative to a base directory
-
- This class is used to hide the details of COW semantics and
- remote file access from higher level code.
- """
- def __init__(self, base, audit=True):
- self.base = base
- if audit:
- self.audit_path = path_auditor(base)
- else:
- self.audit_path = always
- self.createmode = None
-
- @propertycache
- def _can_symlink(self):
- return checklink(self.base)
-
- def _fixfilemode(self, name):
- if self.createmode is None:
- return
- os.chmod(name, self.createmode & 0666)
-
- def __call__(self, path, mode="r", text=False, atomictemp=False):
- self.audit_path(path)
- f = os.path.join(self.base, path)
-
- if not text and "b" not in mode:
- mode += "b" # for that other OS
-
- nlink = -1
- if mode not in ("r", "rb"):
- try:
- nlink = nlinks(f)
- except OSError:
- nlink = 0
- d = os.path.dirname(f)
- if not os.path.isdir(d):
- makedirs(d, self.createmode)
- if atomictemp:
- return atomictempfile(f, mode, self.createmode)
- if nlink > 1:
- rename(mktempcopy(f), f)
- fp = posixfile(f, mode)
- if nlink == 0:
- self._fixfilemode(f)
- return fp
-
- def symlink(self, src, dst):
- self.audit_path(dst)
- linkname = os.path.join(self.base, dst)
- try:
- os.unlink(linkname)
- except OSError:
- pass
-
- dirname = os.path.dirname(linkname)
- if not os.path.exists(dirname):
- makedirs(dirname, self.createmode)
-
- if self._can_symlink:
- 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(dst, "w")
- f.write(src)
- f.close()
- self._fixfilemode(dst)
-
-class chunkbuffer(object):
- """Allow arbitrary sized chunks of data to be efficiently read from an
- iterator over chunks of arbitrary size."""
-
- def __init__(self, in_iter):
- """in_iter is the iterator that's iterating over the input chunks.
- targetsize is how big a buffer to try to maintain."""
- self.iter = iter(in_iter)
- self.buf = ''
- self.targetsize = 2**16
-
- def read(self, l):
- """Read L bytes of data from the iterator of chunks of data.
- Returns less than L bytes if the iterator runs dry."""
- if l > len(self.buf) and self.iter:
- # Clamp to a multiple of self.targetsize
- targetsize = max(l, self.targetsize)
- collector = cStringIO.StringIO()
- collector.write(self.buf)
- collected = len(self.buf)
- for chunk in self.iter:
- collector.write(chunk)
- collected += len(chunk)
- if collected >= targetsize:
- break
- if collected < targetsize:
- self.iter = False
- self.buf = collector.getvalue()
- if len(self.buf) == l:
- s, self.buf = str(self.buf), ''
- else:
- s, self.buf = self.buf[:l], buffer(self.buf, l)
- return s
-
-def filechunkiter(f, size=65536, limit=None):
- """Create a generator that produces the data in the file size
- (default 65536) bytes at a time, up to optional limit (default is
- to read all data). Chunks may be less than size bytes if the
- chunk is the last chunk in the file, or the file is a socket or
- some other type of file that sometimes reads less data than is
- requested."""
- assert size >= 0
- assert limit is None or limit >= 0
- while True:
- if limit is None: nbytes = size
- else: nbytes = min(limit, size)
- s = nbytes and f.read(nbytes)
- if not s: break
- if limit: limit -= len(s)
- yield s
-
-def makedate():
- lt = time.localtime()
- if lt[8] == 1 and time.daylight:
- tz = time.altzone
- else:
- tz = time.timezone
- return time.mktime(lt), tz
-
-def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
- """represent a (unixtime, offset) tuple as a localized time.
- unixtime is seconds since the epoch, and offset is the time zone's
- number of seconds away from UTC. if timezone is false, do not
- append time zone to string."""
- t, tz = date or makedate()
- if "%1" in format or "%2" in format:
- sign = (tz > 0) and "-" or "+"
- minutes = abs(tz) // 60
- format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
- format = format.replace("%2", "%02d" % (minutes % 60))
- s = time.strftime(format, time.gmtime(float(t) - tz))
- return s
-
-def shortdate(date=None):
- """turn (timestamp, tzoff) tuple into iso 8631 date."""
- return datestr(date, format='%Y-%m-%d')
-
-def strdate(string, format, defaults=[]):
- """parse a localized time string and return a (unixtime, offset) tuple.
- if the string cannot be parsed, ValueError is raised."""
- def timezone(string):
- tz = string.split()[-1]
- if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
- sign = (tz[0] == "+") and 1 or -1
- hours = int(tz[1:3])
- minutes = int(tz[3:5])
- return -sign * (hours * 60 + minutes) * 60
- if tz == "GMT" or tz == "UTC":
- return 0
- return None
-
- # NOTE: unixtime = localunixtime + offset
- offset, date = timezone(string), string
- if offset != None:
- date = " ".join(string.split()[:-1])
-
- # add missing elements from defaults
- for part in defaults:
- found = [True for p in part if ("%"+p) in format]
- if not found:
- date += "@" + defaults[part]
- format += "@%" + part[0]
-
- timetuple = time.strptime(date, format)
- localunixtime = int(calendar.timegm(timetuple))
- if offset is None:
- # local timezone
- unixtime = int(time.mktime(timetuple))
- offset = unixtime - localunixtime
- else:
- unixtime = localunixtime + offset
- return unixtime, offset
-
-def parsedate(date, formats=None, defaults=None):
- """parse a localized date/time string and return a (unixtime, offset) tuple.
-
- The date may be a "unixtime offset" string or in one of the specified
- formats. If the date already is a (unixtime, offset) tuple, it is returned.
- """
- if not date:
- return 0, 0
- if isinstance(date, tuple) and len(date) == 2:
- return date
- if not formats:
- formats = defaultdateformats
- date = date.strip()
- try:
- when, offset = map(int, date.split(' '))
- except ValueError:
- # fill out defaults
- if not defaults:
- defaults = {}
- now = makedate()
- for part in "d mb yY HI M S".split():
- if part not in defaults:
- if part[0] in "HMS":
- defaults[part] = "00"
- else:
- defaults[part] = datestr(now, "%" + part[0])
-
- for format in formats:
- try:
- when, offset = strdate(date, format, defaults)
- except (ValueError, OverflowError):
- pass
- else:
- break
- else:
- raise Abort(_('invalid date: %r ') % date)
- # validate explicit (probably user-specified) date and
- # time zone offset. values must fit in signed 32 bits for
- # current 32-bit linux runtimes. timezones go from UTC-12
- # to UTC+14
- if abs(when) > 0x7fffffff:
- raise Abort(_('date exceeds 32 bits: %d') % when)
- if offset < -50400 or offset > 43200:
- raise Abort(_('impossible time zone offset: %d') % offset)
- return when, offset
-
-def matchdate(date):
- """Return a function that matches a given date match specifier
-
- Formats include:
-
- '{date}' match a given date to the accuracy provided
-
- '<{date}' on or before a given date
-
- '>{date}' on or after a given date
-
- """
-
- def lower(date):
- d = dict(mb="1", d="1")
- return parsedate(date, extendeddateformats, d)[0]
-
- def upper(date):
- d = dict(mb="12", HI="23", M="59", S="59")
- for days in "31 30 29".split():
- try:
- d["d"] = days
- return parsedate(date, extendeddateformats, d)[0]
- except:
- pass
- d["d"] = "28"
- return parsedate(date, extendeddateformats, d)[0]
-
- date = date.strip()
- if date[0] == "<":
- when = upper(date[1:])
- return lambda x: x <= when
- elif date[0] == ">":
- when = lower(date[1:])
- return lambda x: x >= when
- elif date[0] == "-":
- try:
- days = int(date[1:])
- except ValueError:
- raise Abort(_("invalid day spec: %s") % date[1:])
- when = makedate()[0] - days * 3600 * 24
- return lambda x: x >= when
- elif " to " in date:
- a, b = date.split(" to ")
- start, stop = lower(a), upper(b)
- return lambda x: x >= start and x <= stop
- else:
- start, stop = lower(date), upper(date)
- return lambda x: x >= start and x <= stop
-
-def shortuser(user):
- """Return a short representation of a user name or email address."""
- f = user.find('@')
- if f >= 0:
- user = user[:f]
- f = user.find('<')
- if f >= 0:
- user = user[f+1:]
- f = user.find(' ')
- if f >= 0:
- user = user[:f]
- f = user.find('.')
- if f >= 0:
- user = user[:f]
- return user
-
-def email(author):
- '''get email of author.'''
- r = author.find('>')
- if r == -1: r = None
- return author[author.find('<')+1:r]
-
-def ellipsis(text, maxlength=400):
- """Trim string to at most maxlength (default: 400) characters."""
- if len(text) <= maxlength:
- return text
- else:
- return "%s..." % (text[:maxlength-3])
-
-def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
- '''yield every hg repository under path, recursively.'''
- def errhandler(err):
- if err.filename == path:
- raise err
- if followsym and hasattr(os.path, 'samestat'):
- def _add_dir_if_not_there(dirlst, dirname):
- match = False
- samestat = os.path.samestat
- dirstat = os.stat(dirname)
- for lstdirstat in dirlst:
- if samestat(dirstat, lstdirstat):
- match = True
- break
- if not match:
- dirlst.append(dirstat)
- return not match
- else:
- followsym = False
-
- if (seen_dirs is None) and followsym:
- seen_dirs = []
- _add_dir_if_not_there(seen_dirs, path)
- for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
- if '.hg' in dirs:
- yield root # found a repository
- qroot = os.path.join(root, '.hg', 'patches')
- if os.path.isdir(os.path.join(qroot, '.hg')):
- yield qroot # we have a patch queue repo here
- if recurse:
- # avoid recursing inside the .hg directory
- dirs.remove('.hg')
- else:
- dirs[:] = [] # don't descend further
- elif followsym:
- newdirs = []
- for d in dirs:
- fname = os.path.join(root, d)
- if _add_dir_if_not_there(seen_dirs, fname):
- if os.path.islink(fname):
- for hgname in walkrepos(fname, True, seen_dirs):
- yield hgname
- else:
- newdirs.append(d)
- dirs[:] = newdirs
-
-_rcpath = None
-
-def os_rcpath():
- '''return default os-specific hgrc search path'''
- path = system_rcpath()
- path.extend(user_rcpath())
- path = [os.path.normpath(f) for f in path]
- return path
-
-def rcpath():
- '''return hgrc search path. if env var HGRCPATH is set, use it.
- for each item in path, if directory, use files ending in .rc,
- else use item.
- make HGRCPATH empty to only look in .hg/hgrc of current repo.
- if no HGRCPATH, use default os-specific path.'''
- global _rcpath
- if _rcpath is None:
- if 'HGRCPATH' in os.environ:
- _rcpath = []
- for p in os.environ['HGRCPATH'].split(os.pathsep):
- if not p: continue
- if os.path.isdir(p):
- for f, kind in osutil.listdir(p):
- if f.endswith('.rc'):
- _rcpath.append(os.path.join(p, f))
- else:
- _rcpath.append(p)
- else:
- _rcpath = os_rcpath()
- return _rcpath
-
-def bytecount(nbytes):
- '''return byte count formatted as readable string, with units'''
-
- units = (
- (100, 1<<30, _('%.0f GB')),
- (10, 1<<30, _('%.1f GB')),
- (1, 1<<30, _('%.2f GB')),
- (100, 1<<20, _('%.0f MB')),
- (10, 1<<20, _('%.1f MB')),
- (1, 1<<20, _('%.2f MB')),
- (100, 1<<10, _('%.0f KB')),
- (10, 1<<10, _('%.1f KB')),
- (1, 1<<10, _('%.2f KB')),
- (1, 1, _('%.0f bytes')),
- )
-
- for multiplier, divisor, format in units:
- if nbytes >= divisor * multiplier:
- return format % (nbytes / float(divisor))
- return units[-1][2] % nbytes
-
-def drop_scheme(scheme, path):
- sc = scheme + ':'
- if path.startswith(sc):
- path = path[len(sc):]
- if path.startswith('//'):
- path = path[2:]
- return path
-
-def uirepr(s):
- # Avoid double backslash in Windows path repr()
- return repr(s).replace('\\\\', '\\')
-
-def termwidth():
- if 'COLUMNS' in os.environ:
- try:
- return int(os.environ['COLUMNS'])
- except ValueError:
- pass
- try:
- import termios, array, fcntl
- for dev in (sys.stdout, sys.stdin):
- try:
- try:
- fd = dev.fileno()
- except AttributeError:
- continue
- if not os.isatty(fd):
- continue
- arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
- return array.array('h', arri)[1]
- except ValueError:
- pass
- except ImportError:
- pass
- return 80
-
-def wrap(line, hangindent, width=None):
- if width is None:
- width = termwidth() - 2
- padding = '\n' + ' ' * hangindent
- return padding.join(textwrap.wrap(line, width=width - hangindent))
-
-def iterlines(iterator):
- for chunk in iterator:
- for line in chunk.splitlines():
- yield line
diff --git a/sys/src/cmd/hg/mercurial/verify.py b/sys/src/cmd/hg/mercurial/verify.py
deleted file mode 100644
index 17daf7662..000000000
--- a/sys/src/cmd/hg/mercurial/verify.py
+++ /dev/null
@@ -1,258 +0,0 @@
-# verify.py - repository integrity checking for Mercurial
-#
-# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from node import nullid, short
-from i18n import _
-import revlog, util, error
-
-def verify(repo):
- lock = repo.lock()
- try:
- return _verify(repo)
- finally:
- lock.release()
-
-def _verify(repo):
- mflinkrevs = {}
- filelinkrevs = {}
- filenodes = {}
- revisions = 0
- badrevs = set()
- errors = [0]
- warnings = [0]
- ui = repo.ui
- cl = repo.changelog
- mf = repo.manifest
-
- if not repo.cancopy():
- raise util.Abort(_("cannot verify bundle or remote repos"))
-
- def err(linkrev, msg, filename=None):
- if linkrev != None:
- badrevs.add(linkrev)
- else:
- linkrev = '?'
- msg = "%s: %s" % (linkrev, msg)
- if filename:
- msg = "%s@%s" % (filename, msg)
- ui.warn(" " + msg + "\n")
- errors[0] += 1
-
- def exc(linkrev, msg, inst, filename=None):
- if isinstance(inst, KeyboardInterrupt):
- ui.warn(_("interrupted"))
- raise
- err(linkrev, "%s: %s" % (msg, inst), filename)
-
- def warn(msg):
- ui.warn(msg + "\n")
- warnings[0] += 1
-
- def checklog(obj, name, linkrev):
- if not len(obj) and (havecl or havemf):
- err(linkrev, _("empty or missing %s") % name)
- return
-
- d = obj.checksize()
- if d[0]:
- err(None, _("data length off by %d bytes") % d[0], name)
- if d[1]:
- err(None, _("index contains %d extra bytes") % d[1], 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)
-
- def checkentry(obj, i, node, seen, linkrevs, f):
- lr = obj.linkrev(obj.rev(node))
- if lr < 0 or (havecl and lr not in linkrevs):
- if lr < 0 or lr >= len(cl):
- msg = _("rev %d points to nonexistent changeset %d")
- else:
- msg = _("rev %d points to unexpected changeset %d")
- err(None, msg % (i, lr), f)
- if linkrevs:
- warn(_(" (expected %s)") % " ".join(map(str, linkrevs)))
- lr = None # can't be trusted
-
- try:
- p1, p2 = obj.parents(node)
- if p1 not in seen and p1 != nullid:
- err(lr, _("unknown parent 1 %s of %s") %
- (short(p1), short(n)), f)
- if p2 not in seen and p2 != nullid:
- err(lr, _("unknown parent 2 %s of %s") %
- (short(p2), short(p1)), f)
- except Exception, inst:
- exc(lr, _("checking parents of %s") % short(node), inst, f)
-
- if node in seen:
- err(lr, _("duplicate revision %d (%d)") % (i, seen[n]), f)
- seen[n] = i
- return lr
-
- revlogv1 = cl.version != revlog.REVLOGV0
- if ui.verbose or not revlogv1:
- ui.status(_("repository uses revlog format %d\n") %
- (revlogv1 and 1 or 0))
-
- havecl = len(cl) > 0
- havemf = len(mf) > 0
-
- ui.status(_("checking changesets\n"))
- seen = {}
- checklog(cl, "changelog", 0)
- for i in repo:
- n = cl.node(i)
- checkentry(cl, i, n, seen, [i], "changelog")
-
- try:
- changes = cl.read(n)
- mflinkrevs.setdefault(changes[0], []).append(i)
- for f in changes[3]:
- filelinkrevs.setdefault(f, []).append(i)
- except Exception, inst:
- exc(i, _("unpacking changeset %s") % short(n), inst)
-
- ui.status(_("checking manifests\n"))
- seen = {}
- checklog(mf, "manifest", 0)
- for i in mf:
- n = mf.node(i)
- lr = checkentry(mf, i, n, seen, mflinkrevs.get(n, []), "manifest")
- if n in mflinkrevs:
- del mflinkrevs[n]
- else:
- err(lr, _("%s not in changesets") % short(n), "manifest")
-
- try:
- for f, fn in mf.readdelta(n).iteritems():
- if not f:
- err(lr, _("file without name in manifest"))
- elif f != "/dev/null":
- fns = filenodes.setdefault(f, {})
- if fn not in fns:
- fns[fn] = i
- except Exception, inst:
- exc(lr, _("reading manifest delta %s") % short(n), inst)
-
- ui.status(_("crosschecking files in changesets and manifests\n"))
-
- if havemf:
- for c,m in sorted([(c, m) for m in mflinkrevs for c in mflinkrevs[m]]):
- err(c, _("changeset refers to unknown manifest %s") % short(m))
- mflinkrevs = None # del is bad here due to scope issues
-
- for f in sorted(filelinkrevs):
- if f not in filenodes:
- lr = filelinkrevs[f][0]
- err(lr, _("in changeset but not in manifest"), f)
-
- if havecl:
- for f in sorted(filenodes):
- if f not in filelinkrevs:
- try:
- fl = repo.file(f)
- lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
- except:
- lr = None
- err(lr, _("in manifest but not in changeset"), f)
-
- ui.status(_("checking files\n"))
-
- storefiles = set()
- for f, f2, size in repo.store.datafiles():
- if not f:
- err(None, _("cannot decode filename '%s'") % f2)
- elif size > 0:
- storefiles.add(f)
-
- files = sorted(set(filenodes) | set(filelinkrevs))
- for f in files:
- try:
- linkrevs = filelinkrevs[f]
- except KeyError:
- # in manifest but not in changelog
- linkrevs = []
-
- if linkrevs:
- lr = linkrevs[0]
- else:
- lr = None
-
- try:
- fl = repo.file(f)
- except error.RevlogError, e:
- err(lr, _("broken revlog! (%s)") % e, f)
- continue
-
- for ff in fl.files():
- try:
- storefiles.remove(ff)
- except KeyError:
- err(lr, _("missing revlog!"), ff)
-
- checklog(fl, f, lr)
- seen = {}
- for i in fl:
- revisions += 1
- n = fl.node(i)
- lr = checkentry(fl, i, n, seen, linkrevs, f)
- if f in filenodes:
- if havemf and n not in filenodes[f]:
- err(lr, _("%s not in manifests") % (short(n)), f)
- else:
- del filenodes[f][n]
-
- # verify contents
- try:
- t = fl.read(n)
- rp = fl.renamed(n)
- if len(t) != fl.size(i):
- if len(fl.revision(n)) != fl.size(i):
- err(lr, _("unpacked size is %s, %s expected") %
- (len(t), fl.size(i)), f)
- except Exception, inst:
- exc(lr, _("unpacking %s") % short(n), inst, f)
-
- # check renames
- try:
- if rp:
- fl2 = repo.file(rp[0])
- if not len(fl2):
- err(lr, _("empty or missing copy source revlog %s:%s")
- % (rp[0], short(rp[1])), f)
- elif rp[1] == nullid:
- ui.note(_("warning: %s@%s: copy source"
- " revision is nullid %s:%s\n")
- % (f, lr, rp[0], short(rp[1])))
- else:
- fl2.rev(rp[1])
- except Exception, inst:
- exc(lr, _("checking rename of %s") % short(n), inst, f)
-
- # cross-check
- if f in filenodes:
- fns = [(mf.linkrev(l), n) for n,l in filenodes[f].iteritems()]
- for lr, node in sorted(fns):
- err(lr, _("%s in manifests not found") % short(node), f)
-
- for f in storefiles:
- warn(_("warning: orphan revlog '%s'") % f)
-
- ui.status(_("%d files, %d changesets, %d total revisions\n") %
- (len(files), len(cl), revisions))
- if warnings[0]:
- ui.warn(_("%d warnings encountered!\n") % warnings[0])
- if errors[0]:
- ui.warn(_("%d integrity errors encountered!\n") % errors[0])
- if badrevs:
- ui.warn(_("(first damaged changeset appears to be %d)\n")
- % min(badrevs))
- return 1
diff --git a/sys/src/cmd/hg/mercurial/win32.py b/sys/src/cmd/hg/mercurial/win32.py
deleted file mode 100644
index 08e35b011..000000000
--- a/sys/src/cmd/hg/mercurial/win32.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# win32.py - utility functions that use win32 API
-#
-# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-"""Utility functions that use win32 API.
-
-Mark Hammond's win32all package allows better functionality on
-Windows. This module overrides definitions in util.py. If not
-available, import of this module will fail, and generic code will be
-used.
-"""
-
-import win32api
-
-import errno, os, sys, pywintypes, win32con, win32file, win32process
-import winerror
-import osutil, encoding
-from win32com.shell import shell, shellcon
-
-def os_link(src, dst):
- try:
- win32file.CreateHardLink(dst, src)
- # CreateHardLink sometimes succeeds on mapped drives but
- # following nlinks() returns 1. Check it now and bail out.
- if nlinks(src) < 2:
- try:
- win32file.DeleteFile(dst)
- except:
- pass
- # Fake hardlinking error
- raise OSError(errno.EINVAL, 'Hardlinking not supported')
- except pywintypes.error, details:
- raise OSError(errno.EINVAL, 'target implements hardlinks improperly')
- except NotImplementedError: # Another fake error win Win98
- raise OSError(errno.EINVAL, 'Hardlinking not supported')
-
-def nlinks(pathname):
- """Return number of hardlinks for the given file."""
- try:
- fh = win32file.CreateFile(pathname,
- win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
- None, win32file.OPEN_EXISTING, 0, None)
- res = win32file.GetFileInformationByHandle(fh)
- fh.Close()
- return res[7]
- except pywintypes.error:
- return os.lstat(pathname).st_nlink
-
-def testpid(pid):
- '''return True if pid is still running or unable to
- determine, False otherwise'''
- try:
- handle = win32api.OpenProcess(
- win32con.PROCESS_QUERY_INFORMATION, False, pid)
- if handle:
- status = win32process.GetExitCodeProcess(handle)
- return status == win32con.STILL_ACTIVE
- except pywintypes.error, details:
- return details[0] != winerror.ERROR_INVALID_PARAMETER
- return True
-
-def lookup_reg(key, valname=None, scope=None):
- ''' Look up a key/value name in the Windows registry.
-
- valname: value name. If unspecified, the default value for the key
- is used.
- scope: optionally specify scope for registry lookup, this can be
- a sequence of scopes to look up in order. Default (CURRENT_USER,
- LOCAL_MACHINE).
- '''
- try:
- from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \
- QueryValueEx, OpenKey
- except ImportError:
- return None
-
- if scope is None:
- scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
- elif not isinstance(scope, (list, tuple)):
- scope = (scope,)
- for s in scope:
- try:
- val = QueryValueEx(OpenKey(s, key), valname)[0]
- # never let a Unicode string escape into the wild
- return encoding.tolocal(val.encode('UTF-8'))
- except EnvironmentError:
- pass
-
-def system_rcpath_win32():
- '''return default os-specific hgrc search path'''
- proc = win32api.GetCurrentProcess()
- try:
- # This will fail on windows < NT
- filename = win32process.GetModuleFileNameEx(proc, 0)
- except:
- filename = win32api.GetModuleFileName(0)
- # Use mercurial.ini found in directory with hg.exe
- progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
- if os.path.isfile(progrc):
- return [progrc]
- # else look for a system rcpath in the registry
- try:
- value = win32api.RegQueryValue(
- win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
- rcpath = []
- for p in value.split(os.pathsep):
- if p.lower().endswith('mercurial.ini'):
- rcpath.append(p)
- elif os.path.isdir(p):
- for f, kind in osutil.listdir(p):
- if f.endswith('.rc'):
- rcpath.append(os.path.join(p, f))
- return rcpath
- except pywintypes.error:
- return []
-
-def user_rcpath_win32():
- '''return os-specific hgrc search path to the user dir'''
- userdir = os.path.expanduser('~')
- if sys.getwindowsversion()[3] != 2 and userdir == '~':
- # We are on win < nt: fetch the APPDATA directory location and use
- # the parent directory as the user home dir.
- appdir = shell.SHGetPathFromIDList(
- shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
- userdir = os.path.dirname(appdir)
- return [os.path.join(userdir, 'mercurial.ini'),
- os.path.join(userdir, '.hgrc')]
-
-def getuser():
- '''return name of current user'''
- return win32api.GetUserName()
-
-def set_signal_handler_win32():
- """Register a termination handler for console events including
- CTRL+C. python signal handlers do not work well with socket
- operations.
- """
- def handler(event):
- win32process.ExitProcess(1)
- win32api.SetConsoleCtrlHandler(handler)
-
diff --git a/sys/src/cmd/hg/mercurial/windows.py b/sys/src/cmd/hg/mercurial/windows.py
deleted file mode 100644
index 5a903f1d6..000000000
--- a/sys/src/cmd/hg/mercurial/windows.py
+++ /dev/null
@@ -1,292 +0,0 @@
-# windows.py - Windows utility function implementations for Mercurial
-#
-# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-from i18n import _
-import osutil, error
-import errno, msvcrt, os, re, sys
-
-nulldev = 'NUL:'
-umask = 002
-
-# wrap osutil.posixfile to provide friendlier exceptions
-def posixfile(name, mode='r', buffering=-1):
- try:
- return osutil.posixfile(name, mode, buffering)
- except WindowsError, err:
- raise IOError(err.errno, err.strerror)
-posixfile.__doc__ = osutil.posixfile.__doc__
-
-class winstdout(object):
- '''stdout on windows misbehaves if sent through a pipe'''
-
- def __init__(self, fp):
- self.fp = fp
-
- def __getattr__(self, key):
- return getattr(self.fp, key)
-
- def close(self):
- try:
- self.fp.close()
- except: pass
-
- def write(self, s):
- try:
- # This is workaround for "Not enough space" error on
- # writing large size of data to console.
- limit = 16000
- l = len(s)
- start = 0
- self.softspace = 0;
- while start < l:
- end = start + limit
- self.fp.write(s[start:end])
- start = end
- except IOError, inst:
- if inst.errno != 0: raise
- self.close()
- raise IOError(errno.EPIPE, 'Broken pipe')
-
- def flush(self):
- try:
- return self.fp.flush()
- except IOError, inst:
- if inst.errno != errno.EINVAL: raise
- self.close()
- raise IOError(errno.EPIPE, 'Broken pipe')
-
-sys.stdout = winstdout(sys.stdout)
-
-def _is_win_9x():
- '''return true if run on windows 95, 98 or me.'''
- try:
- return sys.getwindowsversion()[3] == 1
- except AttributeError:
- return 'command' in os.environ.get('comspec', '')
-
-def openhardlinks():
- return not _is_win_9x() and "win32api" in globals()
-
-def system_rcpath():
- try:
- return system_rcpath_win32()
- except:
- return [r'c:\mercurial\mercurial.ini']
-
-def user_rcpath():
- '''return os-specific hgrc search path to the user dir'''
- try:
- path = user_rcpath_win32()
- except:
- home = os.path.expanduser('~')
- path = [os.path.join(home, 'mercurial.ini'),
- os.path.join(home, '.hgrc')]
- userprofile = os.environ.get('USERPROFILE')
- if userprofile:
- path.append(os.path.join(userprofile, 'mercurial.ini'))
- path.append(os.path.join(userprofile, '.hgrc'))
- return path
-
-def parse_patch_output(output_line):
- """parses the output produced by patch and returns the filename"""
- pf = output_line[14:]
- if pf[0] == '`':
- pf = pf[1:-1] # Remove the quotes
- return pf
-
-def sshargs(sshcmd, host, user, port):
- '''Build argument list for ssh or Plink'''
- pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
- args = user and ("%s@%s" % (user, host)) or host
- return port and ("%s %s %s" % (args, pflag, port)) or args
-
-def testpid(pid):
- '''return False if pid dead, True if running or not known'''
- return True
-
-def set_flags(f, l, x):
- pass
-
-def set_binary(fd):
- # When run without console, pipes may expose invalid
- # fileno(), usually set to -1.
- if hasattr(fd, 'fileno') and fd.fileno() >= 0:
- msvcrt.setmode(fd.fileno(), os.O_BINARY)
-
-def pconvert(path):
- return '/'.join(path.split(os.sep))
-
-def localpath(path):
- return path.replace('/', '\\')
-
-def normpath(path):
- return pconvert(os.path.normpath(path))
-
-def realpath(path):
- '''
- Returns the true, canonical file system path equivalent to the given
- path.
- '''
- # TODO: There may be a more clever way to do this that also handles other,
- # less common file systems.
- return os.path.normpath(os.path.normcase(os.path.realpath(path)))
-
-def samestat(s1, s2):
- return False
-
-# A sequence of backslashes is special iff it precedes a double quote:
-# - if there's an even number of backslashes, the double quote is not
-# quoted (i.e. it ends the quoted region)
-# - if there's an odd number of backslashes, the double quote is quoted
-# - in both cases, every pair of backslashes is unquoted into a single
-# backslash
-# (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
-# So, to quote a string, we must surround it in double quotes, double
-# the number of backslashes that preceed double quotes and add another
-# backslash before every double quote (being careful with the double
-# quote we've appended to the end)
-_quotere = None
-def shellquote(s):
- global _quotere
- if _quotere is None:
- _quotere = re.compile(r'(\\*)("|\\$)')
- return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
-
-def quotecommand(cmd):
- """Build a command string suitable for os.popen* calls."""
- # The extra quotes are needed because popen* runs the command
- # through the current COMSPEC. cmd.exe suppress enclosing quotes.
- return '"' + cmd + '"'
-
-def popen(command, mode='r'):
- # Work around "popen spawned process may not write to stdout
- # under windows"
- # http://bugs.python.org/issue1366
- command += " 2> %s" % nulldev
- return os.popen(quotecommand(command), mode)
-
-def explain_exit(code):
- return _("exited with status %d") % code, code
-
-# if you change this stub into a real check, please try to implement the
-# username and groupname functions above, too.
-def isowner(st):
- return True
-
-def find_exe(command):
- '''Find executable for command searching like cmd.exe does.
- If command is a basename then PATH is searched for command.
- PATH isn't searched if command is an absolute or relative path.
- An extension from PATHEXT is found and added if not present.
- If command isn't found None is returned.'''
- pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
- pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
- if os.path.splitext(command)[1].lower() in pathexts:
- pathexts = ['']
-
- def findexisting(pathcommand):
- 'Will append extension (if needed) and return existing file'
- for ext in pathexts:
- executable = pathcommand + ext
- if os.path.exists(executable):
- return executable
- return None
-
- if os.sep in command:
- return findexisting(command)
-
- for path in os.environ.get('PATH', '').split(os.pathsep):
- executable = findexisting(os.path.join(path, command))
- if executable is not None:
- return executable
- return None
-
-def set_signal_handler():
- try:
- set_signal_handler_win32()
- except NameError:
- pass
-
-def statfiles(files):
- '''Stat each file in files and yield stat or None if file does not exist.
- Cluster and cache stat per directory to minimize number of OS stat calls.'''
- ncase = os.path.normcase
- sep = os.sep
- dircache = {} # dirname -> filename -> status | None if file does not exist
- for nf in files:
- nf = ncase(nf)
- dir, base = os.path.split(nf)
- if not dir:
- dir = '.'
- cache = dircache.get(dir, None)
- if cache is None:
- try:
- dmap = dict([(ncase(n), s)
- for n, k, s in osutil.listdir(dir, True)])
- except OSError, err:
- # handle directory not found in Python version prior to 2.5
- # Python <= 2.4 returns native Windows code 3 in errno
- # Python >= 2.5 returns ENOENT and adds winerror field
- # EINVAL is raised if dir is not a directory.
- if err.errno not in (3, errno.ENOENT, errno.EINVAL,
- errno.ENOTDIR):
- raise
- dmap = {}
- cache = dircache.setdefault(dir, dmap)
- yield cache.get(base, None)
-
-def getuser():
- '''return name of current user'''
- raise error.Abort(_('user name not available - set USERNAME '
- 'environment variable'))
-
-def username(uid=None):
- """Return the name of the user with the given uid.
-
- If uid is None, return the name of the current user."""
- return None
-
-def groupname(gid=None):
- """Return the name of the group with the given gid.
-
- If gid is None, return the name of the current group."""
- return None
-
-def _removedirs(name):
- """special version of os.removedirs that does not remove symlinked
- directories or junction points if they actually contain files"""
- if osutil.listdir(name):
- return
- os.rmdir(name)
- head, tail = os.path.split(name)
- if not tail:
- head, tail = os.path.split(head)
- while head and tail:
- try:
- if osutil.listdir(name):
- return
- os.rmdir(head)
- except:
- break
- head, tail = os.path.split(head)
-
-def unlink(f):
- """unlink and remove the directory if it is empty"""
- os.unlink(f)
- # try removing directories that might now be empty
- try:
- _removedirs(os.path.dirname(f))
- except OSError:
- pass
-
-try:
- # override functions with win32 versions if possible
- from win32 import *
-except ImportError:
- pass
-
-expandglobs = True