--- a/contrib/buildrpm Fri Jul 06 08:48:28 2007 +0200
+++ b/contrib/buildrpm Fri Jul 06 08:55:36 2007 +0200
@@ -29,7 +29,7 @@
version=`hg tags | perl -e 'while(<STDIN>){if(/^(\d\S+)/){print$1;exit}}'`
# Compute the release number as the difference in revision numbers
# between the tip and the most recent tag.
-release=`hg tags | perl -e 'while(<STDIN>){/^(\S+)\s+(\d+)/;if($1eq"tip"){$t=$2}else{print$t-$2+1;exit}}'`
+release=`hg tags | perl -e 'while(<STDIN>){($tag,$id)=/^(\S+)\s+(\d+)/;if($tag eq "tip"){$tip = $id}elsif($tag=~/^\d/){print $tip-$id+1;exit}}'`
tip=`hg -q tip`
# Beat up the spec file
@@ -40,6 +40,19 @@
-e 's,^%setup.*,,' \
$specfile > $tmpspec
+cat <<EOF >> $tmpspec
+%changelog
+* `date +'%a %b %d %Y'` `hg showconfig ui.username` $version-$release
+- Automatically built via $0
+
+EOF
+hg log \
+ --template '* {date|rfc822date} {author}\n- {desc|firstline}\n\n' \
+ .hgtags \
+ | sed -e 's/^\(\* [MTWFS][a-z][a-z]\), \([0-3][0-9]\) \([A-Z][a-z][a-z]\) /\1 \3 \2 /' \
+ -e '/^\* [MTWFS][a-z][a-z] /{s/ [012][0-9]:[0-9][0-9]:[0-9][0-9] [+-][0-9]\{4\}//}' \
+ >> $tmpspec
+
rpmbuild --define "_topdir $rpmdir" -bb $tmpspec
if [ $? = 0 ]; then
rm -rf $tmpspec $rpmdir/BUILD
--- a/contrib/macosx/Readme.html Fri Jul 06 08:48:28 2007 +0200
+++ b/contrib/macosx/Readme.html Fri Jul 06 08:55:36 2007 +0200
@@ -19,10 +19,14 @@
<p class="p2"><br></p>
<p class="p3">This is <i>not</i> a stand-alone version of Mercurial.</p>
<p class="p2"><br></p>
-<p class="p3">To use it, you must have the Universal MacPython 2.4.3 from <a href="http://www.python.org">www.python.org</a> installed.</p>
+<p class="p3">To use it, you must have the appropriate version of Universal MacPython from <a href="http://www.python.org">www.python.org</a> installed.</p>
<p class="p2"><br></p>
-<p class="p3">You can download MacPython 2.4.3 from here:</p>
-<p class="p4"><span class="s1"><a href="http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg">http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg</a></span></p>
+<p class="p3">You can find more information and download MacPython from here:</p>
+<p class="p4"><span class="s1"><a href="http://www.python.org/download">http://www.python.org/download</a></span></p>
+<p class="p2"><br></p>
+<p class="p3">Or direct links to the latest version are:</p>
+<p class="p4"><span class="s1"><a href="http://www.python.org/ftp/python/2.5.1/python-2.5.1-macosx.dmg">Python 2.5.1 for Macintosh OS X</a></span></p>
+<p class="p4"><span class="s1"><a href="http://www.python.org/ftp/python/2.4.4/python-2.4.4-macosx2006-10-18.dmg">Python 2.4.4 for Macintosh OS X</a></span></p>
<p class="p2"><br></p>
<p class="p1"><b>After you install</b></p>
<p class="p2"><br></p>
--- a/contrib/mercurial.spec Fri Jul 06 08:48:28 2007 +0200
+++ b/contrib/mercurial.spec Fri Jul 06 08:55:36 2007 +0200
@@ -8,6 +8,17 @@
URL: http://www.selenic.com/mercurial
BuildRoot: /tmp/build.%{name}-%{version}-%{release}
+# From the README:
+#
+# Note: some distributions fails to include bits of distutils by
+# default, you'll need python-dev to install. You'll also need a C
+# compiler and a 3-way merge tool like merge, tkdiff, or kdiff3.
+#
+# python-devel provides an adequate python-dev. The merge tool is a
+# run-time dependency.
+#
+BuildRequires: python >= 2.3, python-devel, make, gcc, asciidoc, xmlto
+
%define pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
%define pythonlib %{_libdir}/python%{pythonver}/site-packages/%{name}
%define hgext %{_libdir}/python%{pythonver}/site-packages/hgext
@@ -21,23 +32,51 @@
%setup -q
%build
-python setup.py build
+make all
%install
-python setup.py install --root $RPM_BUILD_ROOT
+python setup.py install --root $RPM_BUILD_ROOT --prefix %{_prefix}
+make install-doc DESTDIR=$RPM_BUILD_ROOT MANDIR=%{_mandir}
+
+install contrib/hgk $RPM_BUILD_ROOT%{_bindir}
+install contrib/convert-repo $RPM_BUILD_ROOT%{_bindir}/mercurial-convert-repo
+install contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}
+install contrib/git-viz/{hg-viz,git-rev-tree} $RPM_BUILD_ROOT%{_bindir}
+
+bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
+mkdir -p $bash_completion_dir
+install contrib/bash_completion $bash_completion_dir/mercurial.sh
+
+zsh_completion_dir=$RPM_BUILD_ROOT%{_datadir}/zsh/site-functions
+mkdir -p $zsh_completion_dir
+install contrib/zsh_completion $zsh_completion_dir/_mercurial
+
+lisp_dir=$RPM_BUILD_ROOT%{_datadir}/emacs/site-lisp
+mkdir -p $lisp_dir
+install contrib/mercurial.el $lisp_dir
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root,-)
-%doc doc/* *.cgi
+%doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html doc/ja *.cgi
+%{_mandir}/man?/hg*.gz
%dir %{pythonlib}
%dir %{hgext}
+%{_sysconfdir}/bash_completion.d/mercurial.sh
+%{_datadir}/zsh/site-functions/_mercurial
+%{_datadir}/emacs/site-lisp/mercurial.el
+%{_bindir}/hg
+%{_bindir}/hgk
%{_bindir}/hgmerge
-%{_bindir}/hg
+%{_bindir}/hg-ssh
+%{_bindir}/hg-viz
+%{_bindir}/git-rev-tree
+%{_bindir}/mercurial-convert-repo
%{pythonlib}/templates
%{pythonlib}/*.py*
%{pythonlib}/hgweb/*.py*
%{pythonlib}/*.so
%{hgext}/*.py*
+%{hgext}/convert/*.py*
--- a/contrib/win32/mercurial.ini Fri Jul 06 08:48:28 2007 +0200
+++ b/contrib/win32/mercurial.ini Fri Jul 06 08:55:36 2007 +0200
@@ -1,41 +1,41 @@
-; System-wide Mercurial config file. To override these settings on a
-; per-user basis, please edit the following file instead, where
-; USERNAME is your Windows user name:
-; C:\Documents and Settings\USERNAME\Mercurial.ini
-
-[ui]
-editor = notepad
-
-; By default, we try to encode and decode all files that do not
-; contain ASCII NUL characters. What this means is that we try to set
-; line endings to Windows style on update, and to Unix style on
-; commit. This lets us cooperate with Linux and Unix users, so
-; everybody sees files with their native line endings.
-
-[extensions]
-; The win32text extension is available and installed by default. It
-; provides built-in Python hooks to perform line ending conversions.
-; This is normally much faster than running an external program.
-hgext.win32text =
-
-
-[encode]
-; Encode files that don't contain NUL characters.
-
-; ** = cleverencode:
-
-; Alternatively, you can explicitly specify each file extension that
-; you want encoded (any you omit will be left untouched), like this:
-
-; *.txt = dumbencode:
-
-
-[decode]
-; Decode files that don't contain NUL characters.
-
-; ** = cleverdecode:
-
-; Alternatively, you can explicitly specify each file extension that
-; you want decoded (any you omit will be left untouched), like this:
-
-; **.txt = dumbdecode:
+; System-wide Mercurial config file. To override these settings on a
+; per-user basis, please edit the following file instead, where
+; USERNAME is your Windows user name:
+; C:\Documents and Settings\USERNAME\Mercurial.ini
+
+[ui]
+editor = notepad
+
+; By default, we try to encode and decode all files that do not
+; contain ASCII NUL characters. What this means is that we try to set
+; line endings to Windows style on update, and to Unix style on
+; commit. This lets us cooperate with Linux and Unix users, so
+; everybody sees files with their native line endings.
+
+[extensions]
+; The win32text extension is available and installed by default. It
+; provides built-in Python hooks to perform line ending conversions.
+; This is normally much faster than running an external program.
+hgext.win32text =
+
+
+[encode]
+; Encode files that don't contain NUL characters.
+
+; ** = cleverencode:
+
+; Alternatively, you can explicitly specify each file extension that
+; you want encoded (any you omit will be left untouched), like this:
+
+; *.txt = dumbencode:
+
+
+[decode]
+; Decode files that don't contain NUL characters.
+
+; ** = cleverdecode:
+
+; Alternatively, you can explicitly specify each file extension that
+; you want decoded (any you omit will be left untouched), like this:
+
+; **.txt = dumbdecode:
--- a/doc/Makefile Fri Jul 06 08:48:28 2007 +0200
+++ b/doc/Makefile Fri Jul 06 08:55:36 2007 +0200
@@ -36,8 +36,8 @@
install: man
for i in $(MAN) ; do \
subdir=`echo $$i | sed -n 's/..*\.\([0-9]\)$$/man\1/p'` ; \
- mkdir -p $(DESTDIR)/$(MANDIR)/$$subdir ; \
- $(INSTALL) $$i $(DESTDIR)/$(MANDIR)/$$subdir ; \
+ mkdir -p $(DESTDIR)$(MANDIR)/$$subdir ; \
+ $(INSTALL) $$i $(DESTDIR)$(MANDIR)/$$subdir ; \
done
clean:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/alias.py Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,77 @@
+# Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
+# This file is published under the GNU GPL.
+
+'''allow user-defined command aliases
+
+To use, create entries in your hgrc of the form
+
+[alias]
+mycmd = cmd --args
+'''
+
+from mercurial.cmdutil import findcmd, UnknownCommand, AmbiguousCommand
+from mercurial import commands
+
+cmdtable = {}
+
+class RecursiveCommand(Exception): pass
+
+class lazycommand(object):
+ '''defer command lookup until needed, so that extensions loaded
+ after alias can be aliased'''
+ def __init__(self, ui, name, target):
+ self._ui = ui
+ self._name = name
+ self._target = target
+ self._cmd = None
+
+ def __len__(self):
+ self._resolve()
+ return len(self._cmd)
+
+ def __getitem__(self, key):
+ self._resolve()
+ return self._cmd[key]
+
+ def __iter__(self):
+ self._resolve()
+ return self._cmd.__iter__()
+
+ def _resolve(self):
+ if self._cmd is not None:
+ return
+
+ try:
+ self._cmd = findcmd(self._ui, self._target)[1]
+ if self._cmd == self:
+ raise RecursiveCommand()
+ if self._target in commands.norepo.split(' '):
+ commands.norepo += ' %s' % self._name
+ return
+ except UnknownCommand:
+ msg = '*** [alias] %s: command %s is unknown' % \
+ (self._name, self._target)
+ except AmbiguousCommand:
+ msg = '*** [alias] %s: command %s is ambiguous' % \
+ (self._name, self._target)
+ except RecursiveCommand:
+ msg = '*** [alias] %s: circular dependency on %s' % \
+ (self._name, self._target)
+ def nocmd(*args, **opts):
+ self._ui.warn(msg + '\n')
+ return 1
+ nocmd.__doc__ = msg
+ self._cmd = (nocmd, [], '')
+ commands.norepo += ' %s' % self._name
+
+def uisetup(ui):
+ for cmd, target in ui.configitems('alias'):
+ if not target:
+ ui.warn('*** [alias] %s: no definition\n' % cmd)
+ continue
+ args = target.split(' ')
+ tcmd = args.pop(0)
+ if args:
+ pui = ui.parentui or ui
+ pui.setconfig('defaults', cmd, ' '.join(args))
+ cmdtable[cmd] = lazycommand(ui, cmd, tcmd)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/children.py Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,41 @@
+# Mercurial extension to provide the 'hg children' command
+#
+# Copyright 2007 by Intevation GmbH <intevation@intevation.de>
+# Author(s):
+# Thomas Arendsen Hein <thomas@intevation.de>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from mercurial import cmdutil
+from mercurial.i18n import _
+
+
+def children(ui, repo, file_=None, **opts):
+ """show the children of the given or working dir revision
+
+ Print the children of the working directory's revisions.
+ If a revision is given via --rev, the children of that revision
+ will be printed. If a file argument is given, revision in
+ which the file was last changed (after the working directory
+ revision or the argument to --rev if given) is printed.
+ """
+ rev = opts.get('rev')
+ if file_:
+ ctx = repo.filectx(file_, changeid=rev)
+ else:
+ ctx = repo.changectx(rev)
+
+ displayer = cmdutil.show_changeset(ui, repo, opts)
+ for node in [cp.node() for cp in ctx.children()]:
+ displayer.show(changenode=node)
+
+
+cmdtable = {
+ "children":
+ (children,
+ [('r', 'rev', '', _('show children of the specified rev')),
+ ('', 'style', '', _('display using template map file')),
+ ('', 'template', '', _('display with template'))],
+ _('hg children [-r REV] [FILE]')),
+}
--- a/hgext/convert/__init__.py Fri Jul 06 08:48:28 2007 +0200
+++ b/hgext/convert/__init__.py Fri Jul 06 08:55:36 2007 +0200
@@ -5,27 +5,40 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-from common import NoRepo
+from common import NoRepo, converter_source, converter_sink
from cvs import convert_cvs
from git import convert_git
from hg import convert_mercurial
+from subversion import convert_svn
-import os
+import os, shutil
from mercurial import hg, ui, util, commands
commands.norepo += " convert"
-converters = [convert_cvs, convert_git, convert_mercurial]
+converters = [convert_cvs, convert_git, convert_svn, convert_mercurial]
-def converter(ui, path):
+def convertsource(ui, path, **opts):
+ for c in converters:
+ if not hasattr(c, 'getcommit'):
+ continue
+ try:
+ return c(ui, path, **opts)
+ except NoRepo:
+ pass
+ raise util.Abort('%s: unknown repository type' % path)
+
+def convertsink(ui, path):
if not os.path.isdir(path):
raise util.Abort("%s: not a directory" % path)
for c in converters:
+ if not hasattr(c, 'putcommit'):
+ continue
try:
return c(ui, path)
except NoRepo:
pass
- raise util.Abort("%s: unknown repository type" % path)
+ raise util.Abort('%s: unknown repository type' % path)
class convert(object):
def __init__(self, ui, source, dest, mapfile, opts):
@@ -179,6 +192,8 @@
def copy(self, rev):
c = self.commitcache[rev]
files = self.source.getchanges(rev)
+
+ do_copies = (hasattr(c, 'copies') and hasattr(self.dest, 'copyfile'))
for f, v in files:
try:
@@ -188,6 +203,11 @@
else:
e = self.source.getmode(f, v)
self.dest.putfile(f, e, data)
+ if do_copies:
+ if f in c.copies:
+ # Merely marks that a copy happened.
+ self.dest.copyfile(c.copies[f], f)
+
r = [self.map[v] for v in c.parents]
f = [f for f, v in files]
@@ -196,6 +216,7 @@
def convert(self):
try:
+ self.source.setrevmap(self.map)
self.ui.status("scanning source...\n")
heads = self.source.getheads()
parents = self.walktree(heads)
@@ -244,10 +265,15 @@
Accepted source formats:
- GIT
- CVS
+ - SVN
Accepted destination formats:
- Mercurial
+ If no revision is given, all revisions will be converted. Otherwise,
+ convert will only import up to the named revision (given in a format
+ understood by the source).
+
If destination isn't given, a new Mercurial repo named <src>-hg will
be created. If <mapfile> isn't given, it will be put in a default
location (<dest>/.hg/shamap by default)
@@ -267,15 +293,12 @@
srcauthor=whatever string you want
'''
- srcc = converter(ui, src)
- if not hasattr(srcc, "getcommit"):
- raise util.Abort("%s: can't read from this repo type" % src)
-
if not dest:
dest = src + "-hg"
ui.status("assuming destination %s\n" % dest)
# Try to be smart and initalize things when required
+ created = False
if os.path.isdir(dest):
if len(os.listdir(dest)) > 0:
try:
@@ -290,15 +313,22 @@
else:
ui.status("initializing destination %s repository\n" % dest)
hg.repository(ui, dest, create=True)
+ created = True
elif os.path.exists(dest):
raise util.Abort("destination %s exists and is not a directory" % dest)
else:
ui.status("initializing destination %s repository\n" % dest)
hg.repository(ui, dest, create=True)
+ created = True
- destc = converter(ui, dest)
- if not hasattr(destc, "putcommit"):
- raise util.Abort("%s: can't write to this repo type" % src)
+ destc = convertsink(ui, dest)
+
+ try:
+ srcc = convertsource(ui, src, rev=opts.get('rev'))
+ except Exception:
+ if created:
+ shutil.rmtree(dest, True)
+ raise
if not mapfile:
try:
@@ -313,6 +343,7 @@
"convert":
(_convert,
[('A', 'authors', '', 'username mapping filename'),
+ ('r', 'rev', '', 'import up to target revision REV'),
('', 'datesort', None, 'try to sort changesets by date')],
'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
}
--- a/hgext/convert/common.py Fri Jul 06 08:48:28 2007 +0200
+++ b/hgext/convert/common.py Fri Jul 06 08:55:36 2007 +0200
@@ -8,14 +8,24 @@
if not x in parts:
raise util.Abort("commit missing field %s" % x)
self.__dict__.update(parts)
+ if not self.desc or self.desc.isspace():
+ self.desc = '*** empty log message ***'
class converter_source(object):
"""Conversion source interface"""
- def __init__(self, ui, path):
+ def __init__(self, ui, path, rev=None):
"""Initialize conversion source (or raise NoRepo("message")
exception if path is not a valid repository)"""
- raise NotImplementedError()
+ self.ui = ui
+ self.path = path
+ self.rev = rev
+
+ self.encoding = 'utf-8'
+
+ def setrevmap(self, revmap):
+ """set the map of already-converted revisions"""
+ pass
def getheads(self):
"""Return a list of this repository's heads"""
@@ -44,6 +54,18 @@
"""Return the tags as a dictionary of name: revision"""
raise NotImplementedError()
+ def recode(self, s, encoding=None):
+ if not encoding:
+ encoding = self.encoding or 'utf-8'
+
+ try:
+ return s.decode(encoding).encode("utf-8")
+ except:
+ try:
+ return s.decode("latin-1").encode("utf-8")
+ except:
+ return s.decode(encoding, "replace").encode("utf-8")
+
class converter_sink(object):
"""Conversion sink (target) interface"""
--- a/hgext/convert/cvs.py Fri Jul 06 08:48:28 2007 +0200
+++ b/hgext/convert/cvs.py Fri Jul 06 08:55:36 2007 +0200
@@ -6,9 +6,9 @@
from common import NoRepo, commit, converter_source
class convert_cvs(converter_source):
- def __init__(self, ui, path):
- self.path = path
- self.ui = ui
+ def __init__(self, ui, path, rev=None):
+ super(convert_cvs, self).__init__(ui, path, rev=rev)
+
cvs = os.path.join(path, "CVS")
if not os.path.exists(cvs):
raise NoRepo("couldn't open CVS repo %s" % path)
@@ -29,15 +29,32 @@
if self.changeset:
return
+ maxrev = 0
+ cmd = 'cvsps -A -u --cvs-direct -q'
+ if self.rev:
+ # TODO: handle tags
+ try:
+ # patchset number?
+ maxrev = int(self.rev)
+ except ValueError:
+ try:
+ # date
+ util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
+ cmd = "%s -d '1970/01/01 00:00:01' -d '%s'" % (cmd, self.rev)
+ except util.Abort:
+ raise util.Abort('revision %s is not a patchset number or date' % self.rev)
+
d = os.getcwd()
try:
os.chdir(self.path)
id = None
state = 0
- for l in os.popen("cvsps -A -u --cvs-direct -q"):
+ for l in os.popen(cmd):
if state == 0: # header
if l.startswith("PatchSet"):
id = l[9:-2]
+ if maxrev and int(id) > maxrev:
+ state = 3
elif l.startswith("Date"):
date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
date = util.datestr(date)
@@ -62,8 +79,6 @@
if l == "Members: \n":
files = {}
log = self.recode(log[:-1])
- if log.isspace():
- log = "*** empty log message ***\n"
state = 2
else:
log += l
@@ -85,6 +100,8 @@
rev = l[colon+1:-2]
rev = rev.split("->")[1]
files[file] = rev
+ elif state == 3:
+ continue
self.heads = self.lastbranch.values()
finally:
@@ -235,9 +252,6 @@
cl.sort()
return cl
- def recode(self, text):
- return text.decode(self.encoding, "replace").encode("utf-8")
-
def getcommit(self, rev):
return self.changeset[rev]
--- a/hgext/convert/git.py Fri Jul 06 08:48:28 2007 +0200
+++ b/hgext/convert/git.py Fri Jul 06 08:55:36 2007 +0200
@@ -4,32 +4,29 @@
from common import NoRepo, commit, converter_source
-def recode(s):
- try:
- return s.decode("utf-8").encode("utf-8")
- except:
- try:
- return s.decode("latin-1").encode("utf-8")
- except:
- return s.decode("utf-8", "replace").encode("utf-8")
+class convert_git(converter_source):
+ def gitcmd(self, s):
+ return os.popen('GIT_DIR=%s %s' % (self.path, s))
-class convert_git(converter_source):
- def __init__(self, ui, path):
+ def __init__(self, ui, path, rev=None):
+ super(convert_git, self).__init__(ui, path, rev=rev)
+
if os.path.isdir(path + "/.git"):
path += "/.git"
- self.path = path
- self.ui = ui
if not os.path.exists(path + "/objects"):
raise NoRepo("couldn't open GIT repo %s" % path)
+ self.path = path
def getheads(self):
- fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
- return [fh.read()[:-1]]
+ if not self.rev:
+ return self.gitcmd('git-rev-parse --branches').read().splitlines()
+ else:
+ fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
+ return [fh.read()[:-1]]
def catfile(self, rev, type):
if rev == "0" * 40: raise IOError()
- fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null"
- % (self.path, type, rev))
+ fh = self.gitcmd("git-cat-file %s %s 2>/dev/null" % (type, rev))
return fh.read()
def getfile(self, name, rev):
@@ -40,8 +37,7 @@
def getchanges(self, version):
self.modecache = {}
- fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s"
- % (self.path, version))
+ fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
changes = []
for l in fh:
if "\t" not in l: continue
@@ -58,7 +54,7 @@
c = self.catfile(version, "commit") # read the commit hash
end = c.find("\n\n")
message = c[end+2:]
- message = recode(message)
+ message = self.recode(message)
l = c[:end].splitlines()
manifest = l[0].split()[1]
parents = []
@@ -69,13 +65,13 @@
tm, tz = p[-2:]
author = " ".join(p[:-2])
if author[0] == "<": author = author[1:-1]
- author = recode(author)
+ author = self.recode(author)
if n == "committer":
p = v.split()
tm, tz = p[-2:]
committer = " ".join(p[:-2])
if committer[0] == "<": committer = committer[1:-1]
- committer = recode(committer)
+ committer = self.recode(committer)
message += "\ncommitter: %s\n" % committer
if n == "parent": parents.append(v)
@@ -89,7 +85,7 @@
def gettags(self):
tags = {}
- fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
+ fh = self.gitcmd('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
prefix = 'refs/tags/'
for line in fh:
line = line.strip()
--- a/hgext/convert/hg.py Fri Jul 06 08:48:28 2007 +0200
+++ b/hgext/convert/hg.py Fri Jul 06 08:55:36 2007 +0200
@@ -29,6 +29,9 @@
if self.repo.dirstate.state(f) == '?':
self.repo.dirstate.update([f], "a")
+ def copyfile(self, source, dest):
+ self.repo.copy(source, dest)
+
def delfile(self, f):
try:
os.unlink(self.repo.wjoin(f))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/convert/subversion.py Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,586 @@
+# Subversion 1.4/1.5 Python API backend
+#
+# Copyright(C) 2007 Daniel Holth et al
+
+import pprint
+import locale
+
+from mercurial import util
+
+# Subversion stuff. Works best with very recent Python SVN bindings
+# e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
+# these bindings.
+
+from cStringIO import StringIO
+
+from common import NoRepo, commit, converter_source
+
+try:
+ from svn.core import SubversionException, Pool
+ import svn.core
+ import svn.ra
+ import svn.delta
+ import svn
+ import transport
+except ImportError:
+ pass
+
+class CompatibilityException(Exception): pass
+
+# SVN conversion code stolen from bzr-svn and tailor
+class convert_svn(converter_source):
+ def __init__(self, ui, url, rev=None):
+ super(convert_svn, self).__init__(ui, url, rev=rev)
+
+ try:
+ SubversionException
+ except NameError:
+ msg = 'subversion python bindings could not be loaded\n'
+ ui.warn(msg)
+ raise NoRepo(msg)
+
+ self.encoding = locale.getpreferredencoding()
+ self.lastrevs = {}
+
+ latest = None
+ if rev:
+ try:
+ latest = int(rev)
+ except ValueError:
+ raise util.Abort('svn: revision %s is not an integer' % rev)
+ try:
+ # Support file://path@rev syntax. Useful e.g. to convert
+ # deleted branches.
+ url, latest = url.rsplit("@", 1)
+ latest = int(latest)
+ except ValueError, e:
+ pass
+ self.url = url
+ self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
+ try:
+ self.transport = transport.SvnRaTransport(url = url)
+ self.ra = self.transport.ra
+ self.ctx = svn.client.create_context()
+ self.base = svn.ra.get_repos_root(self.ra)
+ self.module = self.url[len(self.base):]
+ self.modulemap = {} # revision, module
+ self.commits = {}
+ self.files = {}
+ self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
+ except SubversionException, e:
+ raise NoRepo("couldn't open SVN repo %s" % url)
+
+ try:
+ self.get_blacklist()
+ except IOError, e:
+ pass
+
+ self.last_changed = self.latest(self.module, latest)
+
+ self.head = self.revid(self.last_changed)
+
+ def setrevmap(self, revmap):
+ lastrevs = {}
+ for revid in revmap.keys():
+ uuid, module, revnum = self.revsplit(revid)
+ lastrevnum = lastrevs.setdefault(module, revnum)
+ if revnum > lastrevnum:
+ lastrevs[module] = revnum
+ self.lastrevs = lastrevs
+
+ def getheads(self):
+ # detect standard /branches, /tags, /trunk layout
+ optrev = svn.core.svn_opt_revision_t()
+ optrev.kind = svn.core.svn_opt_revision_number
+ optrev.value.number = self.last_changed
+ rpath = self.url.strip('/')
+ paths = svn.client.ls(rpath, optrev, False, self.ctx)
+ if 'branches' in paths and 'trunk' in paths:
+ self.module += '/trunk'
+ lt = self.latest(self.module, self.last_changed)
+ self.head = self.revid(lt)
+ self.heads = [self.head]
+ branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx)
+ for branch in branches.keys():
+ module = '/branches/' + branch
+ brevnum = self.latest(module, self.last_changed)
+ brev = self.revid(brevnum, module)
+ self.ui.note('found branch %s at %d\n' % (branch, brevnum))
+ self.heads.append(brev)
+ else:
+ self.heads = [self.head]
+ return self.heads
+
+ def getfile(self, file, rev):
+ data, mode = self._getfile(file, rev)
+ self.modecache[(file, rev)] = mode
+ return data
+
+ def getmode(self, file, rev):
+ return self.modecache[(file, rev)]
+
+ def getchanges(self, rev):
+ self.modecache = {}
+ files = self.files[rev]
+ cl = files
+ cl.sort()
+ # caller caches the result, so free it here to release memory
+ del self.files[rev]
+ return cl
+
+ def getcommit(self, rev):
+ if rev not in self.commits:
+ uuid, module, revnum = self.revsplit(rev)
+ self.module = module
+ self.reparent(module)
+ stop = self.lastrevs.get(module, 0)
+ self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
+ commit = self.commits[rev]
+ # caller caches the result, so free it here to release memory
+ del self.commits[rev]
+ return commit
+
+ def gettags(self):
+ tags = {}
+ def parselogentry(*arg, **args):
+ orig_paths, revnum, author, date, message, pool = arg
+ for path in orig_paths:
+ if not path.startswith('/tags/'):
+ continue
+ ent = orig_paths[path]
+ source = ent.copyfrom_path
+ rev = ent.copyfrom_rev
+ tag = path.split('/', 2)[2]
+ tags[tag] = self.revid(rev, module=source)
+
+ start = self.revnum(self.head)
+ try:
+ svn.ra.get_log(self.ra, ['/tags'], 0, start, 0, True, False,
+ parselogentry)
+ return tags
+ except SubversionException:
+ self.ui.note('no tags found at revision %d\n' % start)
+ return {}
+
+ # -- helper functions --
+
+ def revid(self, revnum, module=None):
+ if not module:
+ module = self.module
+ return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
+
+ def revnum(self, rev):
+ return int(rev.split('@')[-1])
+
+ def revsplit(self, rev):
+ url, revnum = rev.encode(self.encoding).split('@', 1)
+ revnum = int(revnum)
+ parts = url.split('/', 1)
+ uuid = parts.pop(0)[4:]
+ mod = ''
+ if parts:
+ mod = '/' + parts[0]
+ return uuid, mod, revnum
+
+ def latest(self, path, stop=0):
+ 'find the latest revision affecting path, up to stop'
+ if not stop:
+ stop = svn.ra.get_latest_revnum(self.ra)
+ try:
+ self.reparent('')
+ dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
+ self.reparent(self.module)
+ except SubversionException:
+ dirent = None
+ if not dirent:
+ raise util.Abort('%s not found up to revision %d' \
+ % (path, stop))
+
+ return dirent.created_rev
+
+ def get_blacklist(self):
+ """Avoid certain revision numbers.
+ It is not uncommon for two nearby revisions to cancel each other
+ out, e.g. 'I copied trunk into a subdirectory of itself instead
+ of making a branch'. The converted repository is significantly
+ smaller if we ignore such revisions."""
+ self.blacklist = set()
+ blacklist = self.blacklist
+ for line in file("blacklist.txt", "r"):
+ if not line.startswith("#"):
+ try:
+ svn_rev = int(line.strip())
+ blacklist.add(svn_rev)
+ except ValueError, e:
+ pass # not an integer or a comment
+
+ def is_blacklisted(self, svn_rev):
+ return svn_rev in self.blacklist
+
+ def reparent(self, module):
+ svn_url = self.base + module
+ self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
+ svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
+
+ def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
+ def get_entry_from_path(path, module=self.module):
+ # Given the repository url of this wc, say
+ # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
+ # extract the "entry" portion (a relative path) from what
+ # svn log --xml says, ie
+ # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
+ # that is to say "tests/PloneTestCase.py"
+
+ if path.startswith(module):
+ relative = path[len(module):]
+ if relative.startswith('/'):
+ return relative[1:]
+ else:
+ return relative
+
+ # The path is outside our tracked tree...
+ self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
+ return None
+
+ received = []
+ # svn.ra.get_log requires no other calls to the ra until it completes,
+ # so we just collect the log entries and parse them afterwards
+ def receivelog(*arg, **args):
+ received.append(arg)
+
+ self.child_cset = None
+ def parselogentry(*arg, **args):
+ orig_paths, revnum, author, date, message, pool = arg
+
+ if self.is_blacklisted(revnum):
+ self.ui.note('skipping blacklisted revision %d\n' % revnum)
+ return
+
+ self.ui.debug("parsing revision %d\n" % revnum)
+
+ if orig_paths is None:
+ self.ui.debug('revision %d has no entries\n' % revnum)
+ return
+
+ if revnum in self.modulemap:
+ new_module = self.modulemap[revnum]
+ if new_module != self.module:
+ self.module = new_module
+ self.reparent(self.module)
+
+ copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
+ copies = {}
+ entries = []
+ rev = self.revid(revnum)
+ parents = []
+
+ # branch log might return entries for a parent we already have
+ if (rev in self.commits or
+ (revnum < self.lastrevs.get(self.module, 0))):
+ return
+
+ try:
+ branch = self.module.split("/")[-1]
+ if branch == 'trunk':
+ branch = ''
+ except IndexError:
+ branch = None
+
+ for path in sorted(orig_paths):
+ # self.ui.write("path %s\n" % path)
+ if path == self.module: # Follow branching back in history
+ ent = orig_paths[path]
+ if ent:
+ if ent.copyfrom_path:
+ # ent.copyfrom_rev may not be the actual last revision
+ prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
+ self.modulemap[prev] = ent.copyfrom_path
+ parents = [self.revid(prev, ent.copyfrom_path)]
+ self.ui.note('found parent of branch %s at %d: %s\n' % \
+ (self.module, prev, ent.copyfrom_path))
+ else:
+ self.ui.debug("No copyfrom path, don't know what to do.\n")
+ # Maybe it was added and there is no more history.
+ entrypath = get_entry_from_path(path, module=self.module)
+ # self.ui.write("entrypath %s\n" % entrypath)
+ if entrypath is None:
+ # Outside our area of interest
+ self.ui.debug("boring@%s: %s\n" % (revnum, path))
+ continue
+ entry = entrypath.decode(self.encoding)
+ ent = orig_paths[path]
+
+ kind = svn.ra.check_path(self.ra, entrypath, revnum)
+ if kind == svn.core.svn_node_file:
+ if ent.copyfrom_path:
+ copyfrom_path = get_entry_from_path(ent.copyfrom_path)
+ if copyfrom_path:
+ self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
+ # It's probably important for hg that the source
+ # exists in the revision's parent, not just the
+ # ent.copyfrom_rev
+ fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
+ if fromkind != 0:
+ copies[self.recode(entry)] = self.recode(copyfrom_path)
+ entries.append(self.recode(entry))
+ elif kind == 0: # gone, but had better be a deleted *file*
+ self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
+
+ # if a branch is created but entries are removed in the same
+ # changeset, get the right fromrev
+ if parents:
+ uuid, old_module, fromrev = self.revsplit(parents[0])
+ else:
+ fromrev = revnum - 1
+ # might always need to be revnum - 1 in these 3 lines?
+ old_module = self.modulemap.get(fromrev, self.module)
+
+ basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
+ entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
+
+ def lookup_parts(p):
+ rc = None
+ parts = p.split("/")
+ for i in range(len(parts)):
+ part = "/".join(parts[:i])
+ info = part, copyfrom.get(part, None)
+ if info[1] is not None:
+ self.ui.debug("Found parent directory %s\n" % info[1])
+ rc = info
+ return rc
+
+ self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
+
+ frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
+
+ # need to remove fragment from lookup_parts and replace with copyfrom_path
+ if frompath is not None:
+ self.ui.debug("munge-o-matic\n")
+ self.ui.debug(entrypath + '\n')
+ self.ui.debug(entrypath[len(frompath):] + '\n')
+ entrypath = froment.copyfrom_path + entrypath[len(frompath):]
+ fromrev = froment.copyfrom_rev
+ self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
+
+ fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
+ if fromkind == svn.core.svn_node_file: # a deleted file
+ entries.append(self.recode(entry))
+ elif fromkind == svn.core.svn_node_dir:
+ # print "Deleted/moved non-file:", revnum, path, ent
+ # children = self._find_children(path, revnum - 1)
+ # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
+ # Sometimes this is tricky. For example: in
+ # The Subversion Repository revision 6940 a dir
+ # was copied and one of its files was deleted
+ # from the new location in the same commit. This
+ # code can't deal with that yet.
+ if ent.action == 'C':
+ children = self._find_children(path, fromrev)
+ else:
+ oroot = entrypath.strip('/')
+ nroot = path.strip('/')
+ children = self._find_children(oroot, fromrev)
+ children = [s.replace(oroot,nroot) for s in children]
+ # Mark all [files, not directories] as deleted.
+ for child in children:
+ # Can we move a child directory and its
+ # parent in the same commit? (probably can). Could
+ # cause problems if instead of revnum -1,
+ # we have to look in (copyfrom_path, revnum - 1)
+ entrypath = get_entry_from_path("/" + child, module=old_module)
+ if entrypath:
+ entry = self.recode(entrypath.decode(self.encoding))
+ if entry in copies:
+ # deleted file within a copy
+ del copies[entry]
+ else:
+ entries.append(entry)
+ else:
+ self.ui.debug('unknown path in revision %d: %s\n' % \
+ (revnum, path))
+ elif kind == svn.core.svn_node_dir:
+ # Should probably synthesize normal file entries
+ # and handle as above to clean up copy/rename handling.
+
+ # If the directory just had a prop change,
+ # then we shouldn't need to look for its children.
+ # Also this could create duplicate entries. Not sure
+ # whether this will matter. Maybe should make entries a set.
+ # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
+ # This will fail if a directory was copied
+ # from another branch and then some of its files
+ # were deleted in the same transaction.
+ children = self._find_children(path, revnum)
+ children.sort()
+ for child in children:
+ # Can we move a child directory and its
+ # parent in the same commit? (probably can). Could
+ # cause problems if instead of revnum -1,
+ # we have to look in (copyfrom_path, revnum - 1)
+ entrypath = get_entry_from_path("/" + child, module=self.module)
+ # print child, self.module, entrypath
+ if entrypath:
+ # Need to filter out directories here...
+ kind = svn.ra.check_path(self.ra, entrypath, revnum)
+ if kind != svn.core.svn_node_dir:
+ entries.append(self.recode(entrypath))
+
+ # Copies here (must copy all from source)
+ # Probably not a real problem for us if
+ # source does not exist
+
+ # Can do this with the copy command "hg copy"
+ # if ent.copyfrom_path:
+ # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
+ # module=self.module)
+ # copyto_entry = entrypath
+ #
+ # print "copy directory", copyfrom_entry, 'to', copyto_entry
+ #
+ # copies.append((copyfrom_entry, copyto_entry))
+
+ if ent.copyfrom_path:
+ copyfrom_path = ent.copyfrom_path.decode(self.encoding)
+ copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
+ if copyfrom_entry:
+ copyfrom[path] = ent
+ self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
+
+ # Good, /probably/ a regular copy. Really should check
+ # to see whether the parent revision actually contains
+ # the directory in question.
+ children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
+ children.sort()
+ for child in children:
+ entrypath = get_entry_from_path("/" + child, module=self.module)
+ if entrypath:
+ entry = entrypath.decode(self.encoding)
+ # print "COPY COPY From", copyfrom_entry, entry
+ copyto_path = path + entry[len(copyfrom_entry):]
+ copyto_entry = get_entry_from_path(copyto_path, module=self.module)
+ # print "COPY", entry, "COPY To", copyto_entry
+ copies[self.recode(copyto_entry)] = self.recode(entry)
+ # copy from quux splort/quuxfile
+
+ self.modulemap[revnum] = self.module # track backwards in time
+ # a list of (filename, id) where id lets us retrieve the file.
+ # eg in git, id is the object hash. for svn it'll be the
+ self.files[rev] = zip(entries, [rev] * len(entries))
+ if not entries:
+ return
+
+ # Example SVN datetime. Includes microseconds.
+ # ISO-8601 conformant
+ # '2007-01-04T17:35:00.902377Z'
+ date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
+
+ log = message and self.recode(message)
+ author = author and self.recode(author) or ''
+
+ cset = commit(author=author,
+ date=util.datestr(date),
+ desc=log,
+ parents=parents,
+ copies=copies,
+ branch=branch)
+
+ self.commits[rev] = cset
+ if self.child_cset and not self.child_cset.parents:
+ self.child_cset.parents = [rev]
+ self.child_cset = cset
+
+ self.ui.note('fetching revision log for "%s" from %d to %d\n' % \
+ (self.module, from_revnum, to_revnum))
+
+ try:
+ discover_changed_paths = True
+ strict_node_history = False
+ svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
+ discover_changed_paths, strict_node_history,
+ receivelog)
+ for entry in received:
+ parselogentry(*entry)
+ except SubversionException, (_, num):
+ if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
+ raise NoSuchRevision(branch=self,
+ revision="Revision number %d" % to_revnum)
+ raise
+
+ def _getfile(self, file, rev):
+ io = StringIO()
+ # TODO: ra.get_file transmits the whole file instead of diffs.
+ mode = ''
+ try:
+ revnum = self.revnum(rev)
+ if self.module != self.modulemap[revnum]:
+ self.module = self.modulemap[revnum]
+ self.reparent(self.module)
+ info = svn.ra.get_file(self.ra, file, revnum, io)
+ if isinstance(info, list):
+ info = info[-1]
+ mode = ("svn:executable" in info) and 'x' or ''
+ mode = ("svn:special" in info) and 'l' or mode
+ except SubversionException, e:
+ notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
+ svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
+ if e.apr_err in notfound: # File not found
+ raise IOError()
+ raise
+ data = io.getvalue()
+ if mode == 'l':
+ link_prefix = "link "
+ if data.startswith(link_prefix):
+ data = data[len(link_prefix):]
+ return data, mode
+
+ def _find_children(self, path, revnum):
+ path = path.strip("/")
+
+ def _find_children_fallback(path, revnum):
+ # SWIG python bindings for getdir are broken up to at least 1.4.3
+ pool = Pool()
+ optrev = svn.core.svn_opt_revision_t()
+ optrev.kind = svn.core.svn_opt_revision_number
+ optrev.value.number = revnum
+ rpath = '/'.join([self.base, path]).strip('/')
+ return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
+
+ if hasattr(self, '_find_children_fallback'):
+ return _find_children_fallback(path, revnum)
+
+ self.reparent("/" + path)
+ pool = Pool()
+
+ children = []
+ def find_children_inner(children, path, revnum = revnum):
+ if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
+ fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
+ getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
+ else:
+ getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
+ if type(getdir) == dict:
+ # python binding for getdir is broken up to at least 1.4.3
+ raise CompatibilityException()
+ dirents = getdir[0]
+ if type(dirents) == int:
+ # got here once due to infinite recursion bug
+ # pprint.pprint(getdir)
+ return
+ c = dirents.keys()
+ c.sort()
+ for child in c:
+ dirent = dirents[child]
+ if dirent.kind == svn.core.svn_node_dir:
+ find_children_inner(children, (path + "/" + child).strip("/"))
+ else:
+ children.append((path + "/" + child).strip("/"))
+
+ try:
+ find_children_inner(children, "")
+ except CompatibilityException:
+ self._find_children_fallback = True
+ self.reparent(self.module)
+ return _find_children_fallback(path, revnum)
+
+ self.reparent(self.module)
+ return [path + "/" + c for c in children]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/convert/transport.py Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2007 Daniel Holth <dholth@fastmail.fm>
+# This is a stripped-down version of the original bzr-svn transport.py,
+# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
+
+# 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
+
+from cStringIO import StringIO
+import os
+from tempfile import mktemp
+
+from svn.core import SubversionException, Pool
+import svn.ra
+import svn.core
+
+# Some older versions of the Python bindings need to be
+# explicitly initialized. But what we want to do probably
+# won't work worth a darn against those libraries anyway!
+svn.ra.initialize()
+
+svn_config = svn.core.svn_config_get_config(None)
+
+
+def _create_auth_baton(pool):
+ """Create a Subversion authentication baton. """
+ import svn.client
+ # Give the client context baton a suite of authentication
+ # providers.h
+ providers = [
+ svn.client.get_simple_provider(pool),
+ svn.client.get_username_provider(pool),
+ svn.client.get_ssl_client_cert_file_provider(pool),
+ svn.client.get_ssl_client_cert_pw_file_provider(pool),
+ svn.client.get_ssl_server_trust_file_provider(pool),
+ ]
+ return svn.core.svn_auth_open(providers, pool)
+
+
+# # The SVN libraries don't like trailing slashes...
+# return url.rstrip('/')
+
+
+class SvnRaCallbacks(svn.ra.callbacks2_t):
+ """Remote access callbacks implementation for bzr-svn."""
+ def __init__(self, pool):
+ svn.ra.callbacks2_t.__init__(self)
+ self.auth_baton = _create_auth_baton(pool)
+ self.pool = pool
+
+ def open_tmp_file(self, pool):
+ return mktemp(prefix='tailor-svn')
+
+class NotBranchError(SubversionException):
+ pass
+
+class SvnRaTransport(object):
+ """
+ Open an ra connection to a Subversion repository.
+ """
+ def __init__(self, url="", ra=None):
+ self.pool = Pool()
+ self.svn_url = url
+
+ # Only Subversion 1.4 has reparent()
+ if ra is None or not hasattr(svn.ra, 'reparent'):
+ self.callbacks = SvnRaCallbacks(self.pool)
+ try:
+ ver = svn.ra.version()
+ try: # Older SVN bindings
+ self.ra = svn.ra.open2(self.svn_url.encode('utf8'), self.callbacks, None, svn_config, None)
+ except TypeError, e:
+ self.ra = svn.ra.open2(self.svn_url.encode('utf8'), self.callbacks, svn_config, None)
+ except SubversionException, (_, num):
+ if num == svn.core.SVN_ERR_RA_ILLEGAL_URL:
+ raise NotBranchError(url)
+ if num == svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED:
+ raise NotBranchError(url)
+ if num == svn.core.SVN_ERR_BAD_URL:
+ raise NotBranchError(url)
+ raise
+
+ else:
+ self.ra = ra
+ svn.ra.reparent(self.ra, self.svn_url.encode('utf8'))
+
+ class Reporter:
+ def __init__(self, (reporter, report_baton)):
+ self._reporter = reporter
+ self._baton = report_baton
+
+ def set_path(self, path, revnum, start_empty, lock_token, pool=None):
+ svn.ra.reporter2_invoke_set_path(self._reporter, self._baton,
+ path, revnum, start_empty, lock_token, pool)
+
+ def delete_path(self, path, pool=None):
+ svn.ra.reporter2_invoke_delete_path(self._reporter, self._baton,
+ path, pool)
+
+ def link_path(self, path, url, revision, start_empty, lock_token,
+ pool=None):
+ svn.ra.reporter2_invoke_link_path(self._reporter, self._baton,
+ path, url, revision, start_empty, lock_token,
+ pool)
+
+ def finish_report(self, pool=None):
+ svn.ra.reporter2_invoke_finish_report(self._reporter,
+ self._baton, pool)
+
+ def abort_report(self, pool=None):
+ svn.ra.reporter2_invoke_abort_report(self._reporter,
+ self._baton, pool)
+
+ def do_update(self, revnum, path, *args, **kwargs):
+ return self.Reporter(svn.ra.do_update(self.ra, revnum, path, *args, **kwargs))
+
+ def clone(self, offset=None):
+ """See Transport.clone()."""
+ if offset is None:
+ return self.__class__(self.base)
+
+ return SvnRaTransport(urlutils.join(self.base, offset), ra=self.ra)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/interhg.py Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,64 @@
+# interhg.py - interhg
+#
+# Copyright 2007 OHASHI Hideya <ohachige@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# The `interhg' Mercurial extension allows you to change changelog and
+# summary text just like InterWiki way.
+#
+# To enable this extension:
+#
+# [extensions]
+# interhg =
+#
+# This is an example to link to a bug tracking system.
+#
+# [interhg]
+# pat1 = s/issue(\d+)/ <a href="http:\/\/bts\/issue\1">issue\1<\/a> /
+#
+# You can add patterns to use pat2, pat3, ...
+# For exapmle.
+#
+# pat2 = s/(^|\s)#(\d+)\b/ <b>#\2<\/b> /
+
+import re
+from mercurial.hgweb import hgweb_mod
+from mercurial import templater
+
+orig_escape = templater.common_filters["escape"]
+
+interhg_table = []
+
+def interhg_escape(x):
+ escstr = orig_escape(x)
+ for pat in interhg_table:
+ regexp = pat[0]
+ format = pat[1]
+ escstr = regexp.sub(format, escstr)
+ return escstr
+
+templater.common_filters["escape"] = interhg_escape
+
+orig_refresh = hgweb_mod.hgweb.refresh
+
+def interhg_refresh(self):
+ interhg_table[:] = []
+ num = 1
+ while True:
+ key = 'pat%d' % num
+ pat = self.config('interhg', key)
+ if pat == None:
+ break
+ pat = pat[2:-1]
+ span = re.search(r'[^\\]/', pat).span()
+ regexp = pat[:span[0] + 1]
+ format = pat[span[1]:]
+ format = re.sub(r'\\/', '/', format)
+ regexp = re.compile(regexp)
+ interhg_table.append((regexp, format))
+ num += 1
+ return orig_refresh(self)
+
+hgweb_mod.hgweb.refresh = interhg_refresh
--- a/mercurial/commands.py Fri Jul 06 08:48:28 2007 +0200
+++ b/mercurial/commands.py Fri Jul 06 08:55:36 2007 +0200
@@ -3109,6 +3109,8 @@
"version": (version_, [], _('hg version')),
}
+extensions.commandtable = table
+
norepo = ("clone init version help debugancestor debugcomplete debugdata"
" debugindex debugindexdot debugdate debuginstall")
optionalrepo = ("paths serve showconfig")
--- a/mercurial/extensions.py Fri Jul 06 08:48:28 2007 +0200
+++ b/mercurial/extensions.py Fri Jul 06 08:55:36 2007 +0200
@@ -6,10 +6,12 @@
# of the GNU General Public License, incorporated herein by reference.
import imp, os
-import commands, hg, util, sys
+import util, sys
from i18n import _
_extensions = {}
+commandtable = {}
+setuphooks = []
def find(name):
'''return module with given extension name'''
@@ -54,13 +56,13 @@
uisetup(ui)
reposetup = getattr(mod, 'reposetup', None)
if reposetup:
- hg.repo_setup_hooks.append(reposetup)
+ setuphooks.append(reposetup)
cmdtable = getattr(mod, 'cmdtable', {})
- overrides = [cmd for cmd in cmdtable if cmd in commands.table]
+ overrides = [cmd for cmd in cmdtable if cmd in commandtable]
if overrides:
ui.warn(_("extension '%s' overrides commands: %s\n")
% (name, " ".join(overrides)))
- commands.table.update(cmdtable)
+ commandtable.update(cmdtable)
def loadall(ui):
result = ui.configitems("extensions")
--- a/mercurial/hg.py Fri Jul 06 08:48:28 2007 +0200
+++ b/mercurial/hg.py Fri Jul 06 08:55:36 2007 +0200
@@ -10,7 +10,7 @@
from repo import *
from i18n import _
import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
-import errno, lock, os, shutil, util, cmdutil
+import errno, lock, os, shutil, util, cmdutil, extensions
import merge as _merge
import verify as _verify
@@ -50,13 +50,11 @@
return False
return repo.local()
-repo_setup_hooks = []
-
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 hook in repo_setup_hooks:
+ for hook in extensions.setuphooks:
hook(ui, repo)
return repo
--- a/mercurial/hgweb/hgwebdir_mod.py Fri Jul 06 08:48:28 2007 +0200
+++ b/mercurial/hgweb/hgwebdir_mod.py Fri Jul 06 08:55:36 2007 +0200
@@ -90,8 +90,12 @@
url = req.env['REQUEST_URI'].split('?')[0]
if not url.endswith('/'):
url += '/'
+ pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
+ base = url[:len(url) - len(pathinfo)]
+ if not base.endswith('/'):
+ base += '/'
- staticurl = config('web', 'staticurl') or url + 'static/'
+ staticurl = config('web', 'staticurl') or base + 'static/'
if not staticurl.endswith('/'):
staticurl += '/'
@@ -118,7 +122,7 @@
yield {"type" : i[0], "extension": i[1],
"node": nodeid, "url": url}
- def entries(sortcolumn="", descending=False, **map):
+ def entries(sortcolumn="", descending=False, subdir="", **map):
def sessionvars(**map):
fields = []
if req.form.has_key('style'):
@@ -134,6 +138,10 @@
rows = []
parity = paritygen(self.stripecount)
for name, path in self.repos:
+ if not name.startswith(subdir):
+ continue
+ name = name[len(subdir):]
+
u = ui.ui(parentui=parentui)
try:
u.readconfig(os.path.join(path, '.hg', 'hgrc'))
@@ -185,6 +193,25 @@
row['parity'] = parity.next()
yield row
+ def makeindex(req, subdir=""):
+ sortable = ["name", "description", "contact", "lastchange"]
+ sortcolumn, descending = self.repos_sorted
+ if req.form.has_key('sort'):
+ sortcolumn = req.form['sort'][0]
+ descending = sortcolumn.startswith('-')
+ if descending:
+ sortcolumn = sortcolumn[1:]
+ if sortcolumn not in sortable:
+ sortcolumn = ""
+
+ sort = [("sort_%s" % column,
+ "%s%s" % ((not descending and column == sortcolumn)
+ and "-" or "", column))
+ for column in sortable]
+ req.write(tmpl("index", entries=entries, subdir=subdir,
+ sortcolumn=sortcolumn, descending=descending,
+ **dict(sort)))
+
try:
virtual = req.env.get("PATH_INFO", "").strip('/')
if virtual.startswith('static/'):
@@ -211,7 +238,11 @@
except hg.RepoError, inst:
req.write(tmpl("error", error=str(inst)))
else:
- req.write(tmpl("notfound", repo=virtual))
+ subdir=req.env.get("PATH_INFO", "").strip('/') + '/'
+ if [r for r in self.repos if r[0].startswith(subdir)]:
+ makeindex(req, subdir)
+ else:
+ req.write(tmpl("notfound", repo=virtual))
else:
if req.form.has_key('static'):
static = os.path.join(templater.templatepath(), "static")
@@ -219,22 +250,6 @@
req.write(staticfile(static, fname, req)
or tmpl("error", error="%r not found" % fname))
else:
- sortable = ["name", "description", "contact", "lastchange"]
- sortcolumn, descending = self.repos_sorted
- if req.form.has_key('sort'):
- sortcolumn = req.form['sort'][0]
- descending = sortcolumn.startswith('-')
- if descending:
- sortcolumn = sortcolumn[1:]
- if sortcolumn not in sortable:
- sortcolumn = ""
-
- sort = [("sort_%s" % column,
- "%s%s" % ((not descending and column == sortcolumn)
- and "-" or "", column))
- for column in sortable]
- req.write(tmpl("index", entries=entries,
- sortcolumn=sortcolumn, descending=descending,
- **dict(sort)))
+ makeindex(req)
finally:
tmpl = None
--- a/mercurial/patch.py Fri Jul 06 08:48:28 2007 +0200
+++ b/mercurial/patch.py Fri Jul 06 08:55:36 2007 +0200
@@ -50,7 +50,7 @@
try:
msg = email.Parser.Parser().parse(fileobj)
- message = msg['Subject']
+ subject = msg['Subject']
user = msg['From']
# should try to parse msg['Date']
date = None
@@ -58,13 +58,13 @@
branch = None
parents = []
- if message:
- if message.startswith('[PATCH'):
- pend = message.find(']')
+ if subject:
+ if subject.startswith('[PATCH'):
+ pend = subject.find(']')
if pend >= 0:
- message = message[pend+1:].lstrip()
- message = message.replace('\n\t', ' ')
- ui.debug('Subject: %s\n' % message)
+ 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
@@ -84,9 +84,6 @@
ui.debug(_('found patch at byte %d\n') % m.start(0))
diffs_seen += 1
cfp = cStringIO.StringIO()
- if message:
- cfp.write(message)
- cfp.write('\n')
for line in payload[:m.start(0)].splitlines():
if line.startswith('# HG changeset patch'):
ui.debug(_('patch generated by hg export\n'))
@@ -94,6 +91,7 @@
# drop earlier commit message content
cfp.seek(0)
cfp.truncate()
+ subject = None
elif hgpatch:
if line.startswith('# User '):
user = line[7:]
@@ -123,6 +121,8 @@
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)
--- a/setup.py Fri Jul 06 08:48:28 2007 +0200
+++ b/setup.py Fri Jul 06 08:55:36 2007 +0200
@@ -2,8 +2,8 @@
#
# This is the mercurial setup script.
#
-# './setup.py install', or
-# './setup.py --help' for more options
+# 'python setup.py install', or
+# 'python setup.py --help' for more options
import sys
if not hasattr(sys, 'version_info') or sys.version_info < (2, 3, 0, 'final'):
--- a/templates/gitweb/map Fri Jul 06 08:48:28 2007 +0200
+++ b/templates/gitweb/map Fri Jul 06 08:55:36 2007 +0200
@@ -5,6 +5,7 @@
changelog = changelog.tmpl
summary = summary.tmpl
error = error.tmpl
+notfound = notfound.tmpl
naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/gitweb/notfound.tmpl Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,19 @@
+{header}
+<title>Mercurial repositories index</title>
+</head>
+
+<body>
+
+<div class="page_header">
+<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div
+ style="float:right;">Mercurial</div></a> Not found: {repo|escape}
+</div>
+
+<div class="page_body">
+The specified repository "{repo|escape}" is unknown, sorry.
+<br/>
+<br/>
+Please go back to the <a href="/">main repository list page</a>.
+</div>
+
+{footer}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-alias Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+cat > $HGRCPATH <<EOF
+[extensions]
+alias=
+
+[alias]
+myinit = init
+cleanstatus = status -c
+unknown = bargle
+ambiguous = s
+recursive = recursive
+EOF
+
+echo '% basic'
+hg myinit alias
+
+echo '% unknown'
+hg unknown
+
+echo '% ambiguous'
+hg ambiguous
+
+echo '% recursive'
+hg recursive
+
+cd alias
+echo foo > foo
+hg ci -Amfoo
+
+echo '% with opts'
+hg cleanst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-alias.out Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,10 @@
+% basic
+% unknown
+*** [alias] unknown: command bargle is unknown
+% ambiguous
+*** [alias] ambiguous: command s is ambiguous
+% recursive
+*** [alias] recursive: circular dependency on recursive
+adding foo
+% with opts
+C foo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-children Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,59 @@
+#!/bin/sh
+# test children command
+
+cat <<EOF >> $HGRCPATH
+[extensions]
+hgext.children=
+EOF
+
+echo "% init"
+hg init t
+cd t
+
+echo "% no working directory"
+hg children
+
+echo % setup
+echo 0 > file0
+hg ci -qAm 0 -d '0 0'
+
+echo 1 > file1
+hg ci -qAm 1 -d '1 0'
+
+echo 2 >> file0
+hg ci -qAm 2 -d '2 0'
+
+hg co null
+echo 3 > file3
+hg ci -qAm 3 -d '3 0'
+
+echo "% hg children at revision 3 (tip)"
+hg children
+
+hg co null
+echo "% hg children at nullrev (should be 0 and 3)"
+hg children
+
+hg co 1
+echo "% hg children at revision 1 (should be 2)"
+hg children
+
+hg co 2
+echo "% hg children at revision 2 (other head)"
+hg children
+
+for i in null 0 1 2 3; do
+ echo "% hg children -r $i"
+ hg children -r $i
+done
+
+echo "% hg children -r 0 file0 (should be 2)"
+hg children -r 0 file0
+
+echo "% hg children -r 1 file0 (should be 2)"
+hg children -r 1 file0
+
+hg co 0
+echo "% hg children file0 at revision 0 (should be 2)"
+hg children file0
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-children.out Fri Jul 06 08:55:36 2007 +0200
@@ -0,0 +1,62 @@
+% init
+% no working directory
+% setup
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+% hg children at revision 3 (tip)
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+% hg children at nullrev (should be 0 and 3)
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg children at revision 1 (should be 2)
+changeset: 2:8f5eea5023c2
+user: test
+date: Thu Jan 01 00:00:02 1970 +0000
+summary: 2
+
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg children at revision 2 (other head)
+% hg children -r null
+changeset: 0:4df8521a7374
+user: test
+date: Thu Jan 01 00:00:00 1970 +0000
+summary: 0
+
+changeset: 3:e2962852269d
+tag: tip
+parent: -1:000000000000
+user: test
+date: Thu Jan 01 00:00:03 1970 +0000
+summary: 3
+
+% hg children -r 0
+changeset: 1:708c093edef0
+user: test
+date: Thu Jan 01 00:00:01 1970 +0000
+summary: 1
+
+% hg children -r 1
+changeset: 2:8f5eea5023c2
+user: test
+date: Thu Jan 01 00:00:02 1970 +0000
+summary: 2
+
+% hg children -r 2
+% hg children -r 3
+% hg children -r 0 file0 (should be 2)
+changeset: 2:8f5eea5023c2
+user: test
+date: Thu Jan 01 00:00:02 1970 +0000
+summary: 2
+
+% hg children -r 1 file0 (should be 2)
+changeset: 2:8f5eea5023c2
+user: test
+date: Thu Jan 01 00:00:02 1970 +0000
+summary: 2
+
+1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+% hg children file0 at revision 0 (should be 2)
+changeset: 2:8f5eea5023c2
+user: test
+date: Thu Jan 01 00:00:02 1970 +0000
+summary: 2
+
--- a/tests/test-import Fri Jul 06 08:48:28 2007 +0200
+++ b/tests/test-import Fri Jul 06 08:55:36 2007 +0200
@@ -93,6 +93,24 @@
hg --cwd b tip | grep second
rm -r b
+# subject: duplicate detection, removal of [PATCH]
+cat > mkmsg2.py <<EOF
+import email.Message, sys
+msg = email.Message.Message()
+msg.set_payload('email patch\n\nnext line\n' + open('tip.patch').read())
+msg['Subject'] = '[PATCH] email patch'
+msg['From'] = 'email patcher'
+sys.stdout.write(msg.as_string())
+EOF
+
+echo '% plain diff in email, [PATCH] subject, message body with subject'
+hg clone -r0 a b
+hg --cwd a diff -r0:1 > tip.patch
+python mkmsg2.py | hg --cwd b import -
+hg --cwd b tip --template '{desc}\n'
+rm -r b
+
+
# bug non regression test
# importing a patch in a subdirectory failed at the commit stage
echo line 2 >> a/d1/d2/a
--- a/tests/test-import.out Fri Jul 06 08:48:28 2007 +0200
+++ b/tests/test-import.out Fri Jul 06 08:55:36 2007 +0200
@@ -100,6 +100,17 @@
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
applying patch from stdin
summary: second change
+% plain diff in email, [PATCH] subject, message body with subject
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 2 changes to 2 files
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying patch from stdin
+email patch
+
+next line
% hg import in a subdirectory
requesting all changes
adding changesets